lib/fussy.ex

defmodule Fussy do
  defmacro __using__(_opts) do
    quote do
      import Fussy, only: [fields: 1]

      @acc_fields Map.new()

      @before_compile Fussy
    end
  end

  defmacro fields(do: block) do
    quote do
      import Fussy

      unquote(block)

      defstruct Map.keys(@acc_fields)

      @validator Fussy.Utils.map_validator_from_fields(@acc_fields)

      @fields Map.keys(@acc_fields) |> MapSet.new()

      # cleanup
      @acc_fields nil
    end
  end

  defmacro field(name, validator, opts \\ []) do
    quote do
      import Fussy.Constructors

      @acc_fields Map.put(@acc_fields, unquote(name), %{
                    validator: unquote(validator),
                    opts: unquote(opts)
                  })
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      alias Fussy.Utils

      @behaviour Fussy.Validator

      def fields, do: @fields
      def validator, do: @validator

      def new(values) do
        validate(@validator, [], values)
      end

      def new!(values) do
        {:ok, struct} = new(values)
        struct
      end

      def validate(%Fussy.Validators.FixedMap{} = v, values), do: validate(v, [], values)

      @impl true
      def validate(%Fussy.Validators.FixedMap{} = v, path, values) do
        case Utils.validate_using(v, path, values) do
          {:ok, map} -> {:ok, struct!(%__MODULE__{}, map)}
          {:error, reason} -> {:error, reason}
        end
      end
    end
  end
end