Packages
ash
1.10.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/dsl/extension.ex
defmodule Ash.Dsl.Extension do
@moduledoc """
An extension to the Ash DSL.
This allows configuring custom DSL components, whos configurations
can then be read back.
The example at the bottom shows how you might build a (not very contextually
relevant) DSL extension that would be used like so:
defmodule MyApp.MyResource do
use Ash.Resource,
extensions: [MyApp.CarExtension]
cars do
car :mazda, "6", trim: :touring
car :toyota, "corolla"
end
end
For (a not very contextually relevant) example:
defmodule MyApp.CarExtension do
@car_schema [
make: [
type: :atom,
required: true,
doc: "The make of the car"
],
model: [
type: :atom,
required: true,
doc: "The model of the car"
],
type: [
type: :atom,
required: true,
doc: "The type of the car",
default: :sedan
]
]
@car %Ash.Dsl.Entity{
name: :car,
describe: "Adds a car",
examples: [
"car :mazda, \"6\""
],
target: MyApp.Car,
args: [:make, :model],
schema: @car_schema
}
@cars %Ash.Dsl.Section{
name: :cars, # The DSL constructor will be `cars`
describe: \"\"\"
Configure what cars are available.
More, deeper explanation. Always have a short one liner explanation,
an empty line, and then a longer explanation.
\"\"\",
entities: [
@car # See `Ash.Dsl.Entity` docs
],
schema: [
default_manufacturer: [
type: :atom,
doc: "The default manufacturer"
]
]
}
use Ash.Dsl.Extension, sections: [@cars]
end
Often, we will need to do complex validation/validate based on the configuration
of other resources. Due to the nature of building compile time DSLs, there are
many restrictions around that process. To support these complex use cases, extensions
can include `transformers` which can validate/transform the DSL state after all basic
sections/entities have been created. See `Ash.Dsl.Transformer` for more information.
Transformers are provided as an option to `use`, like so:
use Ash.Dsl.Extension, sections: [@cars], transformers: [
MyApp.Transformers.ValidateNoOverlappingMakesAndModels
]
To expose the configuration of your DSL, define functions that use the
helpers like `get_entities/2` and `get_opt/3`. For example:
defmodule MyApp.Cars do
def cars(resource) do
Ash.Dsl.Extension.get_entities(resource, [:cars])
end
end
MyApp.Cars.cars(MyResource)
# [%MyApp.Car{...}, %MyApp.Car{...}]
"""
@callback sections() :: [Ash.Dsl.Section.t()]
@callback transformers() :: [module]
@doc "Get the entities configured for a given section"
def get_entities(resource, path) do
:persistent_term.get({resource, :ash, path}, %{entities: []}).entities
end
@doc """
Get an option value for a section at a given path.
Checks to see if it has been overridden via configuration.
"""
def get_opt(resource, path, value, default, configurable? \\ false) do
if configurable? do
case get_opt_config(resource, path, value) do
{:ok, value} ->
value
_ ->
Keyword.get(
:persistent_term.get({resource, :ash, path}, %{opts: []}).opts,
value,
default
)
end
else
Keyword.get(
:persistent_term.get({resource, :ash, path}, %{opts: []}).opts,
value,
default
)
end
end
def get_opt_config(resource, path, value) do
with {:ok, config} <- Application.fetch_env(:ash, resource),
{:ok, value} <-
path
|> List.wrap()
|> Kernel.++([value])
|> Enum.reduce_while({:ok, config}, fn key, {:ok, config} ->
if Keyword.keyword?(config) do
case Keyword.fetch(config, key) do
{:ok, value} -> {:cont, {:ok, value}}
:error -> {:halt, :error}
end
else
{:halt, :error}
end
end) do
{:ok, value}
end
end
@doc false
defmacro __using__(opts) do
quote bind_quoted: [
sections: opts[:sections] || [],
transformers: opts[:transformers] || []
] do
alias Ash.Dsl.Extension
@behaviour Extension
Extension.build(__MODULE__, sections)
@_sections sections
@_transformers transformers
@doc false
def sections, do: @_sections
@doc false
def transformers, do: @_transformers
end
end
@doc false
def prepare(extensions) do
body =
quote location: :keep do
@extensions unquote(extensions)
# Due to a few strange stateful bugs I've seen,
# we clear the process of any potentially related state
Process.get()
|> Enum.filter(fn key ->
is_tuple(key) and elem(key, 0) == __MODULE__
end)
|> Enum.each(&Process.delete/1)
:persistent_term.get()
|> Enum.filter(fn key ->
is_tuple(key) and elem(key, 0) == __MODULE__
end)
|> Enum.each(&:persistent_term.delete/1)
end
imports =
for extension <- extensions || [] do
extension = Macro.expand_once(extension, __ENV__)
quote location: :keep do
require Ash.Dsl.Extension
alias Ash.Dsl.Extension
Extension.import_extension(unquote(extension))
end
end
[body | imports]
end
@doc false
defmacro set_state do
quote generated: true, location: :keep do
alias Ash.Dsl.Transformer
ash_dsl_config =
{__MODULE__, :ash_sections}
|> Process.get([])
|> Enum.map(fn {_extension, section_path} ->
{section_path,
Process.get(
{__MODULE__, :ash, section_path},
[]
)}
end)
|> Enum.into(%{})
:persistent_term.put({__MODULE__, :extensions}, @extensions)
Ash.Dsl.Extension.write_dsl_to_persistent_term(__MODULE__, ash_dsl_config)
transformers_to_run =
@extensions
|> Enum.flat_map(& &1.transformers())
|> Transformer.sort()
__MODULE__
|> Ash.Dsl.Extension.run_transformers(
transformers_to_run,
ash_dsl_config
)
|> Map.put_new(:persist, %{})
end
end
defmacro load do
quote do
:persistent_term.put({__MODULE__, :extensions}, @extensions)
Ash.Dsl.Extension.write_dsl_to_persistent_term(__MODULE__, @ash_dsl_config)
:ok
end
end
def run_transformers(mod, transformers, ash_dsl_config) do
Enum.reduce_while(transformers, ash_dsl_config, fn transformer, dsl ->
result =
try do
transformer.transform(mod, dsl)
rescue
e ->
if Exception.exception?(e) do
reraise e, __STACKTRACE__
else
reraise "Exception in transformer #{inspect(transformer)}: \n\n#{
Exception.message(e)
}",
__STACKTRACE__
end
end
case result do
:halt ->
{:halt, dsl}
{:ok, new_dsl} ->
write_dsl_to_persistent_term(mod, new_dsl)
{:cont, new_dsl}
{:error, error} ->
raise_transformer_error(transformer, error)
end
end)
end
@doc false
def write_dsl_to_persistent_term(mod, dsl) do
dsl
|> Map.delete(:persist)
|> Enum.each(fn {section_path, value} ->
:persistent_term.put({mod, :ash, section_path}, value)
end)
Enum.each(Map.get(dsl, :persist, %{}), fn {key, value} ->
:persistent_term.put(key, value)
end)
dsl
end
defp raise_transformer_error(transformer, error) do
if Exception.exception?(error) do
raise error
else
raise "Error while running transformer #{inspect(transformer)}: #{inspect(error)}"
end
end
@doc false
def all_section_paths(path, prior \\ [])
def all_section_paths(nil, _), do: []
def all_section_paths([], _), do: []
def all_section_paths(sections, prior) do
Enum.flat_map(sections, fn section ->
nested = all_section_paths(section.sections(), [section.name, prior])
[Enum.reverse(prior) ++ [section.name] | nested]
end)
|> Enum.uniq()
end
@doc false
def all_section_config_paths(path, prior \\ [])
def all_section_config_paths(nil, _), do: []
def all_section_config_paths([], _), do: []
def all_section_config_paths(sections, prior) do
Enum.flat_map(sections, fn section ->
nested = all_section_config_paths(section.sections(), [section.name, prior])
fields =
Enum.map(section.schema, fn {key, _} ->
{Enum.reverse(prior) ++ [section.name], key}
end)
fields ++ nested
end)
|> Enum.uniq()
end
@doc false
defmacro import_extension(extension) do
quote do
import unquote(extension), only: :macros
end
end
@doc false
defmacro build(extension, sections) do
quote bind_quoted: [sections: sections, extension: extension] do
alias Ash.Dsl.Extension
for section <- sections do
Extension.build_section(extension, section)
end
end
end
@doc false
defmacro build_section(extension, section, path \\ []) do
quote bind_quoted: [section: section, path: path, extension: extension] do
alias Ash.Dsl
{section_modules, entity_modules, opts_module} =
Dsl.Extension.do_build_section(__MODULE__, extension, section, path)
@doc Dsl.Section.describe(__MODULE__, section)
# This macro argument is only called `body` so that it looks nicer
# in the DSL docs
defmacro unquote(section.name)(body) do
opts_module = unquote(opts_module)
section_path = unquote(path ++ [section.name])
section = unquote(Macro.escape(section))
configured_imports =
for module <- unquote(section.imports) do
quote do
import unquote(module)
end
end
entity_imports =
for module <- unquote(entity_modules) do
quote do
import unquote(module)
end
end
section_imports =
for module <- unquote(section_modules) do
quote do
import unquote(module)
end
end
opts_import =
if Map.get(unquote(Macro.escape(section)), :schema, []) == [] do
[]
else
[
quote do
import unquote(opts_module)
end
]
end
configured_unimports =
for module <- unquote(section.imports) do
quote do
import unquote(module), only: []
end
end
entity_unimports =
for module <- unquote(entity_modules) do
quote do
import unquote(module), only: []
end
end
section_unimports =
for module <- unquote(section_modules) do
quote do
import unquote(module), only: []
end
end
opts_unimport =
if Map.get(unquote(Macro.escape(section)), :schema, []) == [] do
[]
else
[
quote do
import unquote(opts_module), only: []
end
]
end
entity_imports ++
section_imports ++
opts_import ++
configured_imports ++
[
quote do
unquote(body[:do])
current_config =
Process.get(
{__MODULE__, :ash, unquote(section_path)},
%{entities: [], opts: []}
)
opts =
case NimbleOptions.validate(
current_config.opts,
Map.get(unquote(Macro.escape(section)), :schema, [])
) do
{:ok, opts} ->
opts
{:error, error} ->
raise Ash.Error.Dsl.DslError,
message: error,
path: unquote(section_path)
end
Process.put(
{__MODULE__, :ash, unquote(section_path)},
%{
entities: current_config.entities,
opts: opts
}
)
end
] ++ configured_unimports ++ opts_unimport ++ entity_unimports ++ section_unimports
end
end
end
@doc false
def do_build_section(mod, extension, section, path) do
entity_modules =
Enum.map(section.entities, fn entity ->
build_entity(mod, extension, path ++ [section.name], entity)
end)
section_modules =
Enum.map(section.sections, fn nested_section ->
nested_mod_name =
path
|> Enum.drop(1)
|> Enum.map(fn nested_section_name ->
Macro.camelize(to_string(nested_section_name))
end)
mod_name =
Module.concat(
[mod | nested_mod_name] ++ [Macro.camelize(to_string(nested_section.name))]
)
{:module, module, _, _} =
defmodule mod_name do
alias Ash.Dsl
@moduledoc Dsl.Section.describe(__MODULE__, nested_section)
require Dsl.Extension
Dsl.Extension.build_section(
extension,
nested_section,
path ++ [section.name]
)
end
module
end)
opts_mod_name =
if section.schema == [] do
nil
else
opts_mod_name = Module.concat([mod, Macro.camelize(to_string(section.name)), Options])
Module.create(
opts_mod_name,
quote bind_quoted: [
section: Macro.escape(section),
section_path: path ++ [section.name],
extension: extension
] do
@moduledoc false
for {field, _opts} <- section.schema do
defmacro unquote(field)(value) do
section_path = unquote(Macro.escape(section_path))
field = unquote(Macro.escape(field))
extension = unquote(extension)
section = unquote(Macro.escape(section))
quote do
current_sections = Process.get({__MODULE__, :ash_sections}, [])
unless {unquote(extension), unquote(section_path)} in current_sections do
Process.put({__MODULE__, :ash_sections}, [
{unquote(extension), unquote(section_path)} | current_sections
])
end
current_config =
Process.get(
{__MODULE__, :ash, unquote(section_path)},
%{entities: [], opts: []}
)
Process.put(
{__MODULE__, :ash, unquote(section_path)},
%{
current_config
| opts: Keyword.put(current_config.opts, unquote(field), unquote(value))
}
)
end
end
end
end,
Macro.Env.location(__ENV__)
)
opts_mod_name
end
{section_modules, entity_modules, opts_mod_name}
end
@doc false
def build_entity(mod, extension, section_path, entity, nested_entity_path \\ []) do
nested_entity_parts = Enum.map(nested_entity_path, &Macro.camelize(to_string(&1)))
mod_parts =
Enum.concat([[mod], nested_entity_parts, [Macro.camelize(to_string(entity.name))]])
mod_name = Module.concat(mod_parts)
options_mod_name = Module.concat(mod_name, "Options")
nested_entity_mods =
Enum.flat_map(entity.entities, fn {key, entities} ->
entities
|> List.wrap()
|> Enum.map(fn entity ->
build_entity(
mod_name,
extension,
section_path,
entity,
nested_entity_path ++ [key]
)
end)
end)
Ash.Dsl.Extension.build_entity_options(options_mod_name, entity.schema, nested_entity_path)
args = Enum.map(entity.args, &Macro.var(&1, mod_name))
Module.create(
mod_name,
quote bind_quoted: [
extension: extension,
entity: Macro.escape(entity),
args: Macro.escape(args),
section_path: Macro.escape(section_path),
options_mod_name: Macro.escape(options_mod_name),
nested_entity_mods: Macro.escape(nested_entity_mods),
nested_entity_path: Macro.escape(nested_entity_path)
] do
@doc Ash.Dsl.Entity.describe(entity)
defmacro unquote(entity.name)(unquote_splicing(args), opts \\ []) do
section_path = unquote(Macro.escape(section_path))
entity_schema = unquote(Macro.escape(entity.schema))
entity = unquote(Macro.escape(entity))
entity_name = unquote(Macro.escape(entity.name))
entity_args = unquote(Macro.escape(entity.args))
options_mod_name = unquote(Macro.escape(options_mod_name))
source = unquote(__MODULE__)
extension = unquote(Macro.escape(extension))
nested_entity_mods = unquote(Macro.escape(nested_entity_mods))
nested_entity_path = unquote(Macro.escape(nested_entity_path))
arg_values = unquote(args)
quote do
# This `try do` block scopes the imports/unimports properly
try do
section_path = unquote(section_path)
entity_name = unquote(entity_name)
extension = unquote(extension)
current_sections = Process.get({__MODULE__, :ash_sections}, [])
Process.put(
{:builder_opts, unquote(nested_entity_path)},
Keyword.merge(
unquote(Keyword.delete(opts, :do)),
Enum.zip(unquote(entity_args), unquote(arg_values))
)
)
import unquote(options_mod_name)
Ash.Dsl.Extension.import_mods(unquote(nested_entity_mods))
unquote(opts[:do])
current_config =
Process.get(
{__MODULE__, :ash, section_path ++ unquote(nested_entity_path)},
%{entities: [], opts: []}
)
import unquote(options_mod_name), only: []
Ash.Dsl.Extension.unimport_mods(unquote(nested_entity_mods))
opts = Process.delete({:builder_opts, unquote(nested_entity_path)})
alias Ash.Dsl.Entity
nested_entities =
unquote(Macro.escape(entity.entities))
|> Enum.map(&elem(&1, 0))
|> Enum.uniq()
|> Enum.reduce(%{}, fn key, acc ->
nested_path = section_path ++ unquote(nested_entity_path) ++ [key]
entities =
{__MODULE__, :ash, nested_path}
|> Process.get(%{entities: []})
|> Map.get(:entities, [])
|> Enum.reverse()
Process.delete({__MODULE__, :ash, nested_path})
Map.update(acc, key, entities, fn current_nested_entities ->
(current_nested_entities || []) ++ entities
end)
end)
built =
case Entity.build(unquote(Macro.escape(entity)), opts, nested_entities) do
{:ok, built} ->
built
{:error, error} ->
additional_path =
if opts[:name] do
[unquote(entity.name), opts[:name]]
else
[unquote(entity.name)]
end
message =
cond do
Exception.exception?(error) ->
Exception.message(error)
is_binary(error) ->
error
true ->
inspect(error)
end
raise Ash.Error.Dsl.DslError,
message: message,
path: section_path ++ additional_path
end
new_config = %{current_config | entities: current_config.entities ++ [built]}
unless {extension, section_path} in current_sections do
Process.put({__MODULE__, :ash_sections}, [
{extension, section_path} | current_sections
])
end
Process.put(
{__MODULE__, :ash, section_path ++ unquote(nested_entity_path)},
new_config
)
rescue
e -> reraise e, __STACKTRACE__
end
end
end
end,
Macro.Env.location(__ENV__)
)
mod_name
end
defmacro import_mods(mods) do
for mod <- mods do
quote do
import unquote(mod)
end
end
end
defmacro unimport_mods(mods) do
for mod <- mods do
quote do
import unquote(mod), only: []
end
end
end
@doc false
def build_entity_options(module_name, schema, nested_entity_path) do
Module.create(
module_name,
quote bind_quoted: [schema: Macro.escape(schema), nested_entity_path: nested_entity_path] do
@moduledoc false
for {key, _value} <- schema do
defmacro unquote(key)(value) do
key = unquote(key)
nested_entity_path = unquote(nested_entity_path)
quote do
current_opts = Process.get({:builder_opts, unquote(nested_entity_path)}, [])
Process.put(
{:builder_opts, unquote(nested_entity_path)},
Keyword.put(current_opts, unquote(key), unquote(value))
)
end
end
end
end,
file: __ENV__.file
)
module_name
end
end