Packages
ash
3.3.1
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/reactor/dsl/action_transformer.ex
defmodule Ash.Reactor.Dsl.ActionTransformer do
@moduledoc """
Responsible for transforming actions.
"""
alias Ash.{Domain, Resource}
alias Reactor.Utils
alias Spark.{Dsl, Dsl.Transformer, Error.DslError}
use Transformer
@doc false
@impl true
@spec before?(module) :: boolean
def before?(Reactor.Dsl.Transformer), do: true
def before?(_), do: false
@doc false
@impl true
@spec transform(Dsl.t()) :: {:ok, Dsl.t()} | {:error, DslError.t()}
def transform(dsl_state) do
dsl_state
|> Transformer.get_entities([:reactor])
|> Utils.reduce_while_ok(dsl_state, fn entity, dsl_state ->
case transform_step(entity, dsl_state) do
{:ok, entity, dsl_state} ->
{:ok, Transformer.replace_entity(dsl_state, [:reactor], entity)}
{:error, reason} ->
{:error, reason}
:ignore ->
{:ok, dsl_state}
end
end)
end
defp transform_step(entity, dsl_state) when entity.action_step? do
with {:ok, entity} <- transform_entity_domain(entity, dsl_state),
:ok <- validate_entity_domain(entity, dsl_state),
:ok <- validate_entity_resource(entity, dsl_state),
{:ok, action} <- get_entity_resource_action(entity, dsl_state),
entity <- %{entity | action: action.name},
:ok <- validate_entity_input_names(entity, action, dsl_state),
:ok <- validate_entity_input_dupes(entity, dsl_state),
:ok <- validate_entity_input_empty(entity, dsl_state),
:ok <- maybe_validate_upsert_identity(entity, dsl_state) do
transform_nested_steps(entity, dsl_state)
end
end
defp transform_step(entity, dsl_state) when entity.type == :transaction do
with :ok <- validate_entity_resources(entity, dsl_state) do
transform_nested_steps(entity, dsl_state)
end
end
defp transform_step(_entity, _dsl_state), do: :ignore
defp transform_nested_steps(entity, dsl_state) when is_list(entity.steps) do
entity.steps
|> Enum.reduce_while({:ok, %{entity | steps: []}, dsl_state}, fn
step, {:ok, entity, dsl_state} ->
case transform_step(step, dsl_state) do
{:ok, step, dsl_state} ->
{:cont, {:ok, %{entity | steps: [step | entity.steps]}, dsl_state}}
:ignore ->
{:cont, {:ok, %{entity | steps: [step | entity.steps]}, dsl_state}}
{:error, reason} ->
{:halt, {:error, reason}}
end
end)
|> case do
{:ok, entity, dsl_state} -> {:ok, %{entity | steps: Enum.reverse(entity.steps)}, dsl_state}
other -> other
end
end
defp transform_nested_steps(entity, dsl_state),
do: {:ok, entity, dsl_state}
defp transform_entity_domain(entity, dsl_state) do
resource_domain = Resource.Info.domain(entity.resource)
default_domain = Transformer.get_option(dsl_state, [:ash], :default_domain)
{:ok, %{entity | domain: entity.domain || resource_domain || default_domain}}
end
defp validate_entity_domain(entity, dsl_state) do
if entity.domain.spark_is() == Domain do
:ok
else
{:error,
DslError.exception(
module: Transformer.get_entities(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` has its domain set to `#{inspect(entity.domain)}` but it is not a valid `Ash.Domain`."
)}
end
end
defp validate_entity_resource(entity, dsl_state) do
if entity.resource.spark_is() == Resource do
:ok
else
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` has its resource set to `#{inspect(entity.resource)}` but it is not a valid Ash resource."
)}
end
end
defp validate_entity_resources(entity, dsl_state) do
entity.resources
|> Enum.reject(&(&1.spark_is() == Resource))
|> case do
[] ->
:ok
[resource] ->
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` has its resources set to `#{inspect(resource)}` but it is not a valid Ash resource."
)}
resources ->
resources = Enum.map_join(resources, ", ", &"`#{inspect(&1)}`")
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` has its resources set to #{resources} but they are not valid Ash resources."
)}
end
end
defp validate_entity_input_dupes(%{inputs: [_]} = _entity, _dsl_state), do: :ok
defp validate_entity_input_dupes(%{inputs: [_ | _]} = entity, dsl_state) do
entity.inputs
|> Enum.map(&MapSet.new(Map.keys(&1.template)))
|> Enum.reduce(&MapSet.intersection/2)
|> Enum.to_list()
|> case do
[] ->
:ok
[key] ->
message = """
The #{entity.type} step `#{inspect(entity.name)}` defines multiple inputs for `#{key}`.
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
keys ->
keys_sentence =
keys
|> Enum.map(&"`#{&1}`")
|> to_sentence(final_sep: " and ")
message = """
The #{entity.type} step `#{inspect(entity.name)}` defines multiple inputs for the keys #{keys_sentence}.
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
end
end
defp validate_entity_input_dupes(_entity, _dsl_state), do: :ok
defp validate_entity_input_empty(entity, dsl_state) when is_map_key(entity, :inputs) do
entity.inputs
|> Enum.filter(&Enum.empty?(&1.template))
|> case do
[] ->
:ok
[_] ->
message = """
The #{entity.type} step `#{inspect(entity.name)}` defines an empty input template.
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
_ ->
message = """
The #{entity.type} step `#{inspect(entity.name)}` defines empty input templates.
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
end
end
defp validate_entity_input_empty(_, _), do: :ok
defp validate_entity_input_names(entity, action, dsl_state) when is_map_key(entity, :inputs) do
argument_names = Enum.map(action.arguments, & &1.name)
allowed_input_names =
entity.resource
|> Resource.Info.attributes()
|> Enum.map(& &1.name)
|> maybe_accept_inputs(action)
|> maybe_reject_inputs(action)
|> Enum.concat(argument_names)
|> MapSet.new()
provided_input_names =
entity.inputs
|> Enum.flat_map(&Map.keys(&1.template))
|> MapSet.new()
provided_input_names
|> MapSet.difference(allowed_input_names)
|> Enum.to_list()
|> case do
[] ->
:ok
[extra] ->
suggestions =
allowed_input_names
|> Enum.map(&to_string/1)
|> sorted_suggestions(extra)
message = """
The #{entity.type} step `#{inspect(entity.name)} refers to an input named `#{extra}` which doesn't exist.
#{suggestions}
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
extras ->
suggestions =
allowed_input_names
|> Enum.map(&to_string/1)
|> sorted_suggestions(hd(extras))
extras_sentence =
extras
|> Enum.map(&"`#{&1}`")
|> to_sentence(final_sep: " and ")
message = """
The #{entity.type} step `#{inspect(entity.name)} refers to an inputs named #{extras_sentence} which don't exist.
#{suggestions}
"""
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message: message
)}
end
end
defp validate_entity_input_names(_entity, _action, _dsl_state), do: :ok
defp maybe_accept_inputs(input_names, action) when length(action.accepts) > 0,
do: Enum.filter(input_names, &(&1 in action.accepts))
defp maybe_accept_inputs(input_names, _), do: input_names
defp maybe_reject_inputs(input_names, action) when length(action.rejects) > 0,
do: Enum.filter(input_names, &(&1 in action.rejects))
defp maybe_reject_inputs(input_names, _), do: input_names
defp get_entity_resource_action(entity, dsl_state) when is_nil(entity.action) do
entity.resource
|> Resource.Info.actions()
|> Enum.find(&(&1.type == entity.type && &1.primary?))
|> case do
nil ->
suggestions =
entity.resource
|> Resource.Info.actions()
|> Enum.filter(&(&1.type == entity.type))
|> Enum.map(&to_string(&1.name))
|> sorted_suggestions(entity.action,
prefix: "Available #{entity.type} actions are ",
suffix: ".",
final_sep: " and "
)
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The step `#{inspect(entity.name)}` has no action name specified and no primary #{entity.type} action." <>
suggestions
)}
action ->
{:ok, action}
end
end
defp get_entity_resource_action(entity, dsl_state) do
case Resource.Info.action(entity.resource, entity.action, action_type(entity.type)) do
nil ->
suggestions =
entity.resource
|> Resource.Info.actions()
|> Enum.filter(&(&1.type == entity.type))
|> Enum.map(&to_string(&1.name))
|> sorted_suggestions(entity.action)
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` refers to an action named `#{entity.action}` which doesn't exist." <>
suggestions
)}
action ->
{:ok, action}
end
end
defp action_type(:bulk_create), do: :create
defp action_type(:bulk_update), do: :update
defp action_type(:create), do: :create
defp action_type(:read), do: :read
defp action_type(:update), do: :update
defp action_type(:destroy), do: :destroy
defp action_type(:action), do: :action
defp maybe_validate_upsert_identity(entity, dsl_state)
when entity.upsert? and entity.upsert_identity do
if Resource.Info.identity(entity.resource, entity.upsert_identity) do
:ok
else
suggestions =
entity.resource
|> Resource.Info.identities()
|> Enum.map(& &1.name)
|> sorted_suggestions(entity.upsert_identity)
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl_state, :module),
path: [:reactor, entity.type, entity.name],
message:
"The #{entity.type} step `#{inspect(entity.name)}` refers to an identity named `#{entity.upsert_identity}` but it does not exist." <>
suggestions
)}
end
end
defp maybe_validate_upsert_identity(_entity, _dsl_state), do: :ok
defp sorted_suggestions(suggestions, tried, options \\ [])
defp sorted_suggestions([], _, _), do: ""
defp sorted_suggestions(suggestions, tried, options) do
tried = to_string(tried)
prefix = Keyword.get(options, :prefix, "Did you mean ")
suffix = Keyword.get(options, :suffix, "?")
suggestions
|> Enum.map(&to_string/1)
|> Enum.sort_by(&String.jaro_distance(&1, tried))
|> Enum.map(&"`#{&1}`")
|> to_sentence(options)
|> then(&"\n\n#{prefix}#{&1}#{suffix}")
end
defp to_sentence([value], _opts), do: to_string(value)
defp to_sentence([_ | _] = values, opts) do
[last | rest] = Enum.reverse(values)
sep = Keyword.get(opts, :sep, ", ")
final_sep = Keyword.get(opts, :final_sep, " or ")
rest =
rest
|> Enum.reverse()
|> Enum.join(sep)
"#{rest}#{final_sep}#{last}"
end
end