defprotocol Rubbergloves.Validation do alias Rubbergloves.Errors.Validation.ValidationError @fallback_to_any true @spec valid?(any) :: Boolean.t def valid?(validation) @spec errors(any) :: ValidationError | [] def errors(validation) end defimpl Rubbergloves.Validation, for: Justify.Dataset do alias Rubbergloves.Errors.Validation.ValidationError def valid?(dataset), do: dataset.valid? def errors(dataset), do: %ValidationError{ errors: dataset.errors} end defimpl Rubbergloves.Validation, for: Any do alias Rubbergloves.Errors.Validation.ValidationError def valid?(:ok), do: true def valid?(:invalid_type), do: false def errors(:ok), do: [] def errors(:invalid_type), do: %ValidationError{ errors: [{:type, "Invalid type"}]} end defmodule Rubbergloves.Validator do # @callback required(struct(), atom()) :: any() @callback required(struct(), atom()) :: any() @callback type(struct(), atom(), any()) :: any() end defmodule Rubbergloves.Validator.Justify do alias Justify.Validators @behaviour Rubbergloves.Validator @impl true def required(struct, key), do: Justify.validate_required(struct, [key]) @impl true def type(struct, key, type), do: Validators.Type.call(struct, key, type) end defmodule Justify.Validators.Type do def call(dataset, field, type) do dataset = Justify.Dataset.new(dataset) value = Map.get(dataset.data, field) with :ok <- confirm_type(value, type) do dataset else :error -> Justify.add_error(dataset, field, "has invalid type expected #{type}", validation: :type) {:error, error} -> Justify.add_error(dataset, field, error, validation: :type) end end defp confirm_type(nil, _), do: :ok defp confirm_type(value, :string) when is_binary(value), do: :ok defp confirm_type(value, :integer) when is_integer(value), do: :ok defp confirm_type(value, :map) when is_map(value), do: :ok defp confirm_type(value, {:one, atom}) when is_atom(atom) and is_map(value) do type = Map.get(value, :__struct__, nil) if(type == atom) do :ok else {:error, "has invalid type expected one #{atom} struct"} end end defp confirm_type(_, {:one, atom}) when is_atom(atom), do: {:error, "has invalid type expected one #{atom} struct"} defp confirm_type([], {:many, atom}) when is_atom(atom), do: :ok defp confirm_type(value, {:many, atom}) when is_atom(atom) and is_list(value) do all = Enum.all?(value, fn item -> case item do %{__struct__: type} -> type == atom _ -> false end end) if(all) do :ok else {:error, "has invalid type expected a list of type: #{atom}"} end end defp confirm_type(_, {:many, atom}) when is_atom(atom), do: {:error, "has invalid type expected a list of type: #{atom}"} defp confirm_type(_, _), do: :error end