Current section
Files
Jump to
Current section
Files
lib/kvasir/command/command.ex
defmodule Kvasir.Command do
@moduledoc ~S"""
"""
@typedoc @moduledoc
@type t :: term
@callback __command__(atom) :: any
@callback create(any) :: {:ok, t} | {:error, atom}
@callback create!(any) :: {:ok, t} | {:error, atom}
@callback factory(any) :: {:ok, map} | {:error, atom}
@callback validate(t) :: :ok | {:error, atom}
@doc @moduledoc
defmacro __using__(_opts \\ []) do
quote do
require Kvasir.Command
import Kvasir.Command, only: [command: 1]
end
end
defmacro field(name, type \\ :string, opts \\ []) do
quote do
Module.put_attribute(
__MODULE__,
:command_fields,
{unquote(name), unquote(type), unquote(opts)}
)
end
end
defmacro command(do: block) do
quote do
Module.register_attribute(__MODULE__, :command_fields, accumulate: true)
try do
import Kvasir.Command, only: [field: 1, field: 2, field: 3]
unquote(block)
after
:ok
end
@struct_fields Enum.reverse(@command_fields)
defstruct Enum.map(@struct_fields, &elem(&1, 0)) ++ [__meta__: %Kvasir.Command.Meta{}]
@behaviour Kvasir.Command
@doc false
@impl Kvasir.Command
def __command__(:fields), do: @struct_fields
@doc ~S"""
Create this command.
"""
@impl Kvasir.Command
def create(data), do: Kvasir.Command.create(__MODULE__, data)
@doc ~S"""
See: `create/1`.
"""
@impl Kvasir.Command
def create!(data), do: Kvasir.Command.create!(__MODULE__, data)
@doc false
@impl Kvasir.Command
def factory(payload), do: {:ok, payload}
@doc false
@impl Kvasir.Command
def validate(command), do: :ok
defoverridable validate: 1, factory: 1
defimpl Jason.Encoder, for: __MODULE__ do
alias Jason.EncodeError
alias Jason.Encoder.Map
alias Kvasir.Command.Encoder
def encode(value, opts) do
case Encoder.encode(value, encoding: :raw) do
{:ok, data} -> Map.encode(data, opts)
{:error, error} -> %EncodeError{message: "Command Encoding Error: #{error}"}
end
end
end
end
end
@doc ~S"""
Create a command by passing the command module and payload.
"""
def create(command, data) do
meta = %Kvasir.Command.Meta{
created: DateTime.utc_now()
}
with {:ok, payload} <- command.factory(data),
result <- struct!(command, Map.put(payload, :__meta__, meta)),
:ok <- command.validate(result) do
{:ok, result}
end
end
def create!(command, data) do
with {:ok, result} <- create(command, data) do
result
else
{:error, reason} -> raise "Failed to create #{command}, reason: #{reason}"
end
end
def is_command?(command) when is_atom(command), do: true
def is_command?(_), do: false
defdelegate decode(value, meta \\ %Kvasir.Command.Meta{}), to: Kvasir.Command.Decoder
end