feat(validation): semantic checks for List attributes (element type, default value, trigger operands)
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
@@ -48,6 +50,11 @@ public class SemanticValidator
|
||||
attributeMap.TryAdd(a.CanonicalName, a);
|
||||
}
|
||||
|
||||
// List-attribute type semantics (MV-5): element-type cardinality + default
|
||||
// value parseability. Trigger-operand rejection (rule 3) is handled below
|
||||
// by the existing NumericDataTypes guard (List is never numeric).
|
||||
ValidateListAttributes(configuration, errors);
|
||||
|
||||
// Collect alarm on-trigger script names for cross-call violation checks
|
||||
var alarmOnTriggerScripts = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var alarm in configuration.Alarms)
|
||||
@@ -250,6 +257,77 @@ public class SemanticValidator
|
||||
return new ValidationResult { Errors = errors, Warnings = warnings };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MV-5 — semantic validation of List-attribute type configuration. Two rules:
|
||||
/// <list type="number">
|
||||
/// <item><b>Element-type cardinality.</b> A <see cref="DataType.List"/> attribute
|
||||
/// must carry a non-empty <see cref="ResolvedAttribute.ElementDataType"/> that is
|
||||
/// a valid element scalar (see <see cref="AttributeValueCodec.IsValidElementType"/>);
|
||||
/// a non-List attribute must NOT carry an element type.</item>
|
||||
/// <item><b>Default-value parseability.</b> A non-empty authored default
|
||||
/// <see cref="ResolvedAttribute.Value"/> on a List attribute must
|
||||
/// <see cref="AttributeValueCodec.Decode"/> without throwing.</item>
|
||||
/// </list>
|
||||
/// Attributes whose <see cref="ResolvedAttribute.DataType"/> doesn't parse to a
|
||||
/// known <see cref="DataType"/> are skipped here (their data type is not "List",
|
||||
/// so only the "no element type" half could apply, and an unparseable type is a
|
||||
/// separate concern not introduced by this feature).
|
||||
/// </summary>
|
||||
private static void ValidateListAttributes(
|
||||
FlattenedConfiguration configuration,
|
||||
List<ValidationEntry> errors)
|
||||
{
|
||||
foreach (var attr in configuration.Attributes)
|
||||
{
|
||||
var isList = string.Equals(attr.DataType, nameof(DataType.List), StringComparison.OrdinalIgnoreCase);
|
||||
var hasElementType = !string.IsNullOrWhiteSpace(attr.ElementDataType);
|
||||
|
||||
// ── Rule 1: element-type cardinality ─────────────────────────────
|
||||
if (!isList)
|
||||
{
|
||||
if (hasElementType)
|
||||
{
|
||||
errors.Add(ValidationEntry.Error(ValidationCategory.MissingMetadata,
|
||||
$"Attribute '{attr.CanonicalName}' has data type '{attr.DataType}' but declares an element type '{attr.ElementDataType}'; element types are only valid on List attributes.",
|
||||
attr.CanonicalName));
|
||||
}
|
||||
continue; // Non-List attributes have no list-specific value to check.
|
||||
}
|
||||
|
||||
if (!hasElementType)
|
||||
{
|
||||
errors.Add(ValidationEntry.Error(ValidationCategory.MissingMetadata,
|
||||
$"List attribute '{attr.CanonicalName}' must declare an element type (one of String, Int32, Float, Double, Boolean, DateTime).",
|
||||
attr.CanonicalName));
|
||||
continue; // Without an element type we can't validate the default value.
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<DataType>(attr.ElementDataType, ignoreCase: true, out var elementType)
|
||||
|| !AttributeValueCodec.IsValidElementType(elementType))
|
||||
{
|
||||
errors.Add(ValidationEntry.Error(ValidationCategory.MissingMetadata,
|
||||
$"List attribute '{attr.CanonicalName}' has element type '{attr.ElementDataType}', which is not a valid element scalar (one of String, Int32, Float, Double, Boolean, DateTime).",
|
||||
attr.CanonicalName));
|
||||
continue; // A bad element type makes the default-value check meaningless.
|
||||
}
|
||||
|
||||
// ── Rule 2: default-value parseability ───────────────────────────
|
||||
if (!string.IsNullOrEmpty(attr.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
AttributeValueCodec.Decode(attr.Value, DataType.List, elementType);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
errors.Add(ValidationEntry.Error(ValidationCategory.MissingMetadata,
|
||||
$"List attribute '{attr.CanonicalName}' has a default value that is not a valid list of '{elementType}': {ex.Message}",
|
||||
attr.CanonicalName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateCallParameters(
|
||||
string callerName,
|
||||
CallTarget call,
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
/// and (on the deploy path) the bound connection must exist on the target site.
|
||||
/// Severity is context-dependent: a non-blocking Warning at template design time
|
||||
/// (bindings are set later) and a deploy-gating Error when enforced (M2.8 / #23).
|
||||
/// 8. Does NOT verify tag path resolution on devices
|
||||
/// 8. List-attribute type semantics (MV-5, via <see cref="SemanticValidator"/>):
|
||||
/// element-type cardinality, default-value parseability, and trigger-operand
|
||||
/// rejection for List attributes.
|
||||
/// 9. Does NOT verify tag path resolution on devices
|
||||
/// </summary>
|
||||
public class ValidationService
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user