Skip to content

Commit 7e039dd

Browse files
committed
provider instance specification DSL — minor, patch and tmf versions
Add minor_version, patch_version and tmf_version to the specification do DSL section. Nil values are stripped before create_specification so unset fields fall back to Specification resource defaults.
1 parent 361f207 commit 7e039dd

3 files changed

Lines changed: 159 additions & 15 deletions

File tree

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

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

55
defmodule Diffo.Provider.Instance.Extension.Verifiers.VerifySpecification do
6-
@moduledoc "Verifies that the specification id is a valid UUID4"
6+
@moduledoc "Verifies that the specification DSL values satisfy the Specification resource's attribute constraints"
77
use Spark.Dsl.Verifier
88

99
alias Spark.Dsl.Verifier
1010
alias Spark.Error.DslError
1111

12+
# Fields validated against Specification attribute constraints (id handled separately)
13+
@spec_fields [:name, :type, :major_version, :minor_version, :patch_version, :tmf_version, :description, :category]
14+
1215
@impl true
1316
def verify(dsl_state) do
1417
resource = Verifier.get_persisted(dsl_state, :module)
18+
19+
errors = check_id(dsl_state, resource) ++ check_attributes(dsl_state, resource)
20+
21+
case errors do
22+
[] -> :ok
23+
errors -> {:error, errors}
24+
end
25+
end
26+
27+
defp check_id(dsl_state, resource) do
1528
spec_id = Verifier.get_option(dsl_state, [:structure, :specification], :id)
1629

17-
errors =
18-
if spec_id && !Diffo.Uuid.uuid4?(spec_id) do
19-
[
20-
DslError.exception(
21-
module: resource,
22-
path: [:structure, :specification, :id],
23-
message: "specification: id must be a valid UUID4"
24-
)
25-
]
30+
if spec_id && !Diffo.Uuid.uuid4?(spec_id) do
31+
[DslError.exception(
32+
module: resource,
33+
path: [:structure, :specification, :id],
34+
message: "specification: id must be a valid UUID4"
35+
)]
36+
else
37+
[]
38+
end
39+
end
40+
41+
defp check_attributes(dsl_state, resource) do
42+
spec_attrs =
43+
Ash.Resource.Info.attributes(Diffo.Provider.Specification)
44+
|> Map.new(&{&1.name, &1})
45+
46+
Enum.flat_map(@spec_fields, fn field ->
47+
value = Verifier.get_option(dsl_state, [:structure, :specification], field)
48+
attr = Map.get(spec_attrs, field)
49+
50+
if not is_nil(value) && not is_nil(attr) do
51+
case Ash.Type.apply_constraints(attr.type, value, attr.constraints) do
52+
{:ok, _} ->
53+
[]
54+
55+
{:error, errors} ->
56+
[DslError.exception(
57+
module: resource,
58+
path: [:structure, :specification, field],
59+
message: "specification: #{field} - #{format_errors(errors)}"
60+
)]
61+
end
2662
else
2763
[]
2864
end
65+
end)
66+
end
2967

30-
case errors do
31-
[] -> :ok
32-
errors -> {:error, errors}
68+
defp format_errors(errors) when is_list(errors) do
69+
if Keyword.keyword?(errors) do
70+
format_error(errors)
71+
else
72+
errors |> Enum.map(&format_error/1) |> Enum.join(", ")
3373
end
3474
end
75+
76+
defp format_error(kwlist) do
77+
{message, bindings} = Keyword.pop(kwlist, :message, "invalid value")
78+
Enum.reduce(bindings, message, fn {key, val}, msg ->
79+
String.replace(msg, "%{#{key}}", to_string(val))
80+
end)
81+
end
3582
end

test/instance_extension/verifier_test.exs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,105 @@ defmodule Diffo.InstanceExtension.VerifierTest do
3131
end
3232
)
3333
end
34+
35+
test "name not matching camelCase pattern warns DslError on compilation" do
36+
Util.assert_compile_time_warning(
37+
Spark.Error.DslError,
38+
"specification: name",
39+
fn ->
40+
defmodule InvalidSpecName do
41+
alias Diffo.Provider.BaseInstance
42+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
43+
44+
resource do
45+
description "resource with non-camelCase specification name"
46+
end
47+
48+
structure do
49+
specification do
50+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
51+
name "not camel case"
52+
end
53+
end
54+
end
55+
end
56+
)
57+
end
58+
59+
test "type not in allowed set warns DslError on compilation" do
60+
Util.assert_compile_time_warning(
61+
Spark.Error.DslError,
62+
"specification: type",
63+
fn ->
64+
defmodule InvalidSpecType do
65+
alias Diffo.Provider.BaseInstance
66+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
67+
68+
resource do
69+
description "resource with invalid specification type"
70+
end
71+
72+
structure do
73+
specification do
74+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
75+
name "invalid"
76+
type :badType
77+
end
78+
end
79+
end
80+
end
81+
)
82+
end
83+
84+
test "negative major_version warns DslError on compilation" do
85+
Util.assert_compile_time_warning(
86+
Spark.Error.DslError,
87+
"specification: major_version",
88+
fn ->
89+
defmodule InvalidSpecMajorVersion do
90+
alias Diffo.Provider.BaseInstance
91+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
92+
93+
resource do
94+
description "resource with negative major_version"
95+
end
96+
97+
structure do
98+
specification do
99+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
100+
name "invalid"
101+
major_version -1
102+
end
103+
end
104+
end
105+
end
106+
)
107+
end
108+
109+
test "tmf_version below minimum warns DslError on compilation" do
110+
Util.assert_compile_time_warning(
111+
Spark.Error.DslError,
112+
"specification: tmf_version",
113+
fn ->
114+
defmodule InvalidSpecTmfVersion do
115+
alias Diffo.Provider.BaseInstance
116+
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo
117+
118+
resource do
119+
description "resource with tmf_version below minimum"
120+
end
121+
122+
structure do
123+
specification do
124+
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
125+
name "invalid"
126+
tmf_version 0
127+
end
128+
end
129+
end
130+
end
131+
)
132+
end
34133
end
35134

36135
describe "characteristics verifier" do

test/type/value_test.exs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ defmodule Diffo.Type.ValueTest do
3333
Ash.Type.cast_input(Value, value, Value.subtype_constraints())
3434
end
3535

36-
@tag bugged: "raw Dynamic struct cast_input requires Value wrapper"
37-
@tag :skip
3836
test "cast_input dynamic" do
3937
value = %Dynamic{type: Patch, value: %Patch{aEnd: 1, zEnd: 42}}
4038

0 commit comments

Comments
 (0)