defmodule AshAuthentication.Strategy.Custom.Transformer do
@moduledoc """
Transformer used by custom strategies.
It delegates transformation passes to the individual strategies.
"""
use Spark.Dsl.Transformer
alias AshAuthentication.{Info, Strategy}
alias Spark.{Dsl.Transformer, Error.DslError}
import AshAuthentication.Strategy.Custom.Helpers
@doc false
@impl true
@spec after?(module) :: boolean
def after?(AshAuthentication.Transformer), do: true
def after?(_), do: false
@doc false
@impl true
@spec before?(module) :: boolean
def before?(Resource.Transformers.DefaultAccept), do: true
def before?(_), do: false
@doc false
@impl true
@spec transform(map) ::
:ok
| {:ok, map()}
| {:error, term()}
| {:warn, map(), String.t() | [String.t()]}
| :halt
def transform(dsl_state) do
with {:ok, dsl_state} <- do_strategy_transforms(dsl_state) do
do_add_on_transforms(dsl_state)
end
end
defp do_strategy_transforms(dsl_state) do
dsl_state
|> Info.authentication_strategies()
|> Enum.reduce_while({:ok, dsl_state}, fn strategy, {:ok, dsl_state} ->
case do_transform(strategy, dsl_state, :strategy) do
{:ok, dsl_state} -> {:cont, {:ok, dsl_state}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp do_add_on_transforms(dsl_state) do
dsl_state
|> Info.authentication_add_ons()
|> Enum.reduce_while({:ok, dsl_state}, fn strategy, {:ok, dsl_state} ->
case do_transform(strategy, dsl_state, :add_on) do
{:ok, dsl_state} -> {:cont, {:ok, dsl_state}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp do_transform(strategy, _, _) when not is_map_key(strategy, :strategy_module) do
name = Strategy.name(strategy)
{:error,
DslError.exception(
path: [:authentication, name],
message:
"The struct defined by `#{inspect(strategy.__struct__)}` must contain a `strategy_module` field."
)}
end
defp do_transform(strategy, _, _) when not is_map_key(strategy, :resource) do
name = Strategy.name(strategy)
{:error,
DslError.exception(
path: [:authentication, name],
message:
"The struct defined by `#{inspect(strategy.__struct__)}` must contain a `resource` field."
)}
end
defp do_transform(strategy, dsl_state, :strategy) do
strategy = %{strategy | resource: Transformer.get_persisted(dsl_state, :module)}
dsl_state = put_strategy(dsl_state, strategy)
entity_module = strategy.__struct__
strategy
|> strategy.strategy_module.transform(dsl_state)
|> case do
{:ok, strategy} when is_struct(strategy, entity_module) ->
{:ok, put_strategy(dsl_state, strategy)}
{:ok, dsl_state} when is_map(dsl_state) ->
{:ok, dsl_state}
{:error, reason} ->
{:error, reason}
end
end
defp do_transform(strategy, dsl_state, :add_on) do
strategy = %{strategy | resource: Transformer.get_persisted(dsl_state, :module)}
dsl_state = put_add_on(dsl_state, strategy)
entity_module = strategy.__struct__
strategy
|> strategy.strategy_module.transform(dsl_state)
|> case do
{:ok, strategy} when is_struct(strategy, entity_module) ->
{:ok, put_add_on(dsl_state, strategy)}
{:ok, dsl_state} when is_map(dsl_state) ->
{:ok, dsl_state}
{:error, reason} ->
{:error, reason}
end
end
end