Packages
ash
1.6.3
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.
Defining a resource won't do much for you. Once you have some resources defined,
you include them in an Api like so:
```elixir
defmodule MyApp.Api do
use Ash.Api
resources do
resource OneResource
resource SecondResource
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, 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()],
params :: Keyword.t() | Ash.query()
) ::
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]])`. See `Ash.Query.side_load/2` for more
information on specifically loading relationships.
#{NimbleOptions.docs(@load_opts_schema)}
"""
@callback load(
record_or_records :: Ash.record() | [Ash.record()],
params :: Keyword.t() | Ash.query()
) ::
{: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!(resource :: Ash.resource(), params :: Keyword.t()) ::
Ash.record() | no_return
@doc """
Create a record.
#{NimbleOptions.docs(@create_opts_schema)}
"""
@callback create(resource :: Ash.resource(), 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!(record :: Ash.record(), params :: Keyword.t()) ::
Ash.record() | no_return
@doc """
Update a record.
#{NimbleOptions.docs(@update_opts_schema)}
"""
@callback update(record :: Ash.record(), 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!(record :: Ash.record(), params :: Keyword.t()) :: :ok | no_return
@doc """
Destroy a record.
#{NimbleOptions.docs(@destroy_opts_schema)}
"""
@callback destroy(record :: 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 do
alias Ash.Dsl.Extension
@on_load :on_load
@ash_dsl_config Extension.set_state()
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),
primary_key <- Ash.Resource.primary_key(resource),
{:ok, filter} <- get_filter(resource, primary_key, id) do
resource
|> Ash.Query.new(api)
|> Ash.Query.filter(filter)
|> Ash.Query.load(opts[:load] || [])
|> 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, :too_many_results}
end
else
{:error, error} ->
{:error, error}
end
end
defp get_filter(resource, primary_key, id) do
case {primary_key, id} do
{[field], [{field, value}]} ->
{:ok, [{field, value}]}
{[field], value} ->
{:ok, [{field, value}]}
{fields, value} ->
if Keyword.keyword?(value) and Enum.sort(Keyword.keys(value)) == Enum.sort(fields) do
{:ok, value}
else
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)}
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(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
@spec update!(Ash.api(), Ash.changeset(), Keyword.t()) :: Ash.resource() | no_return()
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.record(), Keyword.t()) :: :ok | no_return
def destroy!(api, record, opts) do
opts = NimbleOptions.validate!(opts, @destroy_opts_schema)
api
|> destroy(record, opts)
|> unwrap_or_raise!()
end
@doc false
@spec destroy(Ash.api(), Ash.record(), Keyword.t()) :: :ok | {:error, Ash.error()}
def destroy(api, %resource{} = record, 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, record, action, opts)
end
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,
"no action provided, and no primary #{to_string(type)} action found for resource #{
inspect(resource)
}"}
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)
raise exception
end
end