Packages
zoi
0.18.2
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/map.ex
defmodule Zoi.Types.Map do
@moduledoc false
use Zoi.Type.Def,
fields: [:key_type, :value_type, :fields, :unrecognized_keys, :coerce, empty_values: []]
alias Zoi.Types.Meta
def opts() do
Zoi.Opts.complex_type_opts()
end
def new(fields, opts) when (is_map(fields) and not is_struct(fields)) or is_list(fields) do
fields =
Enum.map(fields, fn {key, type} ->
if type.meta.required == nil do
{key, Zoi.required(type)}
else
{key, type}
end
end)
opts =
opts
|> resolve_unrecognized_keys()
|> Keyword.put_new(:coerce, false)
apply_type(opts ++ [fields: fields])
end
def new(_fields, _opts) do
raise ArgumentError, "expected a map with field definitions"
end
def new(key_type, value_type, opts) when is_struct(key_type) do
opts =
opts
|> resolve_unrecognized_keys()
|> Keyword.put_new(:coerce, false)
apply_type(Keyword.merge(opts, key_type: key_type, value_type: value_type))
end
def new(_key_value, _value_type, _opts) do
raise ArgumentError, "expected a map with valid key and type definitions"
end
defp resolve_unrecognized_keys(opts) do
case {opts[:unrecognized_keys], opts[:strict]} do
{nil, nil} -> Keyword.put(opts, :unrecognized_keys, :strip)
{nil, true} -> opts |> Keyword.delete(:strict) |> Keyword.put(:unrecognized_keys, :error)
{nil, false} -> opts |> Keyword.delete(:strict) |> Keyword.put(:unrecognized_keys, :strip)
{_, _} -> Keyword.delete(opts, :strict)
end
end
defimpl Zoi.Type do
def parse(%Zoi.Types.Map{fields: fields} = type, input, opts)
when is_list(fields) and is_map(input) and not is_struct(input) do
Zoi.Types.KeyValue.parse(type, input, opts)
end
def parse(%Zoi.Types.Map{} = schema, input, _opts)
when is_map(input) and not is_struct(input) do
Enum.reduce(input, {%{}, []}, fn {key, value}, {input, errors} ->
with {:ok, key_parsed} <- Zoi.parse(schema.key_type, key),
{:ok, value_parsed} <- Zoi.parse(schema.value_type, value) do
{Map.put(input, key_parsed, value_parsed), errors}
else
{:error, err} ->
new_errors =
Enum.reduce(err, errors, fn e, acc ->
[Zoi.Error.prepend_path(e, [key]) | acc]
end)
{input, new_errors}
end
end)
|> then(fn {parsed, errors} ->
if errors == [] do
{:ok, parsed}
else
{:error, Enum.reverse(errors), parsed}
end
end)
end
def parse(%Zoi.Types.Map{fields: fields, coerce: true} = type, input, opts)
when is_list(fields) and is_struct(input) do
parse(type, Map.from_struct(input), opts)
end
def parse(%Zoi.Types.Map{coerce: true} = schema, input, opts)
when is_struct(input) do
parse(schema, Map.from_struct(input), opts)
end
def parse(schema, _, _) do
{:error, Zoi.Error.invalid_type(:map, error: schema.meta.error)}
end
end
defimpl Zoi.TypeSpec do
# If the keys are strings, there isn't a good way to represent that in typespecs
def spec(%Zoi.Types.Map{fields: [{key, _val} | _rest]}, _opts) when is_binary(key) do
quote do: map()
end
def spec(%Zoi.Types.Map{fields: fields}, opts) when is_list(fields) do
fields
|> Enum.map(fn {key, type} ->
{key, Zoi.type_spec(type, opts), type}
end)
|> Enum.map(fn {key, type_spec, type} ->
case Meta.required?(type.meta) do
true -> quote do: {required(unquote(key)), unquote(type_spec)}
_ -> quote do: {optional(unquote(key)), unquote(type_spec)}
end
end)
|> then("e(do: %{unquote_splicing(&1)}))
end
def spec(%Zoi.Types.Map{key_type: key_type, value_type: value_type}, opts)
when is_struct(key_type) do
key_spec = Zoi.type_spec(key_type, opts)
value_spec = Zoi.type_spec(value_type, opts)
# If key and value are any type, we use map() (any map)
if key_type == Zoi.any() and value_type == Zoi.any() do
quote do
map()
end
else
quote do
%{optional(unquote(key_spec)) => unquote(value_spec)}
end
end
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(%Zoi.Types.Map{fields: fields} = type, opts) when is_list(fields) do
fields_doc =
container_doc("%{", fields, "}", %Inspect.Opts{limit: 10}, fn
{key, schema}, _opts -> concat("#{key}: ", Inspect.inspect(schema, opts))
end)
Zoi.Inspect.build(type, opts, fields: fields_doc)
end
# Key/value mode - simple format without coerce/strict
def inspect(type, opts) do
list = [
key: Inspect.inspect(type.key_type, opts),
value: Inspect.inspect(type.value_type, opts)
]
container_doc("#Zoi.map<", list, ">", %Inspect.Opts{limit: 8}, fn
{key, value}, _opts -> concat("#{key}: ", value)
end)
end
end
defimpl Zoi.JSONSchema.Encoder do
def encode(%Zoi.Types.Map{fields: fields} = schema) when is_list(fields) do
%{
type: :object,
properties:
Enum.into(fields, %{}, fn {key, value} ->
{key, Zoi.JSONSchema.encode_schema(value)}
end),
required:
Enum.flat_map(fields, fn {k, v} ->
if Meta.required?(v.meta), do: [k], else: []
end),
additionalProperties: schema.unrecognized_keys != :error
}
end
def encode(_schema), do: %{type: :object}
end
defimpl Zoi.Describe.Encoder do
def encode(_schema), do: "`t:map/0`"
end
end