Packages

An extension on `Ecto.Schema` where you can provide additional options, which will be read by the corresponding `PropSchema.TestHarness` module, used in the test files to generate property tests.

Current section

Files

Jump to
prop_schema lib prop_schema.ex
Raw

lib/prop_schema.ex

defmodule PropSchema do
@moduledoc """
An extension on `Ecto.Schema` where you can provide additional options, which will be read by the corresponding
`PropSchema.Executor` module, used in the test files to generate property tests.
"""
alias PropSchema.Types
defmacro __using__(_opts) do
quote do
import PropSchema, only: [prop_schema: 2, prop_embedded: 1]
use Ecto.Schema
Module.register_attribute(__MODULE__, :prop_schema_fields, accumulate: true)
end
end
@doc """
Declares an `Ecto.Schema` with additional mechanisms that will be used under the hood to generate property tests
using `PropSchema.Executor`.
## Field Declaration
When a field is declared with `prop_field/3` it will add the extra values to the
additional mechanisms, but a field can just be declared with `Ecto.Schema.field/3` and `PropSchema.Executor` will
not know about it.
## Examples
prop_schema "example" do
prop_field(:example_string, :string, string_type: :alphanumeric, required: true)
prop_field(:example_int, :integer, postive: true, required: false)
field(:example_float, :float)
end
"""
@spec prop_schema(String.t(), do: Types.ast_expression()) :: Types.ast_expression()
defmacro prop_schema(source, do: block) do
schema(source, block)
end
@doc """
High school english class time! `prop_embedded/1` is to `prop_schema/2` as `Ecto.Schema.embedded_schema/1` is to `Ecto.Schema.schema/2`
## Examples
prop_embedded do
prop_field(:example_string, :string, string_type: :alphanumeric, required: true)
prop_field(:example_int, :integer, postive: true, required: false)
field(:example_float, :float)
end
"""
@spec prop_embedded(do: Types.ast_expression()) :: Types.ast_expression()
defmacro prop_embedded(do: block) do
embedded_schema(block)
end
defp schema(source, block) do
quote do
try do
import PropSchema
schema(unquote(source), do: unquote(block))
after
:ok
end
Module.eval_quoted(__ENV__, [
PropSchema.__prop_schema__(@prop_schema_fields)
])
end
end
defp embedded_schema(block) do
quote do
try do
import PropSchema
embedded_schema(do: unquote(block))
after
:ok
end
Module.eval_quoted(__ENV__, [
PropSchema.__prop_schema__(@prop_schema_fields)
])
end
end
@doc """
Declares a field in the schema, processes it for use in `PropSchema.Executor` and then passes it through to `Ecto.Schema.field/3`. See `prop_schema/2` for examples.
"""
@spec prop_field(atom(), atom(), keyword()) :: Types.ast_expression()
defmacro prop_field(name, type \\ :string, opts \\ []) do
quote do
PropSchema.__field__(__MODULE__, unquote(name), unquote(type), unquote(opts))
field(unquote(name), unquote(type), unquote(opts))
end
end
def __field__(mod, name, type, opts) do
Module.put_attribute(mod, :prop_schema_fields, {name, {type, Enum.into(opts, %{})}})
end
def __prop_schema__(prop_schema_fields) do
map = prop_schema_fields |> Enum.into(%{}) |> Macro.escape()
quote do
def __prop_schema__, do: unquote(map)
end
end
end