From d5aad7cb34e4507d540a5191bbd5b4a095c53cf1 Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Thu, 19 Mar 2026 23:22:08 +1030 Subject: [PATCH 1/2] improved error handling --- .../DSL-Diffo.Provider.Instance.Extension.md | 4 +- lib/diffo/helpers/uuid.ex | 2 +- .../instance/extension/characteristic.ex | 21 ++++-- .../components/instance/extension/feature.ex | 52 +++++++++----- .../instance/extension/specification.ex | 4 +- .../assigner_test.exs | 2 +- .../characteristic_test.exs | 29 ++++++++ test/instance_extension/feature_test.exs | 29 ++++++++ .../instance_extension/specification_test.exs | 27 ++++++++ test/provider/instance_test.exs | 2 +- .../resource/deployment_class_value.ex | 32 +++++++++ .../invalid/invalid_characteristic.ex | 66 ++++++++++++++++++ .../invalid/invalid_feature_characteristic.ex | 69 +++++++++++++++++++ .../resource/invalid/invalid_specification.ex | 62 +++++++++++++++++ test/support/resource/shelf.ex | 8 +++ test/support/servo.ex | 18 +++++ 16 files changed, 395 insertions(+), 32 deletions(-) rename test/{assigner => instance_extension}/assigner_test.exs (99%) create mode 100644 test/instance_extension/characteristic_test.exs create mode 100644 test/instance_extension/feature_test.exs create mode 100644 test/instance_extension/specification_test.exs create mode 100644 test/support/resource/deployment_class_value.ex create mode 100644 test/support/resource/invalid/invalid_characteristic.ex create mode 100644 test/support/resource/invalid/invalid_feature_characteristic.ex create mode 100644 test/support/resource/invalid/invalid_specification.ex diff --git a/documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md b/documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md index 3a7a303..a42a05b 100644 --- a/documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md +++ b/documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md @@ -56,9 +56,7 @@ Configuration for Instance Features features do feature :dynamic_line_management do is_enabled? true - characteristics do - characteristic :constraints, Diffo.Access.Constraints - end + characteristic :constraints, Diffo.Access.Constraints end end diff --git a/lib/diffo/helpers/uuid.ex b/lib/diffo/helpers/uuid.ex index 1dbe288..c7b9ae9 100644 --- a/lib/diffo/helpers/uuid.ex +++ b/lib/diffo/helpers/uuid.ex @@ -63,7 +63,7 @@ defmodule Diffo.Uuid do case info do {:ok, result} -> - if result[:version] == 4 do + if result[:version] == 4 and result[:variant] == :rfc4122 do true else false diff --git a/lib/diffo/provider/components/instance/extension/characteristic.ex b/lib/diffo/provider/components/instance/extension/characteristic.ex index fd93018..effc494 100644 --- a/lib/diffo/provider/components/instance/extension/characteristic.ex +++ b/lib/diffo/provider/components/instance/extension/characteristic.ex @@ -30,6 +30,9 @@ defmodule Diffo.Provider.Instance.Characteristic do [] -> changeset + {:error, error} -> + Ash.Changeset.add_error(changeset, error) + _ -> characteristic_ids = Enum.map(characteristics, &Map.get(&1, :id)) Ash.Changeset.force_set_argument(changeset, :characteristics, characteristic_ids) @@ -43,14 +46,20 @@ defmodule Diffo.Provider.Instance.Characteristic do characteristics = Info.characteristics(module) Enum.reduce_while(characteristics, [], fn %{name: name, value_type: value_type}, acc -> - value = struct(value_type) + try do + value = struct(value_type) - case Provider.create_characteristic(%{name: name, type: type, value: value}) do - {:ok, result} -> - {:cont, [result | acc]} + case Provider.create_characteristic(%{name: name, type: type, value: value}) do + {:ok, result} -> + {:cont, [result | acc]} - {:error, _error} -> - {:halt, []} + {:error, error} -> + {:halt, {:error, error}} + end + rescue + _e in UndefinedFunctionError -> + {:halt, + {:error, "couldn't create characteristic with value of unknown type #{value_type}"}} end end) end diff --git a/lib/diffo/provider/components/instance/extension/feature.ex b/lib/diffo/provider/components/instance/extension/feature.ex index f8d8a9f..779febc 100644 --- a/lib/diffo/provider/components/instance/extension/feature.ex +++ b/lib/diffo/provider/components/instance/extension/feature.ex @@ -30,6 +30,9 @@ defmodule Diffo.Provider.Instance.Feature do [] -> changeset + {:error, error} -> + Ash.Changeset.add_error(changeset, error) + _ -> feature_ids = Enum.map(features, &Map.get(&1, :id)) Ash.Changeset.force_set_argument(changeset, :features, feature_ids) @@ -49,28 +52,41 @@ defmodule Diffo.Provider.Instance.Feature do fn %{name: name, is_enabled?: isEnabled, characteristics: characteristics}, acc -> characteristic_ids = Enum.reduce_while(characteristics, [], fn %{name: name, value_type: value_type}, acc -> - value = struct(value_type) + try do + value = struct(value_type) + + case Provider.create_characteristic(%{name: name, value: value, type: :feature}) do + {:ok, result} -> + {:cont, [result.id | acc]} + + {:error, error} -> + {:halt, {:error, error}} + end + rescue + _e in UndefinedFunctionError -> + {:halt, + {:error, + "couldn't create feature characteristic with value of unknown type #{value_type}"}} + end + end) - case Provider.create_characteristic(%{name: name, value: value, type: :feature}) do + case characteristic_ids do + {:error, error} -> + {:halt, {:error, error}} + + _ -> + # create feature with feature characteristics + case Provider.create_feature(%{ + name: name, + isEnabled: isEnabled, + characteristics: characteristic_ids + }) do {:ok, result} -> - {:cont, [result.id | acc]} + {:cont, [result | acc]} - {:error, _error} -> - {:halt, []} + {:error, error} -> + {:halt, {:error, error}} end - end) - - # create feature with feature characteristics - case Provider.create_feature(%{ - name: name, - isEnabled: isEnabled, - characteristics: characteristic_ids - }) do - {:ok, result} -> - {:cont, [result | acc]} - - {:error, _error} -> - {:halt, []} end end ) diff --git a/lib/diffo/provider/components/instance/extension/specification.ex b/lib/diffo/provider/components/instance/extension/specification.ex index 0abec37..9469784 100644 --- a/lib/diffo/provider/components/instance/extension/specification.ex +++ b/lib/diffo/provider/components/instance/extension/specification.ex @@ -29,8 +29,8 @@ defmodule Diffo.Provider.Instance.Specification do {:ok, specification} -> Ash.Changeset.force_set_argument(changeset, :specified_by, specification.id) - {:error, _error} -> - Logger.error("couldn't find/create required specification") + {:error, error} -> + Ash.Changeset.add_error(changeset, error) end end diff --git a/test/assigner/assigner_test.exs b/test/instance_extension/assigner_test.exs similarity index 99% rename from test/assigner/assigner_test.exs rename to test/instance_extension/assigner_test.exs index 4ac163c..867fb19 100644 --- a/test/assigner/assigner_test.exs +++ b/test/instance_extension/assigner_test.exs @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -defmodule Diffo.Test.AssignerTest do +defmodule Diffo.InstanceExtension.AssignerTest do @moduledoc false use ExUnit.Case alias Diffo.Provider.Specification diff --git a/test/instance_extension/characteristic_test.exs b/test/instance_extension/characteristic_test.exs new file mode 100644 index 0000000..d563191 --- /dev/null +++ b/test/instance_extension/characteristic_test.exs @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.InstanceExtension.CharacteristicTest do + @moduledoc false + use ExUnit.Case + alias Diffo.Test.Servo + + setup_all do + AshNeo4j.BoltyHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) + end + + describe "characteristic" do + test "create resource fails when characteristic value type invalid" do + {:error, error} = Servo.build_invalid_characteristic(%{}) + %Ash.Error.Invalid{errors: errors} = error + + assert hd(errors).message == + "couldn't create characteristic with value of unknown type Elixir.InvalidValue" + end + end +end diff --git a/test/instance_extension/feature_test.exs b/test/instance_extension/feature_test.exs new file mode 100644 index 0000000..4b6cf81 --- /dev/null +++ b/test/instance_extension/feature_test.exs @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.InstanceExtension.FeatureTest do + @moduledoc false + use ExUnit.Case + alias Diffo.Test.Servo + + setup_all do + AshNeo4j.BoltyHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) + end + + describe "feature" do + test "create resource with fails when feature characteristic value type invalid" do + {:error, error} = Servo.build_invalid_feature_characteristic(%{}) + %Ash.Error.Invalid{errors: errors} = error + + assert hd(errors).message == + "couldn't create feature characteristic with value of unknown type Elixir.InvalidValue" + end + end +end diff --git a/test/instance_extension/specification_test.exs b/test/instance_extension/specification_test.exs new file mode 100644 index 0000000..9cba730 --- /dev/null +++ b/test/instance_extension/specification_test.exs @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.InstanceExtension.SpecificationTest do + @moduledoc false + use ExUnit.Case + alias Diffo.Test.Servo + + setup_all do + AshNeo4j.BoltyHelper.start() + end + + setup do + on_exit(fn -> + AshNeo4j.Neo4jHelper.delete_all() + end) + end + + describe "specification" do + test "create resource fails when specification id not uuid v4" do + {:error, error} = Servo.build_invalid_specification(%{}) + %Ash.Error.Invalid{errors: errors} = error + assert hd(errors).message == "must be a uuid v4 or nil" + end + end +end diff --git a/test/provider/instance_test.exs b/test/provider/instance_test.exs index 2e92c93..178b593 100644 --- a/test/provider/instance_test.exs +++ b/test/provider/instance_test.exs @@ -12,7 +12,7 @@ defmodule Diffo.Provider.InstanceTest do end setup do - on_exit(fn -> + on_exit(fn -> AshNeo4j.Neo4jHelper.delete_all() end) end diff --git a/test/support/resource/deployment_class_value.ex b/test/support/resource/deployment_class_value.ex new file mode 100644 index 0000000..c82933e --- /dev/null +++ b/test/support/resource/deployment_class_value.ex @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Test.DeploymentClassValue do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + DeploymentClassValue - AshTyped Struct for DeploymentClass Feature Characteristic Value + """ + use Ash.TypedStruct, extensions: [AshJason.TypedStruct, AshOutstanding.TypedStruct] + + jason do + pick [:class, :mask] + compact true + end + + outstanding do + expect [:name] + end + + typed_struct do + field :class, :string, description: "the deployment class" + field :mask, :string, description: "the mask name" + end + + defimpl String.Chars do + def to_string(struct) do + inspect(struct) + end + end +end diff --git a/test/support/resource/invalid/invalid_characteristic.ex b/test/support/resource/invalid/invalid_characteristic.ex new file mode 100644 index 0000000..52b0ef6 --- /dev/null +++ b/test/support/resource/invalid/invalid_characteristic.ex @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Test.InvalidCharacteristic do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + InvalidCharacteristic - Resource Instance with an Invalid Characteristic + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.ActionHelper + + alias Diffo.Test.Servo + + use Ash.Resource, + fragments: [BaseInstance], + domain: Servo + + resource do + description "Ash Resource with an invalid characteristic" + end + + specification do + id "3caf29b9-0b91-4b8f-8568-2960131b1feb" + name "invalidCharacteristic" + type :resourceSpecification + category "Network Resource" + end + + characteristics do + characteristic :invalid, InvalidValue + end + + actions do + create :build do + description "creates a new InvalidCharacteristic resource instance for build" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> + ActionHelper.build_before(changeset) + end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after( + changeset, + result, + Servo, + :get_invalid_characteristic_by_id + ) + end) + + change load [:href] + upsert? false + end + end +end diff --git a/test/support/resource/invalid/invalid_feature_characteristic.ex b/test/support/resource/invalid/invalid_feature_characteristic.ex new file mode 100644 index 0000000..2d8f791 --- /dev/null +++ b/test/support/resource/invalid/invalid_feature_characteristic.ex @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Test.InvalidFeatureCharacteristic do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + InvalidFeatureCharacteristic - Resource Instance with an Invalid Feature Characteristic + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.ActionHelper + + alias Diffo.Test.Servo + + use Ash.Resource, + fragments: [BaseInstance], + domain: Servo + + resource do + description "Ash Resource with an invalid feature characteristic" + end + + specification do + id "1f2402ca-82da-428e-a58b-5405a5431386" + name "invalidFeatureCharacteristic" + type :resourceSpecification + category "Network Resource" + end + + features do + feature :invalid_feature_characteristic do + is_enabled? true + characteristic :invalid, InvalidValue + end + end + + actions do + create :build do + description "creates a new InvalidFeatureCharacteristic resource instance for build" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> + ActionHelper.build_before(changeset) + end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after( + changeset, + result, + Servo, + :get_invalid_feature_characteristic_by_id + ) + end) + + change load [:href] + upsert? false + end + end +end diff --git a/test/support/resource/invalid/invalid_specification.ex b/test/support/resource/invalid/invalid_specification.ex new file mode 100644 index 0000000..4591912 --- /dev/null +++ b/test/support/resource/invalid/invalid_specification.ex @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 diffo contributors +# +# SPDX-License-Identifier: MIT + +defmodule Diffo.Test.InvalidSpecification do + @moduledoc """ + Diffo - TMF Service and Resource Management with a difference + + InvalidSpecification - Resource Instance with an Invalid Specification + """ + + alias Diffo.Provider.BaseInstance + alias Diffo.Provider.Instance.ActionHelper + + alias Diffo.Test.Servo + + use Ash.Resource, + fragments: [BaseInstance], + domain: Servo + + resource do + description "Ash Resource with an invalid specification" + end + + specification do + id "ef016d85-9dbd-429c-04da-1df56cc7dda5" + name "invalidSpecification" + type :resourceSpecification + category "Network Resource" + end + + actions do + create :build do + description "creates a new InvalidSpecification resource instance for build" + accept [:id, :name, :type, :which] + argument :specified_by, :uuid, public?: false + argument :relationships, {:array, :struct} + argument :features, {:array, :uuid}, public?: false + argument :characteristics, {:array, :uuid}, public?: false + argument :places, {:array, :struct} + argument :parties, {:array, :struct} + + change set_attribute(:type, :resource) + + change before_action(fn changeset, _context -> + ActionHelper.build_before(changeset) + end) + + change after_action(fn changeset, result, _context -> + ActionHelper.build_after( + changeset, + result, + Servo, + :get_invalid_specification_by_id + ) + end) + + change load [:href] + upsert? false + end + end +end diff --git a/test/support/resource/shelf.ex b/test/support/resource/shelf.ex index 8a48617..6740157 100644 --- a/test/support/resource/shelf.ex +++ b/test/support/resource/shelf.ex @@ -19,6 +19,7 @@ defmodule Diffo.Test.Shelf do alias Diffo.Test.Servo alias Diffo.Test.ShelfValue + alias Diffo.Test.DeploymentClassValue use Ash.Resource, fragments: [BaseInstance], @@ -37,6 +38,13 @@ defmodule Diffo.Test.Shelf do category "Network Resource" end + features do + feature :spectralManagement do + is_enabled? true + characteristic :deploymentClass, DeploymentClassValue + end + end + characteristics do characteristic :shelf, ShelfValue characteristic :slots, AssignableValue diff --git a/test/support/servo.ex b/test/support/servo.ex index 793df85..652e03b 100644 --- a/test/support/servo.ex +++ b/test/support/servo.ex @@ -14,6 +14,9 @@ defmodule Diffo.Test.Servo do alias Diffo.Test.Shelf alias Diffo.Test.Card + alias Diffo.Test.InvalidSpecification + alias Diffo.Test.InvalidCharacteristic + alias Diffo.Test.InvalidFeatureCharacteristic domain do description "service and resource management" @@ -35,5 +38,20 @@ defmodule Diffo.Test.Servo do define :relate_card, action: :relate define :assign_port, action: :assign_port end + + resource InvalidSpecification do + define :get_invalid_specification_by_id, action: :read, get_by: :id + define :build_invalid_specification, action: :build + end + + resource InvalidCharacteristic do + define :get_invalid_characteristic_by_id, action: :read, get_by: :id + define :build_invalid_characteristic, action: :build + end + + resource InvalidFeatureCharacteristic do + define :get_invalid_feature_characteristic_by_id, action: :read, get_by: :id + define :build_invalid_feature_characteristic, action: :build + end end end From ca2a4fe813ea30d237c3d357d4a33df185b696ca Mon Sep 17 00:00:00 2001 From: Matt Beanland Date: Thu, 19 Mar 2026 23:26:08 +1030 Subject: [PATCH 2/2] 0.1.6 release --- CHANGELOG.md | 12 +++++++++++- README.md | 2 +- diffo.livemd | 2 +- .../use_diffo_provider_instance_extension.livemd | 2 +- mix.exs | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f4f2c9..6b0f4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,4 +57,14 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline ### Fixes -* fixed relationship enrichment inconsistent across neo4j versions \ No newline at end of file +* fixed relationship enrichment inconsistent across neo4j versions + +## [v0.1.6](https://github.com/diffo-dev/diffo/compare/v0.1.5...v0.1.6) (2026-03-19) + +### Fixes + +* incorrect domain label + +### Maintenance + +* improved error handling \ No newline at end of file diff --git a/README.md b/README.md index 7714fc7..0a7f57b 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ by adding `diffo` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:diffo, "~> 0.1.5"} + {:diffo, "~> 0.1.6"} ] end ``` diff --git a/diffo.livemd b/diffo.livemd index 2c211fc..bea2be2 100644 --- a/diffo.livemd +++ b/diffo.livemd @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT ```elixir Mix.install( [ - {:diffo, "~> 0.1.5"} + {:diffo, "~> 0.1.6"} ], consolidate_protocols: false ) diff --git a/documentation/how_to/use_diffo_provider_instance_extension.livemd b/documentation/how_to/use_diffo_provider_instance_extension.livemd index 284eb64..b47e0d2 100644 --- a/documentation/how_to/use_diffo_provider_instance_extension.livemd +++ b/documentation/how_to/use_diffo_provider_instance_extension.livemd @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT ```elixir Mix.install( [ - {:diffo, "~> 0.1.5"} + {:diffo, "~> 0.1.6"} ], consolidate_protocols: false ) diff --git a/mix.exs b/mix.exs index ce2b0ab..9a614b8 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule Diffo.MixProject do @moduledoc false use Mix.Project - @version "0.1.5" + @version "0.1.6" @name "Diffo" @description "TMF Service and Resource Manager with a difference" @github_url "https://github.com/diffo-dev/diffo"