improve strongly typed validations for Work Set and other Work Data

This commit is contained in:
Michael Becker 2025-10-26 09:26:09 -04:00
parent 79d86a933c
commit 3a96529a21
10 changed files with 204 additions and 56 deletions

@ -1 +1 @@
Subproject commit 8bea362b3ca08a642ba196b8670e8e44e5e799e3 Subproject commit 8638fc4c3a77de9b6fad3a9a17911995300e71c3

View File

@ -52,4 +52,10 @@ public abstract class AttributeImplementation : ClassImplementation
{ {
ExecuteBuildElementInternal(oms, targetInstance, elementContent, elementContentInstance, objCell); ExecuteBuildElementInternal(oms, targetInstance, elementContent, elementContentInstance, objCell);
} }
protected abstract bool IsValidValueInternal(object value);
public bool IsValidValue(object value)
{
return IsValidValueInternal(value);
}
} }

View File

@ -54,4 +54,9 @@ public class BooleanAttributeImplementation : AttributeImplementation<bool>
objCell.Add("value", oms.GetAttributeValue<bool>(targetInstance, elementContentInstance)); objCell.Add("value", oms.GetAttributeValue<bool>(targetInstance, elementContentInstance));
objCell.Add("text", oms.GetAttributeValue<bool>(targetInstance, elementContentInstance) ? "Yes" : "No"); objCell.Add("text", oms.GetAttributeValue<bool>(targetInstance, elementContentInstance) ? "Yes" : "No");
} }
protected override bool IsValidValueInternal(object value)
{
return value is bool;
}
} }

View File

@ -61,4 +61,9 @@ public class DateAttributeImplementation : AttributeImplementation<DateTime>
objCell.Add("text", dt.ToString()); objCell.Add("text", dt.ToString());
objCell.Add("dateTimePrecision", "DAY"); objCell.Add("dateTimePrecision", "DAY");
} }
protected override bool IsValidValueInternal(object value)
{
return value is DateTime;
}
} }

View File

@ -55,4 +55,9 @@ public class NumericAttributeImplementation : AttributeImplementation<decimal>
objCell.Add("precision", 6); objCell.Add("precision", 6);
objCell.Add("format", "#0.######"); objCell.Add("format", "#0.######");
} }
protected override bool IsValidValueInternal(object value)
{
return value is decimal;
}
} }

View File

@ -77,4 +77,9 @@ public class TextAttributeImplementation : AttributeImplementation<string>
objCell.Add("value", oms.GetAttributeValue<string>(targetInstance, elementContentInstance)); objCell.Add("value", oms.GetAttributeValue<string>(targetInstance, elementContentInstance));
} }
} }
protected override bool IsValidValueInternal(object value)
{
return value is string;
}
} }

View File

@ -68,4 +68,17 @@ public class ClassImplementationRegistry
implementation = null; implementation = null;
return false; return false;
} }
public IEnumerable<T> GetAll<T>() where T : ClassImplementation
{
List<T> list = new List<T>();
foreach (KeyValuePair<Guid, ClassImplementation> kvp in implementations)
{
if (kvp.Value is T)
{
list.Add((T)kvp.Value);
}
}
return list;
}
} }

View File

@ -66,6 +66,7 @@ namespace Mocha.Core
public static Guid ValidateOnlyOnSubmit { get; } = new Guid("{400fcd8e-823b-4f4a-aa38-b444f763259b}"); 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 UserIsLoggedIn { get; } = new Guid("{8e93d9f3-a897-4c97-935c-b3427f90633b}");
public static Guid Derived { get; } = new Guid("{66991ca1-ef08-4f30-846c-4984c2a3139d}"); 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 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 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}");
}
} }
} }

View File

@ -137,6 +137,72 @@ public class OmsContext
return defaultValue; return defaultValue;
} }
private void ValidateSubclassConstraints(InstanceHandle parm, System.Collections.IEnumerable en)
{
bool singular = false;
if (!Oms.TryGetAttributeValue<bool>(parm, Oms.GetInstance(KnownAttributeGuids.Boolean.Singular), out singular))
{
singular = false;
}
bool includeSubclasses = false;
if (!Oms.TryGetAttributeValue<bool>(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<InstanceHandle> 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<AttributeImplementation> attrs = Oms.ClassImplementations.GetAll<AttributeImplementation>();
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) public void SetWorkData(IInstanceReference parm, object? value)
{ {
SetWorkData(parm.GetHandle(), value); SetWorkData(parm.GetHandle(), value);
@ -147,75 +213,31 @@ public class OmsContext
{ {
if (Oms.IsInstanceOf(parm, Oms.GetInstance(KnownInstanceGuids.Classes.WorkSet))) if (Oms.IsInstanceOf(parm, Oms.GetInstance(KnownInstanceGuids.Classes.WorkSet)))
{ {
bool singular = false;
if (!Oms.TryGetAttributeValue<bool>(parm, Oms.GetInstance(KnownAttributeGuids.Boolean.Singular), out singular))
{
singular = false;
}
if (value is IInstanceReference) if (value is IInstanceReference)
{ {
IReadOnlyCollection<InstanceHandle> irs = Oms.GetRelatedInstances(parm, Oms.GetInstance(KnownRelationshipGuids.Work_Set__has_valid__Class)); ValidateSubclassConstraints(parm, new IInstanceReference[] { (IInstanceReference)value });
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");
}
}
} }
else if (value is IEnumerable<InstanceHandle>) else if (value is IEnumerable<InstanceHandle>)
{ {
IEnumerable<InstanceHandle> insts = (IEnumerable<InstanceHandle>)value; ValidateSubclassConstraints(parm, (IEnumerable<InstanceHandle>)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<InstanceHandle> 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<InstanceHandle>)value).FirstOrDefault();
} }
else if (value is IEnumerable<InstanceWrapper>) else if (value is IEnumerable<InstanceWrapper>)
{ {
IEnumerable<InstanceWrapper> insts = (IEnumerable<InstanceWrapper>)value; ValidateSubclassConstraints(parm, (IEnumerable<InstanceWrapper>)value);
if (singular && insts.Count() > 1) }
{ else if (value is IEnumerable<IInstanceReference>)
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."); {
} ValidateSubclassConstraints(parm, (IEnumerable<IInstanceReference>)value);
IEnumerable<InstanceHandle> 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");
}
}
}
}
} }
else else
{ {
throw new ArgumentException(String.Format("cannot assign literal data '{0}' to a Work Set", value)); throw new ArgumentException(String.Format("cannot assign literal data '{0}' to a Work Set", value));
} }
} }
else
{
ValidateAttributeConstraints(parm, value);
}
} }
_WorkData[parm] = value; _WorkData[parm] = value;
} }

View File

@ -59,6 +59,28 @@ public class WorkSetTests : OmsTestsBase
}, Throws.ArgumentException); }, 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] [Test]
public void MultipleValidClassConstraintSingular() public void MultipleValidClassConstraintSingular()
{ {
@ -151,4 +173,64 @@ public class WorkSetTests : OmsTestsBase
context.SetWorkData(workSet, true); context.SetWorkData(workSet, true);
}, Throws.ArgumentException); }, 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);
}
} }