Packages
ash
1.15.0
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/api/api.ex
defmodule Ash.Api do
@moduledoc """
An Api allows you to interact with your resources, and holds non-resource-specific configuration.
Your Api can also house config that is not resource specific. For example, the json api extension
adds an api extension that lets you toggle authorization on/off for all resources in that Api.
You include them in an Api like so:
```elixir
defmodule MyApp.Api do
use Ash.Api
resources do
resource OneResource
resource SecondResource
end
end
```
Then you can interact through that Api with the actions that those resources expose.
For example: `MyApp.Api.create(changeset)`, or `MyApp.Api.read(query)`. Corresponding
actions must be defined in your resources in order to call them through the Api.
"""
import Ash.OptionsHelpers, only: [merge_schemas: 3]
alias Ash.Actions.{Create, Destroy, Read, Update}
alias Ash.Error.Invalid.{InvalidPrimaryKey, NoPrimaryAction, NoSuchAction, NoSuchResource}
@global_opts [
verbose?: [
type: :boolean,
default: false,
doc: "Log engine operations (very verbose?)"
],
action: [
type: :any,
doc: "The action to use, either an Action struct or the name of the action"
],
authorize?: [
type: :boolean,
default: false,
doc:
"If an actor is provided, authorization happens automatically. If not, this flag can be used to authorize with no user."
],
actor: [
type: :any,
doc:
"If an actor is provided, it will be used in conjunction with the authorizers of a resource to authorize access"
]
]
@read_opts_schema merge_schemas([], @global_opts, "Global Options")
@doc false
def read_opts_schema, do: @read_opts_schema
@load_opts_schema merge_schemas([], @global_opts, "Global Options")
@get_opts_schema [
load: [
type: :any,
doc: "Fields or relationships to load in the query. See `Ash.Query.load/2`"
]
]
|> merge_schemas(@global_opts, "Global Options")
@shared_create_and_update_opts_schema []
|> merge_schemas(@global_opts, "Global Options")
@create_opts_schema [
upsert?: [
type: :boolean,
default: false,
doc:
"If a conflict is found based on the primary key, the record is updated in the database (requires upsert support)"
]
]
|> merge_schemas(@global_opts, "Global Options")
|> merge_schemas(
@shared_create_and_update_opts_schema,
"Shared Create/Edit Options"
)
@update_opts_schema []
|> merge_schemas(@global_opts, "Global Options")
|> merge_schemas(
@shared_create_and_update_opts_schema,
"Shared Create/Edit Options"
)
@destroy_opts_schema merge_schemas([], @global_opts, "Global Opts")
@doc """
Get a record by a primary key. See `c:get/3` for more.
#{NimbleOptions.docs(@get_opts_schema)}
"""
@callback get!(resource :: Ash.resource(), id_or_filter :: term(), params :: Keyword.t()) ::
Ash.record() | no_return
@doc """
Get a record by a primary key.
For a resource with a composite primary key, pass a keyword list, e.g
`MyApi.get(MyResource, first_key: 1, second_key: 2)`
#{NimbleOptions.docs(@get_opts_schema)}
"""
@callback get(resource :: Ash.resource(), id_or_filter :: term(), params :: Keyword.t()) ::
{:ok, Ash.record()} | {:error, Ash.error()}
@doc """
Run an ash query. See `c:read/2` for more.
#{NimbleOptions.docs(@read_opts_schema)}
"""
@callback read!(Ash.query(), params :: Keyword.t()) ::
list(Ash.resource()) | no_return
@doc """
Run a query on a resource.
For more information, on building a query, see `Ash.Query`.
#{NimbleOptions.docs(@read_opts_schema)}
"""
@callback read(Ash.query(), params :: Keyword.t()) ::
{:ok, list(Ash.resource())} | {:error, Ash.error()}
@doc """
Load fields or relationships on already fetched records. See `c:load/2` for more information.
#{NimbleOptions.docs(@load_opts_schema)}
"""
@callback load!(
record_or_records :: Ash.record() | [Ash.record()],
query :: Ash.query(),
opts :: Keyword.t()
) ::
Ash.record() | [Ash.record()] | no_return
@doc """
Load fields or relationships on already fetched records.
Accepts a list of non-loaded fields and loads them on the provided records or a query, in
which case the loaded fields of the query are used. Relationship loads can be nested, for
example: `MyApi.load(record, [posts: [:comments]])`.
#{NimbleOptions.docs(@load_opts_schema)}
"""
@callback load(
record_or_records :: Ash.record() | [Ash.record()],
query :: Ash.query(),
opts :: Keyword.t()
) ::
{:ok, Ash.record() | [Ash.record()]} | {:error, Ash.error()}
@doc """
Create a record. See `c:create/2` for more information.
#{NimbleOptions.docs(@create_opts_schema)}
"""
@callback create!(Ash.changeset(), params :: Keyword.t()) ::
Ash.record() | no_return
@doc """
Create a record.
#{NimbleOptions.docs(@create_opts_schema)}
"""
@callback create(Ash.changeset(), params :: Keyword.t()) ::
{:ok, Ash.record()} | {:error, Ash.error()}
@doc """
Update a record. See `c:update/2` for more information.
#{NimbleOptions.docs(@update_opts_schema)}
"""
@callback update!(Ash.changeset(), params :: Keyword.t()) ::
Ash.record() | no_return
@doc """
Update a record.
#{NimbleOptions.docs(@update_opts_schema)}
"""
@callback update(Ash.changeset(), params :: Keyword.t()) ::
{:ok, Ash.record()} | {:error, Ash.error()}
@doc """
Destroy a record. See `c:destroy/2` for more information.
#{NimbleOptions.docs(@destroy_opts_schema)}
"""
@callback destroy!(Ash.changeset() | Ash.record(), params :: Keyword.t()) :: :ok | no_return
@doc """
Destroy a record.
#{NimbleOptions.docs(@destroy_opts_schema)}
"""
@callback destroy(Ash.changeset() | Ash.record(), params :: Keyword.t()) ::
:ok | {:error, Ash.error()}
@doc """
Refetches a record by primary key. See `c:reload/1` for more.
"""
@callback reload!(record :: Ash.record(), params :: Keyword.t()) :: Ash.record() | no_return
@doc """
Refetches a record by primary key.
"""
@callback reload(record :: Ash.record()) :: {:ok, Ash.record()} | {:error, Ash.error()}
alias Ash.Dsl.Extension
defmacro __using__(opts) do
extensions = [Ash.Api.Dsl | opts[:extensions] || []]
body =
quote do
@before_compile Ash.Api
@behaviour Ash.Api
end
preparations = Extension.prepare(extensions)
[body | preparations]
end
defmacro __before_compile__(_env) do
quote generated: true, unquote: false do
alias Ash.Dsl.Extension
@on_load :on_load
ash_dsl_config = Macro.escape(Extension.set_state())
@doc false
def ash_dsl_config do
unquote(ash_dsl_config)
end
def on_load do
Extension.load()
end
use Ash.Api.Interface
end
end
alias Ash.Dsl.Extension
def resource(api, resource) do
api
|> resources()
|> Enum.find(&(&1 == resource))
|> case do
nil -> {:error, NoSuchResource.exception(resource: resource)}
resource -> {:ok, resource}
end
end
@spec resources(Ash.api()) :: [Ash.resource()]
def resources(api) do
api
|> Extension.get_entities([:resources])
|> Enum.map(& &1.resource)
end
@doc false
@spec get!(Ash.api(), Ash.resource(), term(), Keyword.t()) :: Ash.record() | no_return
def get!(api, resource, id, opts \\ []) do
opts = NimbleOptions.validate!(opts, @get_opts_schema)
api
|> get(resource, id, opts)
|> unwrap_or_raise!()
end
@doc false
@spec get(Ash.api(), Ash.resource(), term(), Keyword.t()) ::
{:ok, Ash.record() | nil} | {:error, Ash.error()}
def get(api, resource, id, opts) do
with {:ok, opts} <- NimbleOptions.validate(opts, @get_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, resource),
{:ok, filter} <- get_filter(resource, id) do
query =
resource
|> Ash.Query.new(api)
|> Ash.Query.filter(filter)
|> Ash.Query.load(opts[:load] || [])
query
|> api.read(Keyword.delete(opts, :load))
|> case do
{:ok, [single_result]} ->
{:ok, single_result}
{:ok, []} ->
{:ok, nil}
{:error, error} ->
{:error, error}
{:ok, results} when is_list(results) ->
{:error,
Ash.Error.Invalid.MultipleResults.exception(count: Enum.count(results), query: query)}
end
else
{:error, error} ->
{:error, error}
end
end
defp get_filter(resource, id) do
primary_key = Ash.Resource.primary_key(resource)
keyword? = Keyword.keyword?(id)
case {primary_key, id} do
{[field], [{field, value}]} ->
{:ok, [{field, value}]}
{[field], value} when not keyword? ->
{:ok, [{field, value}]}
{fields, value} ->
cond do
not keyword? ->
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)}
Enum.sort(Keyword.keys(value)) == Enum.sort(fields) ->
{:ok, value}
true ->
get_identity_filter(resource, id)
end
end
end
defp get_identity_filter(resource, id) do
sorted_keys = Enum.sort(Keyword.keys(id))
resource
|> Ash.Resource.identities()
|> Enum.find_value(
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)},
fn identity ->
if sorted_keys == Enum.sort(identity.keys) do
{:ok, id}
else
false
end
end
)
end
@doc false
@spec load!(
Ash.api(),
Ash.record() | list(Ash.record()),
Ash.query() | list(atom | {atom, list()}),
Keyword.t()
) ::
list(Ash.record()) | Ash.record() | no_return
def load!(api, data, query, opts \\ []) do
opts = NimbleOptions.validate!(opts, @load_opts_schema)
api
|> load(data, query, opts)
|> unwrap_or_raise!()
end
@doc false
@spec load(
Ash.api(),
Ash.record() | list(Ash.record()),
Ash.query() | list(atom | {atom, list()}),
Keyword.t()
) ::
{:ok, list(Ash.record()) | Ash.record()} | {:error, Ash.error()}
def load(api, data, query, opts \\ [])
def load(_, [], _, _), do: {:ok, []}
def load(_, nil, _, _), do: {:ok, nil}
def load(_, {:error, error}, _, _), do: {:error, error}
def load(api, {:ok, values}, query, opts) do
load(api, values, query, opts)
end
def load(api, data, query, opts) when not is_list(data) do
api
|> load(List.wrap(data), query, opts)
|> case do
{:ok, [data]} -> {:ok, data}
{:error, error} -> {:error, error}
end
end
def load(api, [%resource{} | _] = data, query, opts) do
query =
case query do
%Ash.Query{} = query ->
query
keyword ->
resource
|> Ash.Query.new(api)
|> Ash.Query.load(keyword)
end
with %{valid?: true} <- query,
{:ok, action} <- get_action(query.resource, opts, :read),
{:ok, opts} <- NimbleOptions.validate(opts, @load_opts_schema) do
Read.run(query, action, Keyword.put(opts, :initial_data, data))
else
{:error, error} ->
{:error, error}
%{errors: errors} ->
{:error, errors}
end
end
@doc false
@spec read!(Ash.api(), Ash.query(), Keyword.t()) ::
list(Ash.record()) | no_return
def read!(api, query, opts \\ []) do
opts = NimbleOptions.validate!(opts, @read_opts_schema)
api
|> read(query, opts)
|> unwrap_or_raise!()
end
@doc false
@spec read(Ash.api(), Ash.query(), Keyword.t()) ::
{:ok, list(Ash.resource())} | {:error, Ash.error()}
def read(api, query, opts \\ []) do
query = Ash.Query.set_api(query, api)
with {:ok, opts} <- NimbleOptions.validate(opts, @read_opts_schema),
{:ok, action} <- get_action(query.resource, opts, :read) do
Read.run(query, action, opts)
else
{:error, error} ->
{:error, error}
end
end
@doc false
@spec create!(Ash.api(), Ash.changeset(), Keyword.t()) ::
Ash.record() | no_return
def create!(api, changeset, opts) do
opts = NimbleOptions.validate!(opts, @create_opts_schema)
api
|> create(changeset, opts)
|> unwrap_or_raise!()
end
@doc false
@spec create(Ash.api(), Ash.changeset(), Keyword.t()) ::
{:ok, Ash.record()} | {:error, Ash.error()}
def create(api, changeset, opts) do
with {:ok, opts} <- NimbleOptions.validate(opts, @create_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, changeset.resource),
{:ok, action} <- get_action(resource, opts, :create) do
Create.run(api, changeset, action, opts)
end
end
@doc false
def update!(api, changeset, opts) do
opts = NimbleOptions.validate!(opts, @update_opts_schema)
api
|> update(changeset, opts)
|> unwrap_or_raise!()
end
@doc false
@spec update(Ash.api(), Ash.record(), Keyword.t()) ::
{:ok, Ash.record()} | {:error, Ash.error()}
def update(api, changeset, opts) do
with {:ok, opts} <- NimbleOptions.validate(opts, @update_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, changeset.resource),
{:ok, action} <- get_action(resource, opts, :update) do
Update.run(api, changeset, action, opts)
end
end
@doc false
@spec destroy!(Ash.api(), Ash.changeset() | Ash.record(), Keyword.t()) :: :ok | no_return
def destroy!(api, changeset, opts) do
opts = NimbleOptions.validate!(opts, @destroy_opts_schema)
api
|> destroy(changeset, opts)
|> unwrap_or_raise!()
end
@doc false
@spec destroy(Ash.api(), Ash.changeset() | Ash.record(), Keyword.t()) ::
:ok | {:error, Ash.error()}
def destroy(api, %Ash.Changeset{resource: resource} = changeset, opts) do
with {:ok, opts} <- NimbleOptions.validate(opts, @destroy_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, resource),
{:ok, action} <- get_action(resource, opts, :destroy) do
Destroy.run(api, changeset, action, opts)
end
end
def destroy(api, record, opts) do
destroy(api, Ash.Changeset.new(record), opts)
end
defp get_action(resource, params, type) do
case Keyword.fetch(params, :action) do
{:ok, %_{} = action} ->
{:ok, action}
{:ok, action} ->
case Ash.Resource.action(resource, action, type) do
nil ->
{:error, NoSuchAction.exception(resource: resource, action: action, type: type)}
action ->
{:ok, action}
end
:error ->
case Ash.Resource.primary_action(resource, type) do
nil ->
{:error, NoPrimaryAction.exception(resource: resource, type: type)}
action ->
{:ok, action}
end
end
end
defp unwrap_or_raise!(:ok), do: :ok
defp unwrap_or_raise!({:ok, result}), do: result
defp unwrap_or_raise!({:error, error}) do
exception = Ash.Error.to_ash_error(error)
case exception do
%{stacktraces?: _} ->
raise %{exception | stacktraces?: true}
_ ->
raise exception
end
end
end