Packages

A normalization/denormalization that works well with Elixir datastructures.

Current section

Files

Jump to
hugs lib hugs context.ex
Raw

lib/hugs/context.ex

defmodule Hugs.Context do
@moduledoc false
alias __MODULE__
# this module represents a versatile context used in different operations:
# normalization, denormalization, building data from primitives. Any operation
# involving working with a data tree and contracts.
require Record
Record.defrecord(:__e, :hugs_error,
datapath: [],
keypath: [],
kind: nil,
props: %{},
ctx_data: nil
)
@enforce_keys [:errors, :keypath, :datapath, :data]
defstruct errors: [], keypath: [], datapath: [], data: nil
def new(data) do
%Context{errors: [], keypath: [], datapath: [], data: data}
end
def downpath(%__MODULE__{keypath: kpath, datapath: dpath}, key, serkey, data) do
# create a new context with the extended path given from key/serkey. As it
# is a new context, it has no errors for now
%__MODULE__{keypath: [key | kpath], datapath: [serkey | dpath], errors: [], data: data}
end
@error_kinds ~w(subcontext type required cast constraint)a
defp add_error(%__MODULE__{errors: errors} = this, {kind, _} = err) when kind in @error_kinds do
%__MODULE__{this | errors: [err | errors]}
end
def with_constraint_error(ctx, constraint, reason, data) do
add_error(ctx, {:constraint, %{data: data, reason: reason, constraint: constraint}})
end
def with_cast_error(ctx, reason, data) do
add_error(ctx, {:cast, %{data: data, reason: reason}})
end
def with_required_error(ctx, %Hugs.FieldDefinition{key: key, required: true, serkey: serkey}) do
add_error(ctx, {:required, %{key: key, serkey: serkey}})
end
def with_type_error(ctx, type, data) do
add_error(ctx, {:type, %{data: data, type: type}})
end
def with_subcontext_error(ctx, %__MODULE__{} = sub) do
# note that we do not pas a map of error properties but directly the
# subcontext
add_error(ctx, {:subcontext, sub})
end
def collect_errors(%__MODULE__{} = this) do
collect_errors(this, [])
end
def collect_errors(
%__MODULE__{errors: errors, keypath: kpath, datapath: dpath, data: data},
acc
) do
kpath = :lists.reverse(kpath)
dpath = :lists.reverse(dpath)
Enum.reduce(errors, acc, fn
{:subcontext, sub}, acc ->
collect_errors(sub, acc)
{kind, props}, acc ->
[__e(datapath: dpath, keypath: kpath, kind: kind, props: props, ctx_data: data) | acc]
end)
end
end