Skip to content

Commit dc26701

Browse files
committed
Instance Extension transformers — bake DSL data at compile time
Five Spark.Dsl.Transformer modules bake structural DSL data into zero-cost module functions (__diffo_specification__/0, __diffo_characteristics__/0, __diffo_features__/0, __diffo_party_declarations__/0) and a behavioural transformer (TransformBuildActions) composes them into __diffo_build_before__/1 and __diffo_build_after__/2. 2-arity variants added to Specification, Feature, Characteristic, and Party helpers accept pre-baked declaration lists instead of doing runtime Info introspection, so the generated build hooks pay no Info lookup cost at runtime. Transformer ordering is enforced via after?/1 so TransformBuildActions always runs after all four structural transformers.
1 parent 620624c commit dc26701

11 files changed

Lines changed: 323 additions & 4 deletions

File tree

lib/diffo/provider/components/instance/extension.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ defmodule Diffo.Provider.Instance.Extension do
224224

225225
use Spark.Dsl.Extension,
226226
sections: [@specification, @features, @characteristics, @parties],
227+
transformers: [
228+
Diffo.Provider.Instance.Extension.Transformers.TransformSpecification,
229+
Diffo.Provider.Instance.Extension.Transformers.TransformCharacteristics,
230+
Diffo.Provider.Instance.Extension.Transformers.TransformFeatures,
231+
Diffo.Provider.Instance.Extension.Transformers.TransformParties,
232+
Diffo.Provider.Instance.Extension.Transformers.TransformBuildActions
233+
],
227234
verifiers: [
228235
Diffo.Provider.Instance.Extension.Verifiers.VerifySpecification,
229236
Diffo.Provider.Instance.Extension.Verifiers.VerifyCharacteristics,

lib/diffo/provider/components/instance/extension/characteristic.ex

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,30 @@ defmodule Diffo.Provider.Instance.Characteristic do
3535
end
3636
end
3737

38+
def set_characteristics_argument(changeset, declarations)
39+
when is_struct(changeset, Ash.Changeset) and is_list(declarations) do
40+
case characteristics = create_characteristics_from_declarations(declarations, :instance) do
41+
[] ->
42+
changeset
43+
44+
{:error, error} ->
45+
Ash.Changeset.add_error(changeset, error)
46+
47+
_ ->
48+
characteristic_ids = Enum.map(characteristics, &Map.get(&1, :id))
49+
Ash.Changeset.force_set_argument(changeset, :characteristics, characteristic_ids)
50+
end
51+
end
52+
3853
@doc """
3954
Creates the Characteristics from a Extended Instance's module
4055
"""
4156
def create_characteristics(module, type) when is_atom(module) and is_atom(type) do
42-
characteristics = Info.characteristics(module)
57+
Info.characteristics(module) |> create_characteristics_from_declarations(type)
58+
end
4359

44-
Enum.reduce_while(characteristics, [], fn %{name: name, value_type: value_type}, acc ->
60+
defp create_characteristics_from_declarations(declarations, type) do
61+
Enum.reduce_while(declarations, [], fn %{name: name, value_type: value_type}, acc ->
4562
try do
4663
attrs =
4764
case value_type do

lib/diffo/provider/components/instance/extension/feature.ex

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,31 @@ defmodule Diffo.Provider.Instance.Feature do
3535
end
3636
end
3737

38+
def set_features_argument(changeset, declarations)
39+
when is_struct(changeset, Ash.Changeset) and is_list(declarations) do
40+
case features = create_features_from_declarations(declarations) do
41+
[] ->
42+
changeset
43+
44+
{:error, error} ->
45+
Ash.Changeset.add_error(changeset, error)
46+
47+
_ ->
48+
feature_ids = Enum.map(features, &Map.get(&1, :id))
49+
Ash.Changeset.force_set_argument(changeset, :features, feature_ids)
50+
end
51+
end
52+
3853
@doc """
3954
Creates the Features from a Extended Instance's module
4055
"""
4156
def create_features(module) when is_atom(module) do
42-
features = Info.features(module)
57+
Info.features(module) |> create_features_from_declarations()
58+
end
4359

60+
defp create_features_from_declarations(declarations) do
4461
Enum.reduce_while(
45-
features,
62+
declarations,
4663
[],
4764
# create any feature characteristics
4865
fn %{name: name, is_enabled?: isEnabled, characteristics: characteristics}, acc ->

lib/diffo/provider/components/instance/extension/party.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ defmodule Diffo.Provider.Instance.Party do
1515
@doc false
1616
def validate_parties(changeset) do
1717
declarations = InstanceInfo.parties(changeset.resource)
18+
validate_parties(changeset, declarations)
19+
end
1820

21+
def validate_parties(changeset, declarations) do
1922
if declarations == [] do
2023
changeset
2124
else

lib/diffo/provider/components/instance/extension/specification.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ defmodule Diffo.Provider.Instance.Specification do
3030
end
3131
end
3232

33+
def set_specified_by_argument(changeset, options)
34+
when is_struct(changeset, Ash.Changeset) and is_list(options) do
35+
specification = struct(__MODULE__, options)
36+
37+
case Provider.create_specification(Map.from_struct(specification)) do
38+
{:ok, _} ->
39+
Ash.Changeset.force_set_argument(changeset, :specified_by, specification.id)
40+
41+
{:error, error} ->
42+
Ash.Changeset.add_error(changeset, error)
43+
end
44+
end
45+
3346
@doc """
3447
Upserts the Specification from a Extended Instance's module
3548
"""
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Diffo.Provider.Instance.Extension.Transformers.TransformBuildActions do
6+
@moduledoc "Generates __diffo_build_before__/1 and __diffo_build_after__/2 from baked structural data"
7+
use Spark.Dsl.Transformer
8+
alias Spark.Dsl.Transformer
9+
10+
@impl true
11+
def transform(dsl_state) do
12+
{:ok, Transformer.eval(dsl_state, [], quote do
13+
@doc false
14+
def __diffo_build_before__(changeset) do
15+
changeset
16+
|> Diffo.Provider.Instance.Specification.set_specified_by_argument(__diffo_specification__())
17+
|> Diffo.Provider.Instance.Feature.set_features_argument(__diffo_features__())
18+
|> Diffo.Provider.Instance.Characteristic.set_characteristics_argument(__diffo_characteristics__())
19+
|> Diffo.Provider.Instance.Party.validate_parties(__diffo_party_declarations__())
20+
end
21+
22+
@doc false
23+
def __diffo_build_after__(changeset, result) do
24+
Diffo.Provider.Instance.ActionHelper.build_after(changeset, result)
25+
end
26+
end)}
27+
end
28+
29+
@impl true
30+
def after?(Diffo.Provider.Instance.Extension.Transformers.TransformSpecification), do: true
31+
def after?(Diffo.Provider.Instance.Extension.Transformers.TransformCharacteristics), do: true
32+
def after?(Diffo.Provider.Instance.Extension.Transformers.TransformFeatures), do: true
33+
def after?(Diffo.Provider.Instance.Extension.Transformers.TransformParties), do: true
34+
def after?(_), do: false
35+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Diffo.Provider.Instance.Extension.Transformers.TransformCharacteristics do
6+
@moduledoc "Bakes characteristic declarations into __diffo_characteristics__/0"
7+
use Spark.Dsl.Transformer
8+
alias Spark.Dsl.Transformer
9+
10+
@impl true
11+
def transform(dsl_state) do
12+
characteristics = Transformer.get_entities(dsl_state, [:characteristics])
13+
escaped = Macro.escape(characteristics)
14+
15+
{:ok, Transformer.eval(dsl_state, [], quote do
16+
@doc false
17+
def __diffo_characteristics__, do: unquote(escaped)
18+
end)}
19+
end
20+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Diffo.Provider.Instance.Extension.Transformers.TransformFeatures do
6+
@moduledoc "Bakes feature declarations into __diffo_features__/0"
7+
use Spark.Dsl.Transformer
8+
alias Spark.Dsl.Transformer
9+
10+
@impl true
11+
def transform(dsl_state) do
12+
features = Transformer.get_entities(dsl_state, [:features])
13+
escaped = Macro.escape(features)
14+
15+
{:ok, Transformer.eval(dsl_state, [], quote do
16+
@doc false
17+
def __diffo_features__, do: unquote(escaped)
18+
end)}
19+
end
20+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Diffo.Provider.Instance.Extension.Transformers.TransformParties do
6+
@moduledoc "Bakes party declarations into __diffo_party_declarations__/0"
7+
use Spark.Dsl.Transformer
8+
alias Spark.Dsl.Transformer
9+
10+
@impl true
11+
def transform(dsl_state) do
12+
parties = Transformer.get_entities(dsl_state, [:parties])
13+
escaped = Macro.escape(parties)
14+
15+
{:ok, Transformer.eval(dsl_state, [], quote do
16+
@doc false
17+
def __diffo_party_declarations__, do: unquote(escaped)
18+
end)}
19+
end
20+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# SPDX-FileCopyrightText: 2025 diffo contributors <https://github.com/diffo-dev/diffo/graphs.contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Diffo.Provider.Instance.Extension.Transformers.TransformSpecification do
6+
@moduledoc "Bakes specification DSL options into __diffo_specification__/0"
7+
use Spark.Dsl.Transformer
8+
alias Spark.Dsl.Transformer
9+
10+
@impl true
11+
def transform(dsl_state) do
12+
spec = [
13+
id: Transformer.get_option(dsl_state, [:specification], :id),
14+
name: Transformer.get_option(dsl_state, [:specification], :name),
15+
type: Transformer.get_option(dsl_state, [:specification], :type),
16+
major_version: Transformer.get_option(dsl_state, [:specification], :major_version),
17+
description: Transformer.get_option(dsl_state, [:specification], :description),
18+
category: Transformer.get_option(dsl_state, [:specification], :category)
19+
]
20+
21+
escaped = Macro.escape(spec)
22+
23+
{:ok, Transformer.eval(dsl_state, [], quote do
24+
@doc false
25+
def __diffo_specification__, do: unquote(escaped)
26+
end)}
27+
end
28+
end

0 commit comments

Comments
 (0)