Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/diffo/provider/components/instance/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ defmodule Diffo.Provider.Instance.Extension do
doc: "The major_version of the specification.",
default: 1
],
minor_version: [
type: :integer,
doc: "The minor_version of the specification."
],
patch_version: [
type: :integer,
doc: "The patch_version of the specification."
],
tmf_version: [
type: :integer,
doc: "The TMF API version of the specification, e.g. 4."
],
description: [
type: :string,
doc: "A generic description of the specified service or resource."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ defmodule Diffo.Provider.Instance.Extension.Persisters.PersistSpecification do
name: Transformer.get_option(dsl_state, [:structure, :specification], :name),
type: Transformer.get_option(dsl_state, [:structure, :specification], :type, :serviceSpecification),
major_version: Transformer.get_option(dsl_state, [:structure, :specification], :major_version, 1),
minor_version: Transformer.get_option(dsl_state, [:structure, :specification], :minor_version),
patch_version: Transformer.get_option(dsl_state, [:structure, :specification], :patch_version),
tmf_version: Transformer.get_option(dsl_state, [:structure, :specification], :tmf_version),
description: Transformer.get_option(dsl_state, [:structure, :specification], :description),
category: Transformer.get_option(dsl_state, [:structure, :specification], :category)
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule Diffo.Provider.Instance.Specification do
@doc """
Struct for a Specification
"""
defstruct [:id, :name, :type, :major_version, :description, :category]
defstruct [:id, :name, :type, :major_version, :minor_version, :patch_version, :tmf_version, :description, :category]

@doc """
Sets the specified_by argument in the changeset, ensuring the Extended Instance's specification exists
Expand All @@ -21,7 +21,9 @@ defmodule Diffo.Provider.Instance.Specification do
when is_struct(changeset, Ash.Changeset) and is_list(options) do
specification = struct(__MODULE__, options)

case Provider.create_specification(Map.from_struct(specification)) do
attrs = specification |> Map.from_struct() |> Map.reject(fn {_, v} -> is_nil(v) end)

case Provider.create_specification(attrs) do
{:ok, _} ->
Ash.Changeset.force_set_argument(changeset, :specified_by, specification.id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,80 @@
# SPDX-License-Identifier: MIT

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

alias Spark.Dsl.Verifier
alias Spark.Error.DslError

# Fields validated against Specification attribute constraints (id handled separately)
@spec_fields [:name, :type, :major_version, :minor_version, :patch_version, :tmf_version, :description, :category]

@impl true
def verify(dsl_state) do
resource = Verifier.get_persisted(dsl_state, :module)

errors = check_id(dsl_state, resource) ++ check_attributes(dsl_state, resource)

case errors do
[] -> :ok
errors -> {:error, errors}
end
end

defp check_id(dsl_state, resource) do
spec_id = Verifier.get_option(dsl_state, [:structure, :specification], :id)

errors =
if spec_id && !Diffo.Uuid.uuid4?(spec_id) do
[
DslError.exception(
module: resource,
path: [:structure, :specification, :id],
message: "specification: id must be a valid UUID4"
)
]
if spec_id && !Diffo.Uuid.uuid4?(spec_id) do
[DslError.exception(
module: resource,
path: [:structure, :specification, :id],
message: "specification: id must be a valid UUID4"
)]
else
[]
end
end

defp check_attributes(dsl_state, resource) do
spec_attrs =
Ash.Resource.Info.attributes(Diffo.Provider.Specification)
|> Map.new(&{&1.name, &1})

Enum.flat_map(@spec_fields, fn field ->
value = Verifier.get_option(dsl_state, [:structure, :specification], field)
attr = Map.get(spec_attrs, field)

if not is_nil(value) && not is_nil(attr) do
case Ash.Type.apply_constraints(attr.type, value, attr.constraints) do
{:ok, _} ->
[]

{:error, errors} ->
[DslError.exception(
module: resource,
path: [:structure, :specification, field],
message: "specification: #{field} - #{format_errors(errors)}"
)]
end
else
[]
end
end)
end

case errors do
[] -> :ok
errors -> {:error, errors}
defp format_errors(errors) when is_list(errors) do
if Keyword.keyword?(errors) do
format_error(errors)
else
errors |> Enum.map(&format_error/1) |> Enum.join(", ")
end
end

defp format_error(kwlist) do
{message, bindings} = Keyword.pop(kwlist, :message, "invalid value")
Enum.reduce(bindings, message, fn {key, val}, msg ->
String.replace(msg, "%{#{key}}", to_string(val))
end)
end
end
2 changes: 1 addition & 1 deletion lib/diffo/provider/components/specification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defmodule Diffo.Provider.Specification do

create :create do
description "creates a major version of a named serviceSpecification or resourceSpecification"
accept [:id, :type, :name, :major_version, :description, :category]
accept [:id, :type, :name, :major_version, :minor_version, :patch_version, :tmf_version, :description, :category]
change load [:version, :href, :instance_type]
upsert? true
upsert_identity :unique_major_version_per_name
Expand Down
2 changes: 1 addition & 1 deletion test/instance_extension/party_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Diffo.InstanceExtension.PartyTest do
alias Diffo.Provider.Party.Extension.Info, as: PartyInfo
alias Diffo.Test.Organization
alias Diffo.Test.Person
alias Diffo.Test.Carrier

alias Diffo.Test.Shelf
alias Diffo.Test.Nbn
alias Diffo.Test.Servo
Expand Down
4 changes: 2 additions & 2 deletions test/instance_extension/place_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Diffo.InstanceExtension.PlaceTest do
alias Diffo.Provider.Place.Extension.Info, as: PlaceInfo
alias Diffo.Test.Organization
alias Diffo.Test.GeographicSite
alias Diffo.Test.ExchangeBuilding

alias Diffo.Test.Shelf
alias Diffo.Test.Nbn

Expand Down Expand Up @@ -108,7 +108,7 @@ defmodule Diffo.InstanceExtension.PlaceTest do
end

test "domain-specific attributes are readable after creation" do
{:ok, building} = Nbn.create_exchange_building(%{
{:ok, _building} = Nbn.create_exchange_building(%{
id: "EX-MEL-002",
name: "South Yarra Exchange",
nli: "MEXMELB0002",
Expand Down
21 changes: 21 additions & 0 deletions test/instance_extension/specification_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,26 @@ defmodule Diffo.InstanceExtension.SpecificationTest do
{:ok, specification} = Diffo.Provider.get_specification_by_id(spec_id)
assert specification.description == description
end

test "minor_version declared in specification DSL roundtrips to the persisted specification" do
Servo.build_shelf(%{name: "s"})

{:ok, specification} = Diffo.Provider.get_specification_by_id(Shelf.specification()[:id])
assert specification.minor_version == Shelf.specification()[:minor_version]
end

test "patch_version declared in specification DSL roundtrips to the persisted specification" do
Servo.build_shelf(%{name: "s"})

{:ok, specification} = Diffo.Provider.get_specification_by_id(Shelf.specification()[:id])
assert specification.patch_version == Shelf.specification()[:patch_version]
end

test "tmf_version declared in specification DSL roundtrips to the persisted specification" do
Servo.build_shelf(%{name: "s"})

{:ok, specification} = Diffo.Provider.get_specification_by_id(Shelf.specification()[:id])
assert specification.tmf_version == Shelf.specification()[:tmf_version]
end
end
end
99 changes: 99 additions & 0 deletions test/instance_extension/verifier_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,105 @@ defmodule Diffo.InstanceExtension.VerifierTest do
end
)
end

test "name not matching camelCase pattern warns DslError on compilation" do
Util.assert_compile_time_warning(
Spark.Error.DslError,
"specification: name",
fn ->
defmodule InvalidSpecName do
alias Diffo.Provider.BaseInstance
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo

resource do
description "resource with non-camelCase specification name"
end

structure do
specification do
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
name "not camel case"
end
end
end
end
)
end

test "type not in allowed set warns DslError on compilation" do
Util.assert_compile_time_warning(
Spark.Error.DslError,
"specification: type",
fn ->
defmodule InvalidSpecType do
alias Diffo.Provider.BaseInstance
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo

resource do
description "resource with invalid specification type"
end

structure do
specification do
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
name "invalid"
type :badType
end
end
end
end
)
end

test "negative major_version warns DslError on compilation" do
Util.assert_compile_time_warning(
Spark.Error.DslError,
"specification: major_version",
fn ->
defmodule InvalidSpecMajorVersion do
alias Diffo.Provider.BaseInstance
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo

resource do
description "resource with negative major_version"
end

structure do
specification do
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
name "invalid"
major_version -1
end
end
end
end
)
end

test "tmf_version below minimum warns DslError on compilation" do
Util.assert_compile_time_warning(
Spark.Error.DslError,
"specification: tmf_version",
fn ->
defmodule InvalidSpecTmfVersion do
alias Diffo.Provider.BaseInstance
use Ash.Resource, fragments: [BaseInstance], domain: Diffo.Test.Servo

resource do
description "resource with tmf_version below minimum"
end

structure do
specification do
id "cd29956f-6c68-44cc-bf54-705eb8d2f754"
name "invalid"
tmf_version 0
end
end
end
end
)
end
end

describe "characteristics verifier" do
Expand Down
4 changes: 4 additions & 0 deletions test/support/resource/shelf.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ defmodule Diffo.Test.Shelf do
id "ef016d85-9dbd-429c-84da-1df56cc7dda5"
name "shelf"
type :resourceSpecification
major_version 1
minor_version 2
patch_version 3
tmf_version 4
description "A Shelf Resource Instance which contain cards"
category "Network Resource"
end
Expand Down
2 changes: 0 additions & 2 deletions test/type/value_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ defmodule Diffo.Type.ValueTest do
Ash.Type.cast_input(Value, value, Value.subtype_constraints())
end

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

Expand Down
Loading