From 3a96529a217a94a48d77f1b1c1d1ec9a4b1803ad Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 26 Oct 2025 09:26:09 -0400 Subject: [PATCH] improve strongly typed validations for Work Set and other Work Data --- mocha-common | 2 +- .../lib/Mocha.Core/AttributeImplementation.cs | 6 + .../BooleanAttributeImplementation.cs | 5 + .../DateAttributeImplementation.cs | 5 + .../NumericAttributeImplementation.cs | 5 + .../TextAttributeImplementation.cs | 5 + .../Mocha.Core/ClassImplementationRegistry.cs | 13 ++ .../src/lib/Mocha.Core/KnownAttributeGuids.cs | 5 + mocha-dotnet/src/lib/Mocha.Core/OmsContext.cs | 132 ++++++++++-------- .../tests/Mocha.Core.Tests/WorkSetTests.cs | 82 +++++++++++ 10 files changed, 204 insertions(+), 56 deletions(-) diff --git a/mocha-common b/mocha-common index 8bea362..8638fc4 160000 --- a/mocha-common +++ b/mocha-common @@ -1 +1 @@ -Subproject commit 8bea362b3ca08a642ba196b8670e8e44e5e799e3 +Subproject commit 8638fc4c3a77de9b6fad3a9a17911995300e71c3 diff --git a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementation.cs b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementation.cs index 03ff9b2..bbde3ec 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementation.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementation.cs @@ -52,4 +52,10 @@ public abstract class AttributeImplementation : ClassImplementation { ExecuteBuildElementInternal(oms, targetInstance, elementContent, elementContentInstance, objCell); } + + protected abstract bool IsValidValueInternal(object value); + public bool IsValidValue(object value) + { + return IsValidValueInternal(value); + } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/BooleanAttributeImplementation.cs b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/BooleanAttributeImplementation.cs index 8e6c83d..a81025b 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/BooleanAttributeImplementation.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/BooleanAttributeImplementation.cs @@ -54,4 +54,9 @@ public class BooleanAttributeImplementation : AttributeImplementation objCell.Add("value", oms.GetAttributeValue(targetInstance, elementContentInstance)); objCell.Add("text", oms.GetAttributeValue(targetInstance, elementContentInstance) ? "Yes" : "No"); } + + protected override bool IsValidValueInternal(object value) + { + return value is bool; + } } \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/DateAttributeImplementation.cs b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/DateAttributeImplementation.cs index a78ac87..79fd89c 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/DateAttributeImplementation.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/DateAttributeImplementation.cs @@ -61,4 +61,9 @@ public class DateAttributeImplementation : AttributeImplementation objCell.Add("text", dt.ToString()); objCell.Add("dateTimePrecision", "DAY"); } + + protected override bool IsValidValueInternal(object value) + { + return value is DateTime; + } } \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/NumericAttributeImplementation.cs b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/NumericAttributeImplementation.cs index 7bc13e1..49d9628 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/NumericAttributeImplementation.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/NumericAttributeImplementation.cs @@ -55,4 +55,9 @@ public class NumericAttributeImplementation : AttributeImplementation objCell.Add("precision", 6); objCell.Add("format", "#0.######"); } + + protected override bool IsValidValueInternal(object value) + { + return value is decimal; + } } \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/TextAttributeImplementation.cs b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/TextAttributeImplementation.cs index c8324b5..b26e9e4 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/TextAttributeImplementation.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/AttributeImplementations/TextAttributeImplementation.cs @@ -77,4 +77,9 @@ public class TextAttributeImplementation : AttributeImplementation objCell.Add("value", oms.GetAttributeValue(targetInstance, elementContentInstance)); } } + + protected override bool IsValidValueInternal(object value) + { + return value is string; + } } \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/ClassImplementationRegistry.cs b/mocha-dotnet/src/lib/Mocha.Core/ClassImplementationRegistry.cs index af97e28..44e3d2a 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/ClassImplementationRegistry.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/ClassImplementationRegistry.cs @@ -68,4 +68,17 @@ public class ClassImplementationRegistry implementation = null; return false; } + + public IEnumerable GetAll() where T : ClassImplementation + { + List list = new List(); + foreach (KeyValuePair kvp in implementations) + { + if (kvp.Value is T) + { + list.Add((T)kvp.Value); + } + } + return list; + } } \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs index 180a6ca..70f3eec 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/KnownAttributeGuids.cs @@ -66,6 +66,7 @@ namespace Mocha.Core public static Guid ValidateOnlyOnSubmit { get; } = new Guid("{400fcd8e-823b-4f4a-aa38-b444f763259b}"); public static Guid UserIsLoggedIn { get; } = new Guid("{8e93d9f3-a897-4c97-935c-b3427f90633b}"); public static Guid Derived { get; } = new Guid("{66991ca1-ef08-4f30-846c-4984c2a3139d}"); + public static Guid IncludeSubclasses { get; } = new Guid("{bc95f90e-d373-48be-b71e-283695afed5a}"); } public static class Numeric { @@ -87,5 +88,9 @@ namespace Mocha.Core public static Guid SecondaryOperandValue { get; } = new Guid("{253a04bb-feb7-474d-b608-43219a753ef8}"); // {e666aa75-6b53-47ea-9afa-369378e17b5d} } + public static class Date + { + public static Guid DateAndTime { get; } = new Guid("{ea71cc92-a5e9-49c1-b487-8ad178b557d2}"); + } } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/OmsContext.cs b/mocha-dotnet/src/lib/Mocha.Core/OmsContext.cs index ef9076d..b8f4c32 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/OmsContext.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/OmsContext.cs @@ -137,6 +137,72 @@ public class OmsContext return defaultValue; } + private void ValidateSubclassConstraints(InstanceHandle parm, System.Collections.IEnumerable en) + { + bool singular = false; + if (!Oms.TryGetAttributeValue(parm, Oms.GetInstance(KnownAttributeGuids.Boolean.Singular), out singular)) + { + singular = false; + } + bool includeSubclasses = false; + if (!Oms.TryGetAttributeValue(parm, Oms.GetInstance(KnownAttributeGuids.Boolean.IncludeSubclasses), out includeSubclasses)) + { + includeSubclasses = false; + } + + int ct = 0; + foreach (object o in en) + { + ct++; + } + + if (singular && ct > 1) + { + throw new InvalidOperationException("cannot assign multiple instances to a singular Work Set"); + } + + foreach (object o in en) + { + if (o is IInstanceReference ir) + { + IReadOnlyCollection irs = Oms.GetRelatedInstances(parm, Oms.GetInstance(KnownRelationshipGuids.Work_Set__has_valid__Class)); + if (irs.Count > 0) + { + if (includeSubclasses) + { + foreach (InstanceHandle possibleParentClass in irs) + { + if (!Oms.IsInstanceOf(ir, possibleParentClass)) + { + throw new ArgumentException("instance reference must be an instance of appropriate class"); + } + } + } + else + { + InstanceHandle parentClass = Oms.GetParentClass(ir); + if (!irs.Contains(parentClass)) + { + throw new ArgumentException("instance reference must be an instance of appropriate class"); + } + } + } + } + } + } + private void ValidateAttributeConstraints(InstanceHandle parm, object value) + { + Guid parentClassGuid = Oms.GetGlobalIdentifier(Oms.GetParentClass(parm)); + IEnumerable attrs = Oms.ClassImplementations.GetAll(); + foreach (AttributeImplementation impl in attrs) + { + if (impl.ClassGuid == parentClassGuid && !impl.IsValidValue(value)) + { + throw new ArgumentException(String.Format("value {0} cannot be converted to a `{1}`", value is string ? "\"" + value + "\"" : value, Oms.GetInstanceText(Oms.GetParentClass(parm))), nameof(value)); + } + } + } + public void SetWorkData(IInstanceReference parm, object? value) { SetWorkData(parm.GetHandle(), value); @@ -147,75 +213,31 @@ public class OmsContext { if (Oms.IsInstanceOf(parm, Oms.GetInstance(KnownInstanceGuids.Classes.WorkSet))) { - bool singular = false; - if (!Oms.TryGetAttributeValue(parm, Oms.GetInstance(KnownAttributeGuids.Boolean.Singular), out singular)) - { - singular = false; - } - if (value is IInstanceReference) { - IReadOnlyCollection irs = Oms.GetRelatedInstances(parm, Oms.GetInstance(KnownRelationshipGuids.Work_Set__has_valid__Class)); - if (irs.Count > 0) - { - InstanceHandle ir = ((IInstanceReference)value).GetHandle(); - InstanceHandle parentClass = Oms.GetParentClass(ir); - if (!irs.Contains(parentClass)) - { - throw new ArgumentException("instance reference must be an instance of appropriate class"); - } - } + ValidateSubclassConstraints(parm, new IInstanceReference[] { (IInstanceReference)value }); } else if (value is IEnumerable) { - IEnumerable insts = (IEnumerable)value; - if (singular && insts.Count() > 1) - { - throw new InvalidOperationException("Singular Work Set must only contain a single InstanceReference or be an array of InstanceReference that contains exactly zero or one item."); - } - IEnumerable irs = Oms.GetRelatedInstances(parm, Oms.GetInstance(KnownRelationshipGuids.Work_Set__has_valid__Class)); - if (irs.Count() > 0) - { - foreach (InstanceHandle ir in insts) - { - InstanceHandle parentClass = Oms.GetParentClass(ir); - if (!irs.Contains(parentClass)) - { - throw new ArgumentException("instance reference must be an instance of appropriate class"); - } - } - } - // value = ((IEnumerable)value).FirstOrDefault(); + ValidateSubclassConstraints(parm, (IEnumerable)value); } else if (value is IEnumerable) { - IEnumerable insts = (IEnumerable)value; - if (singular && insts.Count() > 1) - { - throw new InvalidOperationException("Singular Work Set must only contain a single InstanceReference or be an array of InstanceReference that contains exactly zero or one item."); - } - IEnumerable irs = Oms.GetRelatedInstances(parm, Oms.GetInstance(KnownRelationshipGuids.Work_Set__has_valid__Class)); - if (irs != null) - { - if (irs.Count() > 0) - { - foreach (InstanceWrapper iw in insts) - { - InstanceHandle ir = iw.GetHandle(); - InstanceHandle parentClass = Oms.GetParentClass(ir); - if (!irs.Contains(parentClass)) - { - throw new ArgumentException("instance reference must be an instance of appropriate class"); - } - } - } - } + ValidateSubclassConstraints(parm, (IEnumerable)value); + } + else if (value is IEnumerable) + { + ValidateSubclassConstraints(parm, (IEnumerable)value); } else { throw new ArgumentException(String.Format("cannot assign literal data '{0}' to a Work Set", value)); } } + else + { + ValidateAttributeConstraints(parm, value); + } } _WorkData[parm] = value; } diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/WorkSetTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/WorkSetTests.cs index 35f7a5a..510fa0a 100644 --- a/mocha-dotnet/tests/Mocha.Core.Tests/WorkSetTests.cs +++ b/mocha-dotnet/tests/Mocha.Core.Tests/WorkSetTests.cs @@ -59,6 +59,28 @@ public class WorkSetTests : OmsTestsBase }, Throws.ArgumentException); } + [Test] + public void SingleValidClassConstraintWithInheritance() + { + // we should be able to put a `Text Attribute` instance into this Work Set if its valid class is `Attribute` + InstanceHandle c_Attribute = Oms.GetInstance(KnownInstanceGuids.Classes.Attribute); + InstanceHandle c_TextAttribute = Oms.GetInstance(KnownInstanceGuids.Classes.TextAttribute); + + WorkSet workSet = Oms.CreateWorkSet("Dummy Work Set", true, new InstanceHandle[] + { + c_Attribute + }); + Oms.SetAttributeValue(workSet, Oms.GetInstance(KnownAttributeGuids.Boolean.IncludeSubclasses), true); + + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Text.Name); + + OmsContext context = Oms.CreateContext(); + Assert.That(delegate () + { + context.SetWorkData(workSet, a_Name); + }, Throws.Nothing); + } + [Test] public void MultipleValidClassConstraintSingular() { @@ -151,4 +173,64 @@ public class WorkSetTests : OmsTestsBase context.SetWorkData(workSet, true); }, Throws.ArgumentException); } + + [Test] + public void TextAttributeWorkData() + { + OmsContext context = Oms.CreateContext(); + Assert.That(delegate () + { + // cannot assign boolean to Text Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Text.Name); + context.SetWorkData(a_Name, true); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // cannot assign DateTime to Text Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Text.Name); + context.SetWorkData(a_Name, DateTime.Now); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // cannot assign decimal to Text Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Text.Name); + context.SetWorkData(a_Name, 5.3M); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // can assign string to Text Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Text.Name); + context.SetWorkData(a_Name, "hello world"); + }, Throws.Nothing); + } + + [Test] + public void DateAttributeWorkData() + { + OmsContext context = Oms.CreateContext(); + Assert.That(delegate () + { + // cannot assign boolean to Date Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Date.DateAndTime); + context.SetWorkData(a_Name, true); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // cannot assign decimal to Date Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Date.DateAndTime); + context.SetWorkData(a_Name, 5.3M); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // cannot assign string to Date Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Date.DateAndTime); + context.SetWorkData(a_Name, "hello world"); + }, Throws.ArgumentException); + Assert.That(delegate () + { + // can assign DateTime to Date Attribute + InstanceHandle a_Name = Oms.GetInstance(KnownAttributeGuids.Date.DateAndTime); + context.SetWorkData(a_Name, DateTime.Now); + }, Throws.Nothing); + } } \ No newline at end of file