Packages
zoi
0.15.0
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/array.ex
defmodule Zoi.Types.Array do
@moduledoc false
use Zoi.Type.Def, fields: [:inner, :min_length, :max_length, :length, coerce: false]
alias Zoi.Validations
def opts() do
error = "invalid type: expected integer"
Zoi.Opts.meta_opts()
|> Zoi.Opts.with_coerce()
|> Zoi.Types.Extend.new(
min_length:
Zoi.Opts.constraint_schema(Zoi.Types.Integer.new([]),
description: "array minimum length",
error: error
),
max_length:
Zoi.Opts.constraint_schema(Zoi.Types.Integer.new([]),
description: "array maximum length",
error: error
),
length:
Zoi.Opts.constraint_schema(Zoi.Types.Integer.new([]),
description: "array exact length",
error: error
)
)
end
def new(inner, opts) when is_struct(inner) do
{validation_opts, opts} = Keyword.split(opts, [:min_length, :max_length, :length])
apply_type([inner: inner] ++ opts)
|> Validations.maybe_set_validation(Validations.Gte, validation_opts[:min_length])
|> Validations.maybe_set_validation(Validations.Lte, validation_opts[:max_length])
|> Validations.maybe_set_validation(Validations.Length, validation_opts[:length])
end
def new(inner, _opts) do
raise ArgumentError,
"you should use a valid Zoi schema, got: #{inspect(inner)}"
end
defimpl Zoi.Type do
def parse(%Zoi.Types.Array{inner: inner} = schema, inputs, _opts) when is_list(inputs) do
inputs
|> Enum.with_index()
|> Enum.reduce({[], []}, fn {input, index}, {parsed, errors} ->
ctx = Zoi.Context.new(inner, input) |> Zoi.Context.add_path([index])
case Zoi.parse(inner, input, ctx: ctx) do
{:ok, value} ->
{[value | parsed], errors}
{:error, err} ->
new_errors =
Enum.reduce(err, errors, fn e, acc ->
[Zoi.Error.prepend_path(e, [index]) | acc]
end)
{parsed, new_errors}
end
end)
|> then(&finalize_result(&1, schema))
end
def parse(%Zoi.Types.Array{coerce: true} = schema, inputs, opts) when is_map(inputs) do
cond do
has_index_keys?(inputs) ->
inputs
|> Enum.filter(fn {key, _value} -> index_key?(key) end)
|> Enum.sort_by(fn {key, _value} -> parse_index(key) end)
|> Enum.map(fn {_key, value} -> value end)
|> then(&parse(schema, &1, opts))
map_size(inputs) == 0 ->
parse(schema, [], opts)
true ->
parse(schema, [inputs], opts)
end
end
def parse(%Zoi.Types.Array{} = schema, input, opts) when is_tuple(input) do
input
|> Tuple.to_list()
|> then(fn list -> parse(schema, list, opts) end)
end
def parse(schema, _, _) do
{:error, Zoi.Error.invalid_type(:array, error: schema.meta.error)}
end
defp finalize_result({parsed, errors}, schema) do
parsed = Enum.reverse(parsed)
if errors == [] do
case validate_constraints(schema, parsed) do
:ok ->
{:ok, parsed}
{:error, new_errors} ->
{:error, new_errors, parsed}
end
else
{:error, Enum.reverse(errors), parsed}
end
end
defp validate_constraints(schema, input) do
[
{Validations.Length, schema.length},
{Validations.Gte, schema.min_length},
{Validations.Lte, schema.max_length}
]
|> Validations.run_validations(schema, input)
end
defp has_index_keys?(map) do
Enum.any?(map, fn {key, _value} -> index_key?(key) end)
end
defp index_key?(key) when is_integer(key), do: true
defp index_key?(key) when is_binary(key) do
case Integer.parse(key) do
{_int, ""} -> true
_ -> false
end
end
defp index_key?(_), do: false
defp parse_index(key) when is_integer(key), do: key
defp parse_index(key) when is_binary(key) do
{int, _} = Integer.parse(key)
int
end
end
defimpl Zoi.TypeSpec do
def spec(%Zoi.Types.Array{inner: inner}, opts) do
inner_spec = Zoi.TypeSpec.spec(inner, opts)
quote do
[unquote(inner_spec)]
end
end
end
defimpl Inspect do
alias Zoi.Validations
def inspect(type, opts) do
extra_fields = [
inner: Inspect.inspect(type.inner, opts),
min_length: Validations.unwrap_validation(type.min_length),
max_length: Validations.unwrap_validation(type.max_length),
length: Validations.unwrap_validation(type.length)
]
Zoi.Inspect.build(type, opts, extra_fields)
end
end
defimpl Zoi.JSONSchema.Encoder do
def encode(%{inner: %Zoi.Types.Any{}} = schema) do
%{type: :array}
|> add_constraints(schema)
end
def encode(schema) do
%{type: :array, items: Zoi.JSONSchema.encode_schema(schema.inner)}
|> add_constraints(schema)
end
defp add_constraints(map, schema) do
map
|> maybe_add(:minItems, schema.min_length)
|> maybe_add(:maxItems, schema.max_length)
|> maybe_add_length(schema.length)
end
defp maybe_add(map, _key, nil), do: map
defp maybe_add(map, key, {value, _opts}), do: Map.put(map, key, value)
defp maybe_add_length(map, nil), do: map
defp maybe_add_length(map, {value, _opts}),
do: map |> Map.put(:minItems, value) |> Map.put(:maxItems, value)
end
defimpl Zoi.Validations.Gte do
def set(schema, value, opts \\ []) do
%{schema | min_length: {value, opts}, length: nil}
end
def validate(_schema, input, value, opts) do
if length(input) >= value do
:ok
else
{:error, Zoi.Error.greater_than_or_equal_to(:array, value, opts)}
end
end
end
defimpl Zoi.Validations.Lte do
def set(schema, value, opts \\ []) do
%{schema | max_length: {value, opts}, length: nil}
end
def validate(_schema, input, value, opts) do
if length(input) <= value do
:ok
else
{:error, Zoi.Error.less_than_or_equal_to(:array, value, opts)}
end
end
end
defimpl Zoi.Validations.Length do
def set(schema, value, opts \\ []) do
%{schema | length: {value, opts}, min_length: nil, max_length: nil}
end
def validate(_schema, input, value, opts) do
if length(input) == value do
:ok
else
{:error, Zoi.Error.invalid_length(:array, value, opts)}
end
end
end
defimpl Zoi.Describe.Encoder do
def encode(%{inner: inner}) do
"list of #{Zoi.Describe.Encoder.encode(inner)}"
end
end
end