Packages

ash

3.24.5
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
ash lib ash type struct.ex
Raw

lib/ash/type/struct.ex

# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Type.Struct do
@constraints [
instance_of: [
type: :atom,
doc: "The module the struct should be an instance of"
],
preserve_nil_values?: [
type: :boolean,
default: false,
doc: """
If set to true, nil values will be preserved both when storing and in the casted struct.
Otherwise, keys whose values are `nil` will be omitted.
preserved_nil_values? is false by default
"""
],
fields: [
type: :keyword_list,
keys: [
*: [
type: :keyword_list,
keys: [
type: [
type: Ash.OptionsHelpers.ash_type(),
required: true
],
allow_nil?: [
type: :boolean,
default: true
],
description: [
type: :string
],
constraints: [
type: :keyword_list,
default: []
]
]
]
],
doc: """
The types of the fields in the struct, and their constraints.
For example:
fields: [
amount: [
type: :integer,
description: "The amount of the transaction",
constraints: [
max: 10
]
],
currency: [
type: :string,
allow_nil?: false,
description: "The currency code of the transaction",
constraints: [
max_length: 3
]
]
]
allow_nil? is true by default
"""
]
]
@moduledoc """
Represents a struct.
Use the `instance_of` constraint to specify that it must be an instance of a specific struct.
This cannot be loaded from a database unless the `instance_of` constraint is provided.
If not, it can only be used to cast input, i.e for arguments.
## Alternative: Ash.TypedStruct
For simpler use cases where you want to define a struct with typed fields inline,
consider using `Ash.TypedStruct`. It provides a DSL for defining structs with:
- Field type specifications and constraints
- Default values
- Required fields (via `allow_nil?: false`)
- Automatic `new/1` and `new!/1` functions
Example:
defmodule MyStruct do
use Ash.TypedStruct
typed_struct do
field :name, :string, allow_nil?: false
field :age, :integer, constraints: [min: 0]
field :email, :string, default: nil
end
end
`Ash.TypedStruct` automatically creates an `Ash.Type.Struct` with the appropriate
constraints under the hood.
## Constraints
#{Spark.Options.docs(@constraints)}
"""
use Ash.Type
import Ash.Gettext
@impl true
def constraints, do: @constraints
@impl true
def storage_type(_), do: :map
@impl true
def init(constraints) do
if is_list(constraints[:fields]) do
constraints[:fields]
|> List.wrap()
|> Enum.reduce_while({:ok, []}, fn {name, config}, {:ok, fields} ->
type = Ash.Type.get_type(config[:type])
constraints = config[:constraints] || []
if Keyword.get(config, :init?, true) do
case Ash.Type.init(type, constraints) do
{:ok, constraints} ->
{:cont,
{:ok,
[{name, Keyword.merge(config, constraints: constraints, type: type)} | fields]}}
{:error, error} ->
{:halt, {:error, error}}
end
else
{:cont, {:ok, [{name, config} | fields]}}
end
end)
|> case do
{:ok, fields} ->
{:ok, Keyword.put(constraints, :fields, Enum.reverse(fields))}
{:error, error} ->
{:error, error}
end
else
if is_nil(constraints[:fields]) do
{:ok, constraints}
else
{:error, "fields must be a list, got `#{constraints[:fields]}`"}
end
end
end
defp fields(constraints) do
case Keyword.fetch(constraints, :fields) do
{:ok, fields} ->
fields
:error ->
instance_of = constraints[:instance_of]
if instance_of && Ash.Resource.Info.resource?(instance_of) do
instance_of
|> Ash.Resource.Info.attributes()
|> Map.new(
&{&1.name, [type: &1.type, constraints: &1.constraints, allow_nil?: &1.allow_nil?]}
)
end
end
end
@impl true
def matches_type?(v, constraints) do
if instance_of = constraints[:instance_of] do
is_struct(v, instance_of)
else
is_struct(v)
end
end
@impl true
def cast_input("", _), do: {:ok, nil}
def cast_input(nil, _), do: {:ok, nil}
def cast_input(value, constraints) when is_binary(value) do
case Ash.Helpers.json_module().decode(value) do
{:ok, value} ->
cast_input(value, constraints)
_ ->
:error
end
end
def cast_input(value, _) when is_map(value), do: {:ok, value}
def cast_input(_, _), do: :error
@impl true
def cast_stored(nil, _), do: {:ok, nil}
def cast_stored(value, constraints) when is_map(value) do
if fields = fields(constraints) do
if constraints[:instance_of] do
nil_values = constraints[:preserve_nil_values?]
Enum.reduce_while(fields, {:ok, struct(constraints[:instance_of], [])}, fn {key, config},
{:ok, acc} ->
case fetch_field(value, key) do
{:ok, value} ->
case Ash.Type.cast_stored(config[:type], value, config[:constraints] || []) do
{:ok, value} ->
if is_nil(value) && !nil_values do
{:cont, {:ok, acc}}
else
{:cont, {:ok, Map.put(acc, key, value)}}
end
other ->
{:halt, other}
end
:error ->
{:cont, {:ok, acc}}
end
end)
else
:error
end
else
:error
end
end
def cast_stored(_, _), do: :error
@impl true
def dump_to_native(nil, _), do: {:ok, nil}
def dump_to_native(value, constraints) when is_map(value) do
if fields = fields(constraints) do
if constraints[:instance_of] do
Enum.reduce_while(fields, {:ok, %{}}, fn {key, config}, {:ok, acc} ->
case Map.fetch(value, key) do
{:ok, value} ->
case Ash.Type.dump_to_native(config[:type], value, config[:constraints] || []) do
{:ok, value} ->
{:cont, {:ok, Map.put(acc, key, value)}}
other ->
{:halt, other}
end
:error ->
{:cont, {:ok, acc}}
end
end)
else
:error
end
else
:error
end
end
def dump_to_native(_, _), do: :error
@impl true
def generator(constraints) do
if !constraints[:instance_of] do
raise ArgumentError,
"Cannot generate instances of the `:struct` type without an `:instance_of` constraint"
end
Ash.Type.Map.generator(constraints)
|> StreamData.map(fn value ->
struct(constraints[:instance_of], value)
end)
end
@impl true
def apply_constraints(nil, _constraints), do: {:ok, nil}
def apply_constraints(value, constraints) do
instance_of = constraints[:instance_of]
if instance_of && !constraints[:fields] && is_struct(value, instance_of) do
{:ok, value}
else
with {:ok, value} <- handle_fields(value, constraints) do
handle_instance_of(value, constraints)
end
end
end
@impl Ash.Type
def load(record, load, _constraints, %{domain: domain} = context) do
opts = Ash.Context.to_opts(context, domain: domain)
Ash.load(record, load, opts)
end
@impl Ash.Type
def merge_load(left, right, constraints, context) do
instance_of = constraints[:instance_of]
if instance_of do
# instance_of_query = Ash.Query.new(instance_of)
left = Ash.Query.load(instance_of, left)
right = Ash.Query.load(instance_of, right)
if left.valid? do
{:ok, Ash.Query.merge_query_load(left, right, context)}
else
{:error, Ash.Error.to_ash_error(left.errors)}
end
else
{:error, "Structs must have an `instance_of` constraint to be loaded through"}
end
end
@impl Ash.Type
def get_rewrites(merged_load, calculation, path, constraints) do
instance_of = constraints[:instance_of]
if instance_of && Ash.Resource.Info.resource?(instance_of) do
merged_load = Ash.Query.load(instance_of, merged_load)
Ash.Actions.Read.Calculations.get_all_rewrites(merged_load, calculation, path)
else
[]
end
end
@impl Ash.Type
def rewrite(value, rewrites, _constraints) do
Ash.Actions.Read.Calculations.rewrite(rewrites, value)
end
@impl Ash.Type
def can_load?(constraints) do
instance_of = constraints[:instance_of]
instance_of && Ash.Resource.Info.resource?(instance_of)
end
defp handle_fields(value, constraints) do
if fields = fields(constraints) do
check_fields(value, fields)
else
{:ok, value}
end
end
defp handle_instance_of(nil, _), do: {:ok, nil}
defp handle_instance_of(value, constraints) do
case Keyword.fetch(constraints, :instance_of) do
{:ok, struct} ->
cond do
is_struct(value, struct) ->
{:ok, value}
is_struct(value) ->
{:error, "is invalid"}
fields(constraints) ->
{:ok, struct(struct, value)}
Ash.Resource.Info.resource?(struct) ->
struct
|> Ash.Resource.Info.public_attributes()
|> Enum.reduce_while({:ok, struct.__struct__()}, fn attribute, {:ok, record} ->
case fetch_field(value, attribute.name) do
{:ok, value} ->
with {:ok, casted} <-
Ash.Type.cast_input(
attribute.type,
value,
attribute.constraints
),
{:ok, casted} <-
Ash.Type.apply_constraints(attribute.type, casted, attribute.constraints) do
if is_nil(casted) and attribute.allow_nil? == false do
{:halt,
{:error, field: attribute.name, message: error_message("is required")}}
else
{:cont, {:ok, Map.put(record, attribute.name, casted)}}
end
else
:error ->
{:halt, {:error, "is invalid"}}
{:error, error} ->
{:halt, {:error, error}}
end
:error ->
if attribute.allow_nil? == false do
{:halt,
{:error, field: attribute.name, message: error_message("is required")}}
else
{:cont, {:ok, record}}
end
end
end)
true ->
{:error, "is invalid"}
end
:error ->
if is_struct(value) do
{:ok, value}
else
{:error, "is invalid"}
end
end
end
defp check_fields(value, fields) do
{errors, result} =
Enum.reduce(fields, {[], %{}}, fn {field, field_constraints}, {errors_acc, result_acc} ->
case fetch_field(value, field) do
{:ok, field_value} ->
case check_field(result_acc, field, field_value, field_constraints) do
{:ok, updated_result} ->
{errors_acc, updated_result}
{:error, field_errors} ->
{errors_acc ++ field_errors, result_acc}
end
:error ->
if field_constraints[:allow_nil?] == false do
field_error = [message: error_message("field must be present"), field: field]
{errors_acc ++ [field_error], result_acc}
else
{errors_acc, result_acc}
end
end
end)
case errors do
[] -> {:ok, result}
_ -> {:error, errors}
end
end
defp check_field(result, field, field_value, field_constraints) do
case Ash.Type.cast_input(
field_constraints[:type],
field_value,
field_constraints[:constraints] || []
) do
{:ok, field_value} ->
case Ash.Type.apply_constraints(
field_constraints[:type],
field_value,
field_constraints[:constraints] || []
) do
{:ok, nil} ->
if field_constraints[:allow_nil?] == false do
{:error,
[[message: error_message("value must not be nil"), field: field, value: nil]]}
else
{:ok, Map.put(result, field, nil)}
end
{:ok, field_value} ->
{:ok, Map.put(result, field, field_value)}
{:error, errors} ->
formatted_errors =
errors
|> List.wrap()
|> Enum.map(
&Ash.Type.CompositeTypeHelpers.format_comprehensive_error_as_keyword(&1, field)
)
|> Ash.Type.CompositeTypeHelpers.filter_non_informative_errors()
{:error, formatted_errors}
end
{:error, error} when is_binary(error) ->
{:error, [[message: error, field: field, value: field_value]]}
{:error, error} when is_list(error) ->
if Keyword.keyword?(error) do
{:error, [Keyword.update(error, :path, [field], &[field | &1])]}
else
{:error,
Enum.map(error, fn error ->
if Keyword.keyword?(error) do
Keyword.update(error, :path, [field], &[field | &1])
else
error
end
end)}
end
{:error, error} ->
{:error, error}
:error ->
{:error, [[message: error_message("invalid value"), field: field, value: field_value]]}
end
end
defp fetch_field(map, atom) when is_atom(atom) do
case Map.fetch(map, atom) do
{:ok, value} -> {:ok, value}
:error -> fetch_field(map, to_string(atom))
end
end
defp fetch_field(map, key), do: Map.fetch(map, key)
end