Skip to content

Commit 23a3f4d

Browse files
committed
improve verify_characterisic, fix shelf bug
1 parent d432d41 commit 23a3f4d

9 files changed

Lines changed: 173 additions & 35 deletions

File tree

lib/diffo/provider/assigner/assignable_characteristic.ex

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ defmodule Diffo.Provider.AssignableCharacteristic do
4747
default :lowest
4848
constraints one_of: [:lowest, :highest, :random]
4949
end
50+
51+
attribute :thing, :atom do
52+
description "the kind of item being assigned (e.g. :slot, :port); set from the pool declaration at build time"
53+
public? true
54+
allow_nil? true
55+
end
5056
end
5157

5258
calculations do
@@ -60,11 +66,15 @@ defmodule Diffo.Provider.AssignableCharacteristic do
6066
public? true
6167
argument :thing, :atom, allow_nil?: false
6268
end
69+
70+
calculate :free, :integer, Diffo.Provider.Calculations.FreeValues do
71+
public? true
72+
end
6373
end
6474

6575
actions do
6676
create :create do
67-
accept [:name, :first, :last, :assignable_type, :algorithm]
77+
accept [:name, :first, :last, :assignable_type, :algorithm, :thing]
6878
argument :instance_id, :uuid
6979
argument :feature_id, :uuid
7080
change manage_relationship(:instance_id, :instance, type: :append)
@@ -77,7 +87,7 @@ defmodule Diffo.Provider.AssignableCharacteristic do
7787
end
7888

7989
preparations do
80-
prepare build(load: [:value])
90+
prepare build(load: [:value, :free])
8191
end
8292

8393
jason do
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.Calculations.FreeValues do
6+
@moduledoc false
7+
use Ash.Resource.Calculation
8+
9+
@impl true
10+
def load(_query, _opts, _context), do: []
11+
12+
@impl true
13+
def calculate(records, _opts, _context) do
14+
Enum.map(records, fn
15+
%{thing: nil} ->
16+
nil
17+
18+
record ->
19+
count =
20+
Diffo.Provider.AssignedToRelationship
21+
|> Ash.Query.filter_input(
22+
source_id: record.instance_id,
23+
pool: record.name,
24+
thing: record.thing
25+
)
26+
|> Ash.read!(domain: Diffo.Provider)
27+
|> length()
28+
29+
record.last - record.first + 1 - count
30+
end)
31+
end
32+
end

lib/diffo/provider/extension/info.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ defmodule Diffo.Provider.Extension.Info do
2727
Code.ensure_loaded?(module) and
2828
Diffo.Provider.Place.Extension in Ash.Resource.Info.extensions(module)
2929
end
30+
31+
@doc "Returns true if the module is a BaseCharacteristic-derived resource (or Characteristic itself)"
32+
@spec characteristic?(module()) :: boolean()
33+
def characteristic?(module) do
34+
Code.ensure_loaded?(module) and
35+
Diffo.Provider.Characteristic.Extension in Ash.Resource.Info.extensions(module)
36+
end
3037
end

lib/diffo/provider/extension/pool.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ defmodule Diffo.Provider.Extension.Pool do
1010

1111
@doc "Creates AssignableCharacteristic nodes for each declared pool during the build action"
1212
def create_pools(result, pools) when is_struct(result) and is_list(pools) do
13-
Enum.reduce_while(pools, {:ok, result}, fn %__MODULE__{name: name}, {:ok, acc} ->
13+
Enum.reduce_while(pools, {:ok, result}, fn %__MODULE__{name: name, thing: thing}, {:ok, acc} ->
1414
case Diffo.Provider.AssignableCharacteristic
15-
|> Ash.Changeset.for_create(:create, %{name: name, instance_id: acc.id})
15+
|> Ash.Changeset.for_create(:create, %{name: name, thing: thing, instance_id: acc.id})
1616
|> Ash.create() do
1717
{:ok, _} -> {:cont, {:ok, acc}}
1818
{:error, error} -> {:halt, {:error, error}}

lib/diffo/provider/extension/verifiers/verify_characteristics.ex

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
# SPDX-License-Identifier: MIT
44

55
defmodule Diffo.Provider.Extension.Verifiers.VerifyCharacteristics do
6-
@moduledoc "Verifies characteristic names are unique and value_type modules exist"
6+
@moduledoc "Verifies characteristic names are unique and value_type modules exist and extend BaseCharacteristic"
77
use Spark.Dsl.Verifier
88

99
alias Spark.Dsl.Verifier
1010
alias Spark.Error.DslError
11+
alias Diffo.Provider.Extension.Info
1112

1213
@impl true
1314
def verify(dsl_state) do
@@ -30,17 +31,30 @@ defmodule Diffo.Provider.Extension.Verifiers.VerifyCharacteristics do
3031
Enum.reduce(characteristics, [], fn char, acc ->
3132
case module_from_value_type(char.value_type) do
3233
{:ok, module} ->
33-
if Code.ensure_loaded?(module) do
34-
acc
35-
else
36-
[
37-
DslError.exception(
38-
module: resource,
39-
path: [:provider, :characteristics, char.name],
40-
message: "characteristics: value_type #{inspect(module)} does not exist"
41-
)
42-
| acc
43-
]
34+
cond do
35+
!Code.ensure_loaded?(module) ->
36+
[
37+
DslError.exception(
38+
module: resource,
39+
path: [:provider, :characteristics, char.name],
40+
message: "characteristics: value_type #{inspect(module)} does not exist"
41+
)
42+
| acc
43+
]
44+
45+
!Info.characteristic?(module) ->
46+
[
47+
DslError.exception(
48+
module: resource,
49+
path: [:provider, :characteristics, char.name],
50+
message:
51+
"characteristics: value_type #{inspect(module)} does not extend BaseCharacteristic"
52+
)
53+
| acc
54+
]
55+
56+
true ->
57+
acc
4458
end
4559

4660
:error ->

lib/diffo/provider/extension/verifiers/verify_features.ex

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
# SPDX-License-Identifier: MIT
44

55
defmodule Diffo.Provider.Extension.Verifiers.VerifyFeatures do
6-
@moduledoc "Verifies feature names are unique and feature characteristic value_type modules exist"
6+
@moduledoc "Verifies feature names are unique and feature characteristic value_type modules exist and extend BaseCharacteristic"
77
use Spark.Dsl.Verifier
88

99
alias Spark.Dsl.Verifier
1010
alias Spark.Error.DslError
11+
alias Diffo.Provider.Extension.Info
1112

1213
@impl true
1314
def verify(dsl_state) do
@@ -45,18 +46,31 @@ defmodule Diffo.Provider.Extension.Verifiers.VerifyFeatures do
4546
Enum.reduce(feature.characteristics || [], [], fn char, inner_acc ->
4647
case module_from_value_type(char.value_type) do
4748
{:ok, module} ->
48-
if Code.ensure_loaded?(module) do
49-
inner_acc
50-
else
51-
[
52-
DslError.exception(
53-
module: resource,
54-
path: [:provider, :features, feature.name, :characteristics, char.name],
55-
message:
56-
"features: characteristic value_type #{inspect(module)} does not exist"
57-
)
58-
| inner_acc
59-
]
49+
cond do
50+
!Code.ensure_loaded?(module) ->
51+
[
52+
DslError.exception(
53+
module: resource,
54+
path: [:provider, :features, feature.name, :characteristics, char.name],
55+
message:
56+
"features: characteristic value_type #{inspect(module)} does not exist"
57+
)
58+
| inner_acc
59+
]
60+
61+
!Info.characteristic?(module) ->
62+
[
63+
DslError.exception(
64+
module: resource,
65+
path: [:provider, :features, feature.name, :characteristics, char.name],
66+
message:
67+
"features: characteristic value_type #{inspect(module)} does not extend BaseCharacteristic"
68+
)
69+
| inner_acc
70+
]
71+
72+
true ->
73+
inner_acc
6074
end
6175

6276
:error ->

test/provider/extension/instance_transformer_test.exs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,9 @@ defmodule Diffo.Provider.Extension.InstanceTransformerTest do
4141
test "bakes characteristics/0 onto the resource" do
4242
chars = ShelfInstance.characteristics()
4343
assert is_list(chars)
44-
assert length(chars) == 3
44+
assert length(chars) == 2
4545
names = Enum.map(chars, & &1.name)
4646
assert :shelf in names
47-
assert :slots in names
4847
assert :shelves in names
4948
end
5049

@@ -54,7 +53,7 @@ defmodule Diffo.Provider.Extension.InstanceTransformerTest do
5453
end
5554

5655
test "characteristics are also accessible via Info" do
57-
assert length(Info.characteristics(ShelfInstance)) == 3
56+
assert length(Info.characteristics(ShelfInstance)) == 2
5857
# Card has :card characteristic; :ports moved to pools do
5958
assert length(Info.characteristics(CardInstance)) == 1
6059
end

test/provider/extension/instance_verifier_test.exs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,34 @@ defmodule Diffo.Provider.Extension.InstanceVerifierTest do
217217
end
218218
)
219219
end
220+
221+
test "value_type not extending BaseCharacteristic warns DslError on compilation" do
222+
Util.assert_compile_time_warning(
223+
Spark.Error.DslError,
224+
"characteristics: value_type Diffo.Test.Instance.ShelfInstance does not extend BaseCharacteristic",
225+
fn ->
226+
defmodule InvalidCharBaseType do
227+
alias Diffo.Provider.BaseInstance
228+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
229+
230+
resource do
231+
description "resource with characteristic value_type that is not a BaseCharacteristic"
232+
end
233+
234+
provider do
235+
specification do
236+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
237+
name "invalid"
238+
end
239+
240+
characteristics do
241+
characteristic :foo, Diffo.Test.Instance.ShelfInstance
242+
end
243+
end
244+
end
245+
end
246+
)
247+
end
220248
end
221249

222250
describe "features verifier" do
@@ -312,6 +340,36 @@ defmodule Diffo.Provider.Extension.InstanceVerifierTest do
312340
end
313341
)
314342
end
343+
344+
test "feature characteristic value_type not extending BaseCharacteristic warns DslError on compilation" do
345+
Util.assert_compile_time_warning(
346+
Spark.Error.DslError,
347+
"features: characteristic value_type Diffo.Test.Instance.ShelfInstance does not extend BaseCharacteristic",
348+
fn ->
349+
defmodule InvalidFeatureCharBaseType do
350+
alias Diffo.Provider.BaseInstance
351+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
352+
353+
resource do
354+
description "resource with feature characteristic value_type that is not a BaseCharacteristic"
355+
end
356+
357+
provider do
358+
specification do
359+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
360+
name "invalid"
361+
end
362+
363+
features do
364+
feature :my_feature do
365+
characteristic :baz, Diffo.Test.Instance.ShelfInstance
366+
end
367+
end
368+
end
369+
end
370+
end
371+
)
372+
end
315373
end
316374

317375
describe "parties verifier" do

test/support/resource/instance/shelf_instance.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ defmodule Diffo.Test.Instance.ShelfInstance do
1212
alias Diffo.Provider.BaseInstance
1313
alias Diffo.Provider.Instance.Relationship
1414
alias Diffo.Provider.Extension.Characteristic
15+
alias Diffo.Provider.Extension.Pool
1516
alias Diffo.Provider.Assigner
1617
alias Diffo.Provider.Assignment
17-
alias Diffo.Provider.AssignableValue
1818
alias Diffo.Test.Servo
1919
alias Diffo.Test.Characteristic.ShelfCharacteristic
2020
alias Diffo.Test.Characteristic.DeploymentClass
@@ -53,10 +53,13 @@ defmodule Diffo.Test.Instance.ShelfInstance do
5353

5454
characteristics do
5555
characteristic :shelf, ShelfCharacteristic
56-
characteristic :slots, AssignableValue
5756
characteristic :shelves, {:array, ShelfCharacteristic}
5857
end
5958

59+
pools do
60+
pool :slots, :slot
61+
end
62+
6063
parties do
6164
party :facilitator, Organization
6265
party :overseer, Person
@@ -97,6 +100,7 @@ defmodule Diffo.Test.Instance.ShelfInstance do
97100
change after_action(fn changeset, result, _context ->
98101
with {:ok, result} <-
99102
Characteristic.update_all(result, changeset, characteristics()),
103+
{:ok, result} <- Pool.update_pools(result, changeset, pools()),
100104
{:ok, result} <- Servo.get_shelf_by_id(result.id),
101105
do: {:ok, result}
102106
end)
@@ -118,7 +122,7 @@ defmodule Diffo.Test.Instance.ShelfInstance do
118122
argument :assignment, :struct, constraints: [instance_of: Assignment]
119123

120124
change after_action(fn changeset, result, _context ->
121-
with {:ok, result} <- Assigner.assign(result, changeset, :slots, :slot),
125+
with {:ok, result} <- Assigner.assign(result, changeset, :slots),
122126
{:ok, result} <- Servo.get_shelf_by_id(result.id),
123127
do: {:ok, result}
124128
end)

0 commit comments

Comments
 (0)