Packages
zoi
0.18.7
0.18.7
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.9.0-rc.1
0.9.0-rc.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
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.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
Current section
Files
Jump to
Current section
Files
lib/zoi/types/meta.ex
defmodule Zoi.Types.Meta do
@moduledoc false
@type effect ::
{:transform, Zoi.transform()}
| {:refine, Zoi.refinement()}
@type t :: %__MODULE__{
effects: [effect()],
error: binary() | nil,
required: boolean(),
description: binary() | nil,
example: Zoi.input(),
metadata: keyword(),
typespec: Macro.t() | nil,
deprecated: binary() | nil
}
@struct_fields [
effects: [],
metadata: [],
required: nil,
error: nil,
description: nil,
example: nil,
typespec: nil,
deprecated: nil
]
@struct_keys Keyword.keys(@struct_fields)
defstruct @struct_fields
# When we propagate the meta schema fields to other schemas, these are the allowed keys
# An example is when using nullable type, which is a union under the hood. We should be able
# to propagate these fields to the union since nullable is a behavior
@propagate_keys [:required, :description, :example, :metadata, :typespec, :deprecated, :error]
@spec create_meta(keyword()) :: {t(), keyword()}
def create_meta(opts) do
{meta_opts, rest_opts} = split_keyword(opts)
{struct!(__MODULE__, meta_opts), rest_opts}
end
@spec propagate_opts(t()) :: keyword()
def propagate_opts(%__MODULE__{} = meta) do
meta
|> Map.take(@propagate_keys)
|> Enum.reject(fn {_k, v} -> v == nil end)
end
defp split_keyword(opts) when is_list(opts) do
Enum.reduce(opts, {[], []}, fn {k, v}, {meta_opts, rest_opts} ->
if k in @struct_keys do
{Keyword.put(meta_opts, k, v), rest_opts}
else
{meta_opts, Keyword.put(rest_opts, k, v)}
end
end)
end
@spec run_effects(Zoi.Context.t()) :: {:ok, Zoi.Context.t()} | {:error, Zoi.Context.t()}
def run_effects(%Zoi.Context{schema: schema} = ctx) do
{ctx, has_partial} =
Enum.reduce(schema.meta.effects, {ctx, false}, fn
{:refine, refinement}, {ctx, has_partial} ->
case run_refinement(refinement, ctx.parsed, ctx) do
{:ok, value} ->
{Zoi.Context.add_parsed(ctx, value), has_partial}
{:error, errors} ->
{Zoi.Context.add_error(ctx, errors), has_partial}
{:error, errors, partial} ->
{ctx |> Zoi.Context.add_parsed(partial) |> Zoi.Context.add_error(errors), true}
end
{:transform, transform}, {ctx, has_partial} ->
case run_transform(transform, ctx.parsed, ctx) do
{:ok, value} ->
{Zoi.Context.add_parsed(ctx, value), has_partial}
{:error, errors} ->
{Zoi.Context.add_error(ctx, errors), has_partial}
{:error, errors, partial} ->
{ctx |> Zoi.Context.add_parsed(partial) |> Zoi.Context.add_error(errors), true}
end
end)
cond do
ctx.errors == [] -> {:ok, ctx}
has_partial -> {:error, ctx}
true -> {:error, %{ctx | parsed: nil}}
end
end
# Internal validation which uses Protocols in form of MFA: {ProtocolMod, :validate, [value, opts]}
defp run_refinement({mod, :validate, args}, input, ctx) do
case apply(mod, :validate, [ctx.schema, input] ++ args) do
:ok ->
{:ok, input}
{:error, err} ->
{:error, Zoi.Errors.add_error(err)}
end
end
defp run_refinement({mod, func, args}, input, ctx) do
case apply(mod, func, [input] ++ args ++ [[ctx: ctx]]) do
:ok ->
{:ok, input}
{:error, err} ->
{:error, Zoi.Errors.add_error(err)}
{:error, err, partial} ->
{:error, Zoi.Errors.add_error(err), partial}
%Zoi.Context{valid?: true, parsed: value} ->
{:ok, value}
%Zoi.Context{errors: errors, parsed: ^input} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: nil} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: partial} ->
{:error, errors, partial}
end
end
defp run_refinement(refine_func, input, ctx) when is_function(refine_func) do
result =
cond do
is_function(refine_func, 1) ->
refine_func.(input)
is_function(refine_func, 2) ->
refine_func.(input, ctx)
end
case result do
:ok ->
{:ok, input}
{:error, err} ->
{:error, Zoi.Errors.add_error(err)}
{:error, err, partial} ->
{:error, Zoi.Errors.add_error(err), partial}
%Zoi.Context{valid?: true, parsed: value} ->
{:ok, value}
%Zoi.Context{errors: errors, parsed: ^input} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: nil} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: partial} ->
{:error, errors, partial}
end
end
defp run_transform({mod, func, args}, input, ctx) do
case apply(mod, func, [input] ++ args ++ [[ctx: ctx]]) do
{:ok, value} ->
{:ok, value}
{:error, err} ->
{:error, Zoi.Errors.add_error(err)}
{:error, err, partial} ->
{:error, Zoi.Errors.add_error(err), partial}
%Zoi.Context{valid?: true, parsed: value} ->
{:ok, value}
%Zoi.Context{errors: errors, parsed: ^input} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: nil} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: partial} ->
{:error, errors, partial}
value ->
{:ok, value}
end
end
defp run_transform(transform_func, input, ctx) when is_function(transform_func) do
result =
cond do
is_function(transform_func, 1) ->
transform_func.(input)
is_function(transform_func, 2) ->
transform_func.(input, ctx)
end
case result do
{:ok, value} ->
{:ok, value}
{:error, err} ->
{:error, Zoi.Errors.add_error(err)}
{:error, err, partial} ->
{:error, Zoi.Errors.add_error(err), partial}
%Zoi.Context{valid?: true, parsed: value} ->
{:ok, value}
%Zoi.Context{errors: errors, parsed: ^input} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: nil} ->
{:error, errors}
%Zoi.Context{errors: errors, parsed: partial} ->
{:error, errors, partial}
value ->
{:ok, value}
end
end
@spec required?(t()) :: boolean()
def required?(%__MODULE__{required: required}) do
case required do
nil -> false
val -> val
end
end
@spec optional?(t()) :: boolean()
def optional?(%__MODULE__{} = meta) do
not required?(meta)
end
@spec deprecated(t()) :: binary() | nil
def deprecated(%__MODULE__{deprecated: deprecated}), do: deprecated
@spec deprecated?(t()) :: boolean()
def deprecated?(%__MODULE__{deprecated: deprecated}) do
deprecated not in [nil, false]
end
end