Current section
Files
Jump to
Current section
Files
lib/zodish/type/optional.ex
defmodule Zodish.Type.Optional do
@moduledoc ~S"""
Makes a given type optional, where you can also define a default
value to be used when the value is `nil`.
"""
alias __MODULE__, as: TOptional
@type t() :: t(Zodish.Type.t())
@type t(inner_type) :: %TOptional{
inner_type: inner_type,
default: inner_type | (-> inner_type) | mfa() | nil
}
defstruct inner_type: nil,
default: nil
@doc ~S"""
Makes the given `inner_type` optional (either missing or nullable).
"""
def new(%_{} = inner_type, opts \\ []) do
Enum.reduce(opts, %TOptional{inner_type: inner_type}, fn
{:default, value}, type -> default(type, value)
{key, _}, _ -> raise(ArgumentError, "Unknown option #{inspect(key)} for Zodish.Type.Optional")
end)
end
@doc ~S"""
Assigns a default value to be used when the value is either missing
or nil. Alternatively, you can provide a zero-arity function or an
MFA that should generate the default value.
"""
def default(%TOptional{} = type, {m, f, a})
when is_atom(m) and is_atom(f) and is_list(a),
do: %{type | default: {m, f, a}}
def default(%TOptional{} = type, value)
when is_function(value, 0),
do: %{type | default: value}
def default(%TOptional{} = type, value) do
case Zodish.Type.parse(type.inner_type, value) do
{:ok, value} -> %{type | default: value}
{:error, _} -> raise(ArgumentError, "The default value must satisfy the inner type of Zodish.Type.Optional")
end
end
end
defimpl Zodish.Type, for: Zodish.Type.Optional do
alias Zodish.Type.Optional, as: TOptional
@impl Zodish.Type
def parse(%TOptional{default: nil}, nil), do: {:ok, nil}
def parse(%TOptional{} = type, nil) do
case resolve(type.default) do
nil -> {:ok, nil}
value -> Zodish.Type.parse(type.inner_type, value)
end
end
def parse(%TOptional{} = type, value), do: Zodish.Type.parse(type.inner_type, value)
#
# PRIVATE
#
defp resolve({m, f, a}), do: apply(m, f, a)
defp resolve(fun) when is_function(fun), do: apply(fun, [])
defp resolve(value), do: value
end