Packages

Check any struct against multiple valid and invalid state definitions

Current section

Files

Jump to
schrodinger lib schrodinger.ex
Raw

lib/schrodinger.ex

defmodule Schrodinger do
@moduledoc """
# Schrodinger
Check any struct against multiple valid and invalid state definitions
See README for more information
"""
defmacro __before_compile__(_env) do
quote do
def valid_states, do: @valid_state
def invalid_states, do: @invalid_state
end
end
defmacro __using__(_opts) do
quote do
Module.register_attribute(__MODULE__, :valid_state, accumulate: true, persist: false)
Module.register_attribute(__MODULE__, :invalid_state, accumulate: true, persist: false)
@before_compile Schrodinger
alias Schrodinger.{InvalidStates, Validation, ValidationError, Validations, ValidStates}
@type parent_struct :: __MODULE__.t()
@type response_tuple :: {:ok, parent_struct} | {:error, Validation.t()}
@spec valid?(parent_struct) :: boolean
def valid?(%__MODULE__{} = struct) do
case validate(struct) do
{:ok, _struct} -> true
{:error, _validation} -> false
end
end
@spec valid?(parent_struct, atom | [atom]) :: boolean
def valid?(%__MODULE__{} = struct, atoms) do
case validate(struct, atoms) do
{:ok, _struct} -> true
{:error, _validation} -> false
end
end
@spec invalid?(parent_struct) :: boolean
def invalid?(%__MODULE__{} = struct), do: !valid?(struct)
@spec validate(parent_struct) :: response_tuple
def validate(%__MODULE__{} = struct) do
case build_validation(struct) do
%Validation{valid_state_matches: valid} = validation when valid == [] ->
{:error, validation}
%Validation{invalid_state_matches: invalid} when invalid == [] ->
{:ok, struct}
validation ->
{:error, validation}
end
end
@spec validate(parent_struct, atom | [atom]) :: response_tuple
def validate(%__MODULE__{} = struct, atom) when is_atom(atom) do
validate(struct, [atom])
end
def validate(%__MODULE__{} = struct, atoms) do
validation = build_validation(struct)
%Validation{valid_state_matches: valid_matches, invalid_state_matches: invalid_matches} =
validation
cond do
Enum.any?(invalid_matches) -> {:error, validation}
Enum.any?(atoms, fn atom -> atom in valid_matches end) -> {:ok, struct}
true -> {:error, validation}
end
end
@spec validate!(parent_struct) :: parent_struct
def validate!(%__MODULE__{} = struct) do
case validate(struct) do
{:ok, struct} ->
struct
{:error, %Validation{valid_state_errors: errors, invalid_state_matches: matches}} ->
raise ValidationError, """
Invalid state matches: #{inspect(matches)}
Valid state errors: #{inspect(errors)}
"""
end
end
@spec build_validation(parent_struct) :: Validation.t()
defp build_validation(%__MODULE__{} = struct) do
%Validation{
valid_states: valid_states(),
invalid_states: invalid_states(),
struct: struct
}
|> ValidStates.get_errors()
|> InvalidStates.get_matches()
end
end
end
end