Packages
ash
1.4.1
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/type/type.ex
defmodule Ash.Type do
@list_constraints [
min_length: [
type: :non_neg_integer,
doc: "A minimum length for the items"
],
max_length: [
type: :non_neg_integer,
doc: "A maximum length for the items"
],
nil_items?: [
type: :boolean,
doc: "Whether or not the list can contain nil items",
default: true
]
]
@short_names [
term: Ash.Type.Term,
string: Ash.Type.String,
integer: Ash.Type.Integer,
boolean: Ash.Type.Boolean,
uuid: Ash.Type.UUID,
date: Ash.Type.Date,
utc_datetime: Ash.Type.UtcDatetime
]
@doc_list_constraints Keyword.put(@list_constraints, :items,
type: :any,
doc:
"Constraints for the elements of the list. See the contained type's docs for more."
)
@moduledoc """
This behaviour is a superset of the Ecto.Type behavior, that also contains
api level information, like what kinds of filters are allowed. Eventually,
this may be used for composite types or serialization.
Much better to `use Ash.Type` than to say `@behaviour Ash.Type` and define
everything yourself.
## Built in types
#{
Enum.map_join(@short_names, fn {key, module} ->
"* `#{inspect(key)}` - `#{inspect(module)}`\n"
end)
}
### Composite Types
Currently, the only composite type supported is a list type, specified via:
`{:array, Type}`. The constraints available are:
#{NimbleOptions.docs(@doc_list_constraints)}
"""
@type constraints :: Keyword.t()
@callback storage_type() :: Ecto.Type.t()
@callback ecto_type() :: Ecto.Type.t()
@callback cast_input(term) :: {:ok, term} | {:error, Keyword.t()} | :error
@callback cast_stored(term) :: {:ok, term} | :error
@callback dump_to_native(term) :: {:ok, term} | :error
@callback constraints() :: constraints()
@callback apply_constraints(term, constraints()) :: :ok | {:error, String.t() | [String.t()]}
@callback equal?(term, term) :: boolean
@type t :: atom | {:array, atom}
@doc false
def list_constraints, do: @list_constraints
@spec get_type(atom | module) :: atom | module
def get_type({:array, value}) do
{:array, get_type(value)}
end
def get_type(value) when is_atom(value) do
case Keyword.fetch(@short_names, value) do
{:ok, mod} -> mod
:error -> value
end
end
def get_type(value) do
value
end
@doc """
Returns the *underlying* storage type (the underlying type of the *ecto type* of the *ash type*)
"""
@spec storage_type(t()) :: Ecto.Type.t()
def storage_type({:array, type}), do: {:array, type.storage_type()}
def storage_type(type), do: type.storage_type()
@doc """
Returns the ecto compatible type for an Ash.Type.
If you `use Ash.Type`, this is created for you. For builtin types
this may return a corresponding ecto builtin type (atom)
"""
@spec ecto_type(t) :: Ecto.Type.t()
def ecto_type({:array, type}), do: {:array, ecto_type(type)}
for {name, mod} <- @short_names do
def ecto_type(unquote(name)), do: ecto_type(unquote(mod))
end
def ecto_type(type) do
type.ecto_type()
end
@spec ash_type?(term) :: boolean
@doc "Returns true if the value is a builtin type or adopts the `Ash.Type` behaviour"
def ash_type?({:array, value}), do: ash_type?(value)
def ash_type?(module) when is_atom(module) do
case Code.ensure_compiled(module) do
{:module, _} -> ash_type_module?(module)
_ -> false
end
end
def ash_type?(_), do: false
@doc """
Casts input (e.g. unknown) data to an instance of the type, or errors
Maps to `Ecto.Type.cast/2`
"""
@spec cast_input(t(), term) :: {:ok, term} | {:error, Keyword.t()} | :error
def cast_input({:array, _type}, term) when not is_list(term) do
{:error, message: "must be a list"}
end
def cast_input({:array, type}, term) do
term
|> Enum.with_index()
|> Enum.reverse()
|> Enum.reduce_while({:ok, []}, fn {item, index}, {:ok, casted} ->
case cast_input(type, item) do
:error ->
{:halt, {:error, index: index}}
{:error, keyword} ->
{:halt, {:error, Keyword.put(keyword, :index, index)}}
{:ok, value} ->
{:cont, {:ok, [value | casted]}}
end
end)
end
def cast_input(type, term) do
case type.cast_input(term) do
{:ok, value} ->
{:ok, value}
:error ->
{:error, "is invalid"}
{:error, other} ->
{:error, other}
end
end
@doc """
Casts a value from the data store to an instance of the type, or errors
Maps to `Ecto.Type.load/2`
"""
@spec cast_stored(t(), term) :: {:ok, term} | {:error, keyword()} | :error
def cast_stored({:array, type}, term) when is_list(term) do
term
|> Enum.with_index()
|> Enum.reverse()
|> Enum.reduce_while({:ok, []}, fn {item, index}, {:ok, casted} ->
case cast_stored(type, item) do
:error ->
{:halt, {:error, index: index}}
{:error, keyword} ->
{:halt, {:error, Keyword.put(keyword, :index, index)}}
{:ok, value} ->
{:cont, {:ok, [value | casted]}}
end
end)
end
def cast_stored(type, term) do
type.cast_stored(term)
end
@doc """
Confirms if a casted value matches the provided constraints.
"""
@spec apply_constraints(t(), term, constraints()) :: :ok | {:error, String.t()}
def apply_constraints({:array, type}, term, constraints) when is_list(constraints) do
list_constraint_errors = list_constraint_errors(term, constraints)
case list_constraint_errors do
[] ->
nil_items? = Keyword.get(constraints, :nil_items?, true)
item_constraints = constraints[:items] || []
if item_constraints != [] || !nil_items? do
errors =
term
|> Enum.with_index()
|> Enum.reduce([], fn {item, index}, errors ->
errors =
if is_nil(item) && not nil_items? do
["cannot be nil at index #{index}" | errors]
else
errors
end
case apply_constraints(type, item, item_constraints) do
:ok ->
errors
{:error, new_errors} ->
new_errors =
new_errors
|> List.wrap()
|> Enum.map(&Kernel.<>(&1, " at index #{index}"))
List.wrap(new_errors) ++ errors
end
end)
if errors == [] do
:ok
else
{:error, errors}
end
else
:ok
end
errors ->
{:error, errors}
end
end
def apply_constraints({:array, _}, _, _) do
{:error, ["must be a list"]}
end
def apply_constraints(type, term, constraints) do
type.apply_constraints(term, constraints)
end
defp list_constraint_errors(term, constraints) do
length =
if Keyword.has_key?(constraints, :max_length) || Keyword.has_key?(constraints, :min_length) do
length(term)
else
0
end
constraints
|> Enum.reduce([], fn
{:min_length, min_length}, errors ->
if length < min_length do
["must have more than #{min_length} items" | errors]
else
errors
end
{:max_length, max_length}, errors ->
if length > max_length do
["must have fewer than #{max_length} items" | errors]
else
errors
end
_, errors ->
errors
end)
end
@spec constraints(t()) :: constraints()
def constraints({:array, _type}) do
@list_constraints
end
def constraints(type) do
type.constraints()
end
@doc """
Casts a value from the Elixir type to a value that the data store can persist
Maps to `Ecto.Type.dump/2`
"""
@spec dump_to_native(t(), term) :: {:ok, term} | {:error, keyword()} | :error
def dump_to_native({:array, type}, term) do
term
|> Enum.reverse()
|> Enum.reduce_while({:ok, []}, fn item, {:ok, dumped} ->
case dump_to_native(type, item) do
:error ->
{:halt, :error}
{:ok, value} ->
{:cont, {:ok, [value | dumped]}}
end
end)
end
def dump_to_native(type, term) do
type.dump_to_native(term)
end
@doc """
Determines if two values of a given type are equal.
Maps to `Ecto.Type.equal?/3`
"""
@spec equal?(t(), term, term) :: boolean
def equal?({:array, type}, [nil | xs], [nil | ys]), do: equal?({:array, type}, xs, ys)
def equal?({:array, type}, [x | xs], [y | ys]),
do: equal?(type, x, y) && equal?({:array, type}, xs, ys)
def equal?({:array, _}, [], []), do: true
def equal?({:array, _}, _, _), do: false
def equal?(type, left, right) do
type.equal?(left, right)
end
# @callback equal?(term, term) :: boolean
defmacro __using__(_) do
quote location: :keep do
@behaviour Ash.Type
parent = __MODULE__
defmodule EctoType do
@moduledoc false
@behaviour Ecto.Type
@parent parent
@impl true
def type do
storage_type = @parent.storage_type()
if Ash.Type.ash_type?(storage_type) do
Ash.Type.storage_type(@parent.storage_type())
else
storage_type
end
end
@impl true
def cast(term) do
@parent.cast_input(term)
end
@impl true
def load(term) do
@parent.cast_stored(term)
end
@impl true
def dump(term) do
@parent.dump_to_native(term)
end
@impl true
def equal?(left, right) do
@parent.equal?(left, right)
end
@impl true
def embed_as(_), do: :self
end
@impl true
def ecto_type, do: EctoType
@impl true
def equal?(left, right), do: left == right
@impl true
def constraints, do: []
@impl true
def apply_constraints(_, _), do: :ok
defoverridable equal?: 2, constraints: 0, apply_constraints: 2
end
end
defp ash_type_module?(module) do
Ash.implements_behaviour?(module, __MODULE__)
end
end