2019-10-03 16:08:36 +13:00
|
|
|
defmodule Ash do
|
2019-12-05 20:18:13 +13:00
|
|
|
@moduledoc """
|
2019-12-06 07:45:02 +13:00
|
|
|
The primary interface for interrogating apis and resources.
|
2019-12-05 20:18:13 +13:00
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
These are tools for interrogating resources to derive behavior based on their
|
|
|
|
configuration. This is how all of the behavior of Ash is ultimately configured.
|
2019-12-05 20:18:13 +13:00
|
|
|
"""
|
2020-06-02 17:47:25 +12:00
|
|
|
alias Ash.Resource.Actions.{Create, Destroy, Read, Update}
|
|
|
|
alias Ash.Resource.Relationships.{BelongsTo, HasMany, HasOne, ManyToMany}
|
2020-01-14 07:16:24 +13:00
|
|
|
|
2019-10-07 09:36:06 +13:00
|
|
|
@type record :: struct
|
2020-05-26 16:45:10 +12:00
|
|
|
@type relationship_cardinality :: :many | :one
|
2019-10-07 09:36:06 +13:00
|
|
|
@type cardinality_one_relationship() :: HasOne.t() | BelongsTo.t()
|
|
|
|
@type cardinality_many_relationship() :: HasMany.t() | ManyToMany.t()
|
|
|
|
@type relationship :: cardinality_one_relationship() | cardinality_many_relationship()
|
|
|
|
@type resource :: module
|
2019-12-05 20:18:13 +13:00
|
|
|
@type data_layer :: module
|
2020-05-14 03:54:44 +12:00
|
|
|
@type data_layer_query :: struct
|
2019-12-03 05:25:00 +13:00
|
|
|
@type api :: module
|
2019-10-07 09:36:06 +13:00
|
|
|
@type error :: struct
|
2019-11-29 19:54:11 +13:00
|
|
|
@type filter :: map()
|
2019-12-23 17:28:40 +13:00
|
|
|
@type params :: Keyword.t()
|
2019-11-30 05:36:01 +13:00
|
|
|
@type sort :: Keyword.t()
|
2019-11-28 10:36:25 +13:00
|
|
|
@type side_loads :: Keyword.t()
|
2020-06-02 15:23:33 +12:00
|
|
|
@type attribute :: Ash.Resource.Attributes.Attribute.t()
|
2019-12-05 20:18:13 +13:00
|
|
|
@type action :: Create.t() | Read.t() | Update.t() | Destroy.t()
|
2020-05-14 03:54:44 +12:00
|
|
|
@type query :: Ash.Query.t()
|
2020-05-21 10:59:58 +12:00
|
|
|
@type actor :: Ash.record()
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A short description of the resource, to be included in autogenerated documentation"
|
|
|
|
@spec describe(resource()) :: String.t()
|
2020-05-02 21:01:57 +12:00
|
|
|
def describe(resource) do
|
|
|
|
resource.describe()
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of authorizers to be used when accessing the resource"
|
|
|
|
@spec authorizers(resource()) :: [module]
|
2020-05-21 10:59:58 +12:00
|
|
|
def authorizers(resource) do
|
|
|
|
resource.authorizers()
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of resource modules for a given API"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec resources(api) :: list(resource())
|
2019-12-02 10:58:29 +13:00
|
|
|
def resources(api) do
|
|
|
|
api.resources()
|
2019-10-03 20:18:07 +13:00
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of field names corresponding to the primary key of a resource"
|
2020-06-02 15:23:33 +12:00
|
|
|
@spec primary_key(resource()) :: list(atom)
|
2019-11-03 09:36:46 +13:00
|
|
|
def primary_key(resource) do
|
|
|
|
resource.primary_key()
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "Gets a relationship by name from the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec relationship(resource(), atom() | String.t()) :: relationship() | nil
|
2019-11-30 05:36:01 +13:00
|
|
|
def relationship(resource, relationship_name) when is_bitstring(relationship_name) do
|
|
|
|
Enum.find(resource.relationships(), &(to_string(&1.name) == relationship_name))
|
|
|
|
end
|
|
|
|
|
2019-10-31 04:10:01 +13:00
|
|
|
def relationship(resource, relationship_name) do
|
2019-11-28 10:36:25 +13:00
|
|
|
Enum.find(resource.relationships(), &(&1.name == relationship_name))
|
2019-10-31 04:10:01 +13:00
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of relationships on the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec relationships(resource()) :: list(relationship())
|
2019-10-04 15:33:55 +13:00
|
|
|
def relationships(resource) do
|
|
|
|
resource.relationships()
|
|
|
|
end
|
|
|
|
|
2020-06-05 14:43:51 +12:00
|
|
|
@spec resource_module?(module) :: boolean
|
|
|
|
def resource_module?(module) do
|
|
|
|
:attributes
|
|
|
|
|> module.module_info()
|
|
|
|
|> Keyword.get(:behaviour, [])
|
|
|
|
|> Enum.any?(&(&1 == Ash.Resource))
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc false
|
2020-05-21 10:59:58 +12:00
|
|
|
def primary_action!(resource, type) do
|
|
|
|
case primary_action(resource, type) do
|
|
|
|
nil -> raise "Required primary #{type} action for #{inspect(resource)}"
|
|
|
|
action -> action
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "Returns the primary action of a given type for a resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec primary_action(resource(), atom()) :: action() | nil
|
2019-11-25 13:01:21 +13:00
|
|
|
def primary_action(resource, type) do
|
|
|
|
resource
|
|
|
|
|> actions()
|
2019-11-28 10:36:25 +13:00
|
|
|
|> Enum.filter(&(&1.type == type))
|
|
|
|
|> case do
|
|
|
|
[action] -> action
|
|
|
|
actions -> Enum.find(actions, & &1.primary?)
|
|
|
|
end
|
2019-11-25 13:01:21 +13:00
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "Returns the action with the matching name and type on the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec action(resource(), atom(), atom()) :: action() | nil
|
2019-11-28 10:36:25 +13:00
|
|
|
def action(resource, name, type) do
|
|
|
|
Enum.find(resource.actions(), &(&1.name == name && &1.type == type))
|
2019-10-31 04:10:01 +13:00
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of all actions on the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec actions(resource()) :: list(action())
|
2019-10-03 20:18:07 +13:00
|
|
|
def actions(resource) do
|
|
|
|
resource.actions()
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "Get an attribute name from the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec attribute(resource(), String.t() | atom) :: attribute() | nil
|
2019-11-30 05:36:01 +13:00
|
|
|
def attribute(resource, name) when is_bitstring(name) do
|
|
|
|
Enum.find(resource.attributes, &(to_string(&1.name) == name))
|
|
|
|
end
|
|
|
|
|
2019-11-28 10:36:25 +13:00
|
|
|
def attribute(resource, name) do
|
|
|
|
Enum.find(resource.attributes, &(&1.name == name))
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "A list of all attributes on the resource"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec attributes(resource()) :: list(attribute())
|
2019-10-03 20:18:07 +13:00
|
|
|
def attributes(resource) do
|
|
|
|
resource.attributes()
|
|
|
|
end
|
|
|
|
|
2020-06-04 18:16:41 +12:00
|
|
|
@doc "The data layer of the resource, or nil if it does not have one"
|
2019-12-05 20:18:13 +13:00
|
|
|
@spec data_layer(resource()) :: data_layer()
|
2019-10-07 09:36:06 +13:00
|
|
|
def data_layer(resource) do
|
|
|
|
resource.data_layer()
|
|
|
|
end
|
2020-06-04 18:16:41 +12:00
|
|
|
|
|
|
|
@doc false
|
|
|
|
@spec data_layer_can?(resource(), Ash.DataLayer.feature()) :: boolean
|
|
|
|
def data_layer_can?(resource, feature) do
|
|
|
|
data_layer = data_layer(resource)
|
|
|
|
|
|
|
|
data_layer && Ash.DataLayer.can?(feature, resource)
|
|
|
|
end
|
|
|
|
|
|
|
|
@doc false
|
|
|
|
@spec data_layer_filters(resource) :: map
|
|
|
|
def data_layer_filters(resource) do
|
|
|
|
Ash.DataLayer.custom_filters(resource)
|
|
|
|
end
|
2019-10-03 16:08:36 +13:00
|
|
|
end
|