Current section

Files

Jump to
cqrs_tools lib cqrs command_validation.ex
Raw

lib/cqrs/command_validation.ex

defmodule Cqrs.CommandValidation do
@moduledoc """
Defines validation functions for a validated command.
## Example
defmodule CreateUser do
use Cqrs.Command
alias Cqrs.CommandValidation
field :email, :string
field :name, :string
field :id, :binary_id, internal: true
derive_event UserCreated
@impl true
def handle_validate(command, _opts) do
Ecto.Changeset.validate_format(command, :email, ~r/@/)
end
@impl true
def after_validate(%{email: email} = command) do
Map.put(command, :id, UUID.uuid5(:oid, email))
end
@impl true
def before_dispatch(command, _opts) do
command
|> CommandValidation.new()
|> CommandValidation.add(&ensure_uniqueness/1)
|> CommandValidation.run()
end
@impl true
def handle_dispatch(_command, _opts) do
{:ok, :dispatched}
end
defp ensure_uniqueness(%{id: id}) do
if Repo.exists?(from u in User, where: u.id == ^id),
do: {:error, "user already exists"},
else: :ok
end
end
"""
@type command :: map()
@type t :: %__MODULE__{command: command(), validations: list()}
@type validation_function ::
(command() -> any() | {:error, any()})
| (command(), keyword() -> any() | {:error, any()})
defstruct [:command, validations: []]
@doc """
Creates a new `Cqrs.CommandValidation` struct.
"""
@spec new(command()) :: t()
def new(command), do: %__MODULE__{command: command}
@doc """
Adds a `validation_function` to the list of validations to run.
"""
@spec add(t(), validation_function()) :: t()
def add(%__MODULE__{validations: validations} = validation, fun)
when is_function(fun, 1) or is_function(fun, 2) do
%{validation | validations: [fun | validations]}
end
@doc """
Runs the list of 'validation_function' functions
"""
@spec run(t(), keyword()) :: {:ok, command()} | {:error, list()}
def run(%__MODULE__{command: command} = validation, opts \\ []) do
case collect_errors(validation, opts) do
[] -> {:ok, command}
errors -> {:error, Keyword.get_values(errors, :error)}
end
end
defp collect_errors(%{command: command, validations: validations}, opts) do
run_validation = fn
fun, acc when is_function(fun, 1) -> [fun.(command) | acc]
fun, acc when is_function(fun, 2) -> [fun.(command, opts) | acc]
end
validations
|> Enum.reduce([], run_validation)
|> Enum.filter(&match?({:error, _}, &1))
end
end