defmodule Fussy.Validators.Tuple do
@behaviour Fussy.Validator
alias Fussy.Utils
defstruct inner: nil, strict: true
@opaque t :: %__MODULE__{}
@spec new(tuple(), strict: boolean()) :: __MODULE__.t()
def new(inner, args \\ []) when is_tuple(inner) do
struct!(%__MODULE__{}, Keyword.merge(args, inner: inner))
end
@impl true
def validate(%__MODULE__{inner: {}}, {}), do: {:ok, {}}
@impl true
def validate(%__MODULE__{inner: inner}, tuple)
when is_tuple(tuple) and tuple_size(inner) == tuple_size(tuple) do
0..(tuple_size(inner) - 1)
|> Enum.reduce({{}, []}, fn idx, {valid_tuple, errors} ->
validator = elem(inner, idx)
item = elem(tuple, idx)
case Utils.validate_using(validator, item) do
{:ok, valid_item} ->
{Tuple.append(valid_tuple, valid_item), errors}
{:error, reason} ->
{valid_tuple, ["idx #{idx} => {#{reason |> Enum.join(", ")}}" | errors]}
end
end)
|> then(fn
{valid_tuple, []} -> {:ok, valid_tuple}
{_, errors} -> {:error, errors |> Enum.reverse()}
end)
end
@impl true
def validate(%__MODULE__{inner: inner, strict: false} = v, list)
when is_list(list) and tuple_size(inner) == length(list) do
validate(v, List.to_tuple(list))
end
@impl true
def validate(%__MODULE__{inner: inner}, _),
do: {:error, ["must be a tuple of size #{tuple_size(inner)}"]}
end