defmodule Z.Map do
@moduledoc """
A module for validating a Map
"""
alias Z.{Result, Error, Issue, Any}
use Z.Type,
options: Z.Any.__z__(:options) ++ [:atomize_keys, :size, :min, :max]
def check(result, :conversions, rules, context) do
result
|> Any.check(:conversions, rules, context)
end
def check(result, :mutations, rules, context) do
result
|> Any.check(:mutations, rules, context)
|> maybe_check(:atomize_keys, rules, context)
end
def check(result, :assertions, rules, context) do
result
|> Any.check(:assertions, rules, context)
|> maybe_check(:size, rules, context)
|> maybe_check(:min, rules, context)
|> maybe_check(:max, rules, context)
end
def check(result, _rule, _options, _context) when result.value == nil do
result
end
def check(result, :type, options, context) when not is_map(result.value) do
message = Keyword.get(options, :message, "input is not a Map")
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_type(),
message,
context
)
)
end
def check(result, :type, _options, _context) do
result
end
def check(result, _rule, _options, _context) when not is_map(result.value) do
result
end
def check(result, :atomize_keys, _enabled, _context) when is_struct(result.value) do
result
end
def check(result, :atomize_keys, false, _context) do
result
end
def check(result, :atomize_keys, true, context) do
check(result, :atomize_keys, :existing_only, context)
end
def check(result, :atomize_keys, mode, context)
when mode not in [:existing_only, :dangerously_allow_non_existing] do
message = "atomize_keys mode must be :existing_only or :dangerously_allow_non_existing"
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_arguments(),
message,
context
)
)
end
def check(result, :atomize_keys, :existing_only, context) do
{kv_list, result} =
Enum.map_reduce(result.value, result, fn kv, res ->
key_to_existing_atom(kv, res, context)
end)
result |> Result.set_value(Map.new(kv_list))
end
def check(result, :atomize_keys, :dangerously_allow_non_existing, _context) do
map =
result.value
|> Map.new(fn
{k, v} when is_binary(k) -> {String.to_atom(k), v}
{k, v} -> {k, v}
end)
result |> Result.set_value(map)
end
def check(result, :size, {size, _options}, context) when not is_integer(size) do
message = "unable to check size with size: #{inspect(size)}, size must be an integer"
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_arguments(),
message,
context
)
)
end
def check(result, :size, {size, options}, context) when map_size(result.value) < size do
message = Keyword.get(options, :message, "input does not have correct size")
result
|> Result.add_issue(
Issue.new(
Error.Codes.too_small(),
message,
context
)
)
end
def check(result, :size, {size, options}, context) when map_size(result.value) > size do
message = Keyword.get(options, :message, "input does not have correct size")
result
|> Result.add_issue(
Issue.new(
Error.Codes.too_big(),
message,
context
)
)
end
def check(result, :size, {_size, _options}, _context) do
result
end
def check(result, :size, size, context) do
check(result, :size, {size, []}, context)
end
def check(result, :min, {size, _options}, context) when not is_integer(size) do
message = "unable to check min size with size: #{inspect(size)}, size must be an integer"
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_arguments(),
message,
context
)
)
end
def check(result, :min, {size, options}, context) when map_size(result.value) < size do
message = Keyword.get(options, :message, "input is too small")
result
|> Result.add_issue(
Issue.new(
Error.Codes.too_small(),
message,
context
)
)
end
def check(result, :min, {_size, _options}, _context) do
result
end
def check(result, :min, size, context) do
check(result, :min, {size, []}, context)
end
def check(result, :max, {size, _options}, context) when not is_integer(size) do
message = "unable to check max size with size: #{inspect(size)}, size must be an integer"
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_arguments(),
message,
context
)
)
end
def check(result, :max, {size, options}, context) when map_size(result.value) > size do
message = Keyword.get(options, :message, "input is too big")
result
|> Result.add_issue(
Issue.new(
Error.Codes.too_big(),
message,
context
)
)
end
def check(result, :max, {_size, _options}, _context) do
result
end
def check(result, :max, size, context) do
check(result, :max, {size, []}, context)
end
defp key_to_existing_atom({k, v}, result, context) when is_binary(k) do
try do
a = String.to_existing_atom(k)
{{a, v}, result}
rescue
_ ->
message = "unable to atomize key"
result =
result
|> Result.add_issue(
Issue.new(
Error.Codes.invalid_string(),
message,
Z.Context.new(nil, k, context)
)
)
{{k, v}, result}
end
end
defp key_to_existing_atom({k, v}, result, _context) do
{{k, v}, result}
end
end