Spark.Dsl.Transformer behaviour (spark v2.6.0)

Copy Markdown View Source

A transformer manipulates and/or validates the entire DSL state of a module at compile time.

Transformers run during compilation and can read, modify, and persist DSL state. They are the primary mechanism for setting defaults, building entities programmatically, caching computed values, and reshaping configuration before the module is finalized.

Usage

defmodule MyApp.MyExtension.Transformers.SetDefaults do
  use Spark.Dsl.Transformer

  def transform(dsl_state) do
    # Read entities and options
    actions = Spark.Dsl.Transformer.get_entities(dsl_state, [:actions])

    # Modify DSL state and return it
    dsl_state = Spark.Dsl.Transformer.persist(dsl_state, :action_count, length(actions))
    {:ok, dsl_state}
  end
end

Callbacks

  • transform/1 - Receives the DSL state map. Return {:ok, dsl_state} to continue with modifications, :ok to continue without modifications, {:error, term} to halt with an error, {:warn, dsl_state, warnings} to continue with warnings, or :halt to silently stop processing further transformers.

  • before?/1 - Return true if this transformer must run before the given transformer module.

  • after?/1 - Return true if this transformer must run after the given transformer module.

Reading vs. Modifying State

Use the helper functions in this module rather than manipulating the DSL state map directly. For reading: get_entities/2, get_option/3, fetch_option/3, get_persisted/2. For writing: add_entity/3, replace_entity/3, remove_entity/3, set_option/4, persist/3.

Transformers vs. Verifiers

If you only need to validate state without modifying it, and you reference other modules (e.g. checking that a referenced module exists), use Spark.Dsl.Verifier instead. Verifiers run after compilation and do not create compile-time dependencies between modules.

Summary

Functions

Adds an entity to the DSL state at the given section path.

Runs the function in an async compiler.

Programmatically builds an entity struct defined in the given extension.

Add a quoted expression to be evaluated in the DSL module's context.

Fetches a DSL option, returning {:ok, value} or :error.

Fetches a persisted value, returning {:ok, value} or :error.

Returns all entities at the given section path.

Returns the source location annotation for a specific option, useful for error reporting.

Gets a DSL option value at the given section path, returning default if not set.

Retrieves a value that was previously stored with persist/3.

Returns the source location annotation for a section, useful for error reporting.

Saves a value into the dsl config with the given key.

Removes entities at the given path that match the filter function.

Replaces an entity at the given path with replacement.

Sets a DSL option value at the given section path.

Types

warning()

@type warning() :: String.t() | {String.t(), :erl_anno.anno()}

Callbacks

after?(module)

@callback after?(module()) :: boolean()

after_compile?()

@callback after_compile?() :: boolean()

before?(module)

@callback before?(module()) :: boolean()

transform(map)

@callback transform(map()) ::
  :ok
  | {:ok, map()}
  | {:error, term()}
  | {:warn, map(), warning() | [warning()]}
  | :halt

Functions

add_entity(dsl_state, path, entity, opts \\ [])

Adds an entity to the DSL state at the given section path.

By default, entities are prepended. Pass type: :append to append instead.

async_compile(dsl, fun)

Runs the function in an async compiler.

Use this for compiling new modules and having them compiled efficiently asynchronously.

build_entity(extension, path, name, opts)

Programmatically builds an entity struct defined in the given extension.

path is the section path (e.g. [:actions]), name is the entity name (e.g. :read), and opts is a keyword list of options matching the entity's schema. The entity's schema validations and transforms are applied.

Commonly used to add entities to DSL state in a transformer:

{:ok, action} = Transformer.build_entity(Ash.Resource.Dsl, [:actions], :read, name: :read)
dsl_state = Transformer.add_entity(dsl_state, [:actions], action)

build_entity!(extension, path, name, opts)

Same as build_entity/4 but raises on error.

eval(dsl, bindings, block)

Add a quoted expression to be evaluated in the DSL module's context.

Use this extremely sparingly. It should almost never be necessary, unless building certain extensions that require the module in question to define a given function.

What you likely want is either one of the DSL introspection functions, like Spark.Dsl.Extension.get_entities/2 or Spark.Dsl.Extension.get_opt/5). If you simply want to store a custom value that can be retrieved easily, or cache some precomputed information onto the resource, use persist/3.

Provide the dsl state, bindings that should be unquote-able, and the quoted block to evaluate in the module. For example, if we wanted to support a resource.primary_key() function that would return the primary key (this is unnecessary, just an example), we might do this:

fields = the_primary_key_fields

dsl_state =
  Spark.Dsl.Transformer.eval(
    dsl_state,
    [fields: fields],
    quote do
      def primary_key() do
        unquote(fields)
      end
    end
  )

fetch_option(dsl_state, path, option)

Fetches a DSL option, returning {:ok, value} or :error.

fetch_persisted(dsl, key)

Fetches a persisted value, returning {:ok, value} or :error.

get_entities(dsl_state, path)

Returns all entities at the given section path.

get_opt_anno(dsl_state, path, option)

@spec get_opt_anno(map(), [atom()], atom()) :: :erl_anno.anno() | nil

Returns the source location annotation for a specific option, useful for error reporting.

get_option(dsl_state, path, option, default \\ nil)

Gets a DSL option value at the given section path, returning default if not set.

get_persisted(dsl, key, default \\ nil)

Retrieves a value that was previously stored with persist/3.

Returns default if the key has not been persisted.

get_section_anno(dsl_state, path)

@spec get_section_anno(map(), [atom()]) :: :erl_anno.anno() | nil

Returns the source location annotation for a section, useful for error reporting.

persist(dsl, key, value)

Saves a value into the dsl config with the given key.

This can be used to precompute some information and cache it onto the resource, or simply store a computed value. It can later be retrieved with Spark.Dsl.Extension.get_persisted/3.

remove_entity(dsl_state, path, func)

Removes entities at the given path that match the filter function.

The function receives each entity and should return true for entities to remove.

replace_entity(dsl_state, path, replacement, matcher \\ nil)

Replaces an entity at the given path with replacement.

By default, matches on struct type and __identifier__. Pass a custom matcher function to control which entity gets replaced.

set_option(dsl_state, path, option, value)

Sets a DSL option value at the given section path.