defmodule Parameter.Field do
@moduledoc """
This module define the structure of a Field inside a Parameter Schema.
"""
alias Parameter.Types
defstruct [:name, :key, :default, type: :string, required: false]
@type t :: %__MODULE__{
name: atom(),
key: binary(),
default: any(),
type: Types.t(),
required: boolean()
}
@spec new!(Keyword.t()) :: t() | no_return()
def new!(opts \\ []) do
case new(opts) do
{:error, error} -> raise ArgumentError, message: error
%__MODULE__{} = result -> result
end
end
@spec new(opts :: Keyword.t()) :: t() | {:error, binary()}
def new(opts \\ []) do
name = Keyword.get(opts, :name)
case Types.validate(:atom, name) do
:ok ->
key = Keyword.get(opts, :key, to_string(name))
opts
|> Keyword.put(:key, key)
|> do_new()
error ->
error
end
end
@spec load(t(), any()) :: {:ok, any} | {:error, binary()}
def load(%__MODULE__{type: type}, value) do
Types.load(type, value)
end
defp do_new(opts) do
key = Keyword.fetch!(opts, :key)
type = Keyword.get(opts, :type, :string)
default = Keyword.get(opts, :default)
required = Keyword.get(opts, :required, false)
default_valid? =
if default do
Types.validate(type, default)
else
:ok
end
type_valid? = type_valid?(type)
# Using Types module to validate field parameters
with :ok <- default_valid?,
:ok <- type_valid?,
:ok <- Types.validate(:string, key),
:ok <- Types.validate(:boolean, required) do
struct!(__MODULE__, opts)
end
end
defp type_valid?({type, _inner_type}) do
if type in Types.composite_types() do
:ok
else
custom_type_valid?(type)
end
end
defp type_valid?(type) do
if type in Types.base_types() do
:ok
else
custom_type_valid?(type)
end
end
defp custom_type_valid?(custom_type) do
if Kernel.function_exported?(custom_type, :load, 1) and
Kernel.function_exported?(custom_type, :validate, 1) do
:ok
else
{:error,
"#{inspect(custom_type)} is not a valid custom type, implement the `Parameter.Parametrizable` on custom modules"}
end
end
end