Packages
cqrs_tools
0.2.25
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.1
0.1.0
A collection of handy Elixir macros for CQRS applications.
Current section
Files
Jump to
Current section
Files
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,
errors
|> Keyword.get_values(:error)
|> Enum.sort()}
end
end
defp collect_errors(%{command: command, validations: validations}, opts) do
stream_opts = [
on_timeout: :kill_task,
timeout: Keyword.get(opts, :timeout, 5000),
max_concurrency: Keyword.get(opts, :max_concurrency, System.schedulers_online())
]
run_validation = fn
fun when is_function(fun, 1) -> fun.(command)
fun when is_function(fun, 2) -> fun.(command, opts)
end
validations
|> Task.async_stream(run_validation, stream_opts)
|> Stream.map(&elem(&1, 1))
|> Stream.map(fn
:timeout -> {:error, :timeout}
other -> other
end)
|> Enum.filter(&match?({:error, _}, &1))
end
end