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, :arg, :parent_data]
defstruct errors: [], keypath: [], datapath: [], data: nil, arg: %{}, parent_data: nil
def new(data, arg \\ %{}) do
%Context{errors: [], keypath: [], datapath: [], data: data, arg: arg, parent_data: nil}
end
def downpath(
%__MODULE__{keypath: kpath, datapath: dpath, data: parent_data} = ctx,
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__{
ctx
| keypath: [key | kpath],
datapath: [serkey | dpath],
errors: [],
data: data,
parent_data: parent_data
}
end
@error_kinds ~w(subcontext type required cast constraint generator)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_generator_error(ctx, field, reason) do
add_error(ctx, {:generator, %{field: field, 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