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
      end

      def new(values) do
        case Fussy.Utils.validate_using(@validator, values) do
          {:ok, map} -> {:ok, struct!(%__MODULE__{}, map)}
          {:error, reason} -> {:error, reason}
        end
      end

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

      @impl true
      def validate(_, values), do: new(values)
    end
  end
end