lib/kungfuig/validator.ex

defmodule Kungfuig.Validator do
  @moduledoc """
  Generic validator encapsulation.

  Default implementation is
    [`NimbleOptions`](https://github.com/dashbitco/nimble_options).
  """

  @doc """
  Validates given options with the schema, provided by the implementation of this behaviour
  """
  @callback validate(options :: map() | keyword()) ::
              {:ok, validated_options :: keyword()} | {:error, any()}

  @doc """
  Generates a documentation for expected options
  """
  @callback doc(options :: keyword()) :: String.t()

  defmacro __using__(opts) do
    quote location: :keep, generated: true do
      @behaviour Kungfuig.Validator

      @validator (case Keyword.get(unquote(opts), :with, NimbleOptions) do
                    module when is_atom(module) -> {module, :validate}
                    {module, fun} when is_atom(module) and is_atom(fun) -> {module, fun}
                  end)
      @documentor (case Keyword.get(unquote(opts), :docs, elem(@validator, 0)) do
                     module when is_atom(module) -> {module, :docs}
                     {module, fun} when is_atom(module) and is_atom(fun) -> {module, fun}
                   end)
      @schema Keyword.get(unquote(opts), :schema, [])

      @impl Kungfuig.Validator
      def validate(options) when is_map(options),
        do: with({:ok, kw} <- options |> Map.to_list() |> validate(), do: {:ok, Map.new(kw)})

      @impl Kungfuig.Validator
      def validate(options) when is_list(options),
        do: with({m, f} <- @validator, do: apply(m, f, [options, @schema]))

      @impl Kungfuig.Validator
      def doc(options \\ []) when is_list(options),
        do: with({m, f} <- @documentor, do: apply(m, f, [@schema, options]))
    end
  end
end