feat(validation): require TagConfig.FullName on Galaxy alias tags; reframe Tag doc
This commit is contained in:
@@ -3,9 +3,11 @@ using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// One canonical tag (signal) in a cluster's generation. Per decision #110:
|
||||
/// <see cref="EquipmentId"/> is REQUIRED when the driver is in an Equipment-kind namespace
|
||||
/// and NULL when in SystemPlatform-kind namespace (Galaxy hierarchy preserved).
|
||||
/// One canonical tag (signal) in a cluster's generation. <see cref="EquipmentId"/> set ⟺ the
|
||||
/// tag participates in the Equipment tree, regardless of the driver's namespace kind. A
|
||||
/// <c>GalaxyMxGateway</c>-bound equipment tag is an <em>alias</em> — a Galaxy attribute surfaced
|
||||
/// under a UNS name, with its Galaxy reference carried in <c>TagConfig.FullName</c>.
|
||||
/// <see cref="EquipmentId"/> is NULL for SystemPlatform mirror tags (FolderPath-scoped).
|
||||
/// </summary>
|
||||
public sealed class Tag
|
||||
{
|
||||
@@ -30,8 +32,8 @@ public sealed class Tag
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required when driver is in Equipment-kind namespace; NULL when in SystemPlatform-kind.
|
||||
/// Cross-table invariant enforced by sp_ValidateDraft (decision #110).
|
||||
/// Set when the tag belongs to an Equipment (driver tag OR Galaxy alias); NULL for
|
||||
/// SystemPlatform mirror tags.
|
||||
/// </summary>
|
||||
public string? EquipmentId { get; set; }
|
||||
|
||||
|
||||
@@ -33,10 +33,43 @@ public static class DraftValidator
|
||||
ValidateEquipmentIdDerivation(draft, errors);
|
||||
ValidateDriverNamespaceCompatibility(draft, errors);
|
||||
ValidateNoEquipmentSignalNameCollision(draft, errors);
|
||||
ValidateAliasTagFullName(draft, errors);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private static void ValidateAliasTagFullName(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
var typeByDriver = draft.DriverInstances
|
||||
.ToDictionary(d => d.DriverInstanceId, d => d.DriverType, StringComparer.Ordinal);
|
||||
foreach (var t in draft.Tags)
|
||||
{
|
||||
if (t.EquipmentId is null) continue;
|
||||
if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway")
|
||||
continue;
|
||||
if (string.IsNullOrWhiteSpace(ExtractTagConfigFullName(t.TagConfig)))
|
||||
errors.Add(new("AliasTagMissingReference",
|
||||
$"Alias tag '{t.TagId}' on equipment '{t.EquipmentId}' is missing a Galaxy reference (TagConfig.FullName)",
|
||||
t.TagId));
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal reader for the top-level "FullName" string in a tag's schemaless TagConfig JSON
|
||||
// (mirrors Phase7Composer.ExtractTagFullName — a small local copy, consistent with this codebase
|
||||
// where the composer keeps its own).
|
||||
private static string? ExtractTagConfigFullName(string? tagConfig)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagConfig)) return null;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(tagConfig);
|
||||
return doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object
|
||||
&& doc.RootElement.TryGetProperty("FullName", out var fn)
|
||||
&& fn.ValueKind == System.Text.Json.JsonValueKind.String ? fn.GetString() : null;
|
||||
}
|
||||
catch (System.Text.Json.JsonException) { return null; }
|
||||
}
|
||||
|
||||
private static void ValidateNoEquipmentSignalNameCollision(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
// Materialiser NodeId key: "{EquipmentId}[/{FolderPath}]/{Name}". Tag (EquipmentId != null) and
|
||||
|
||||
Reference in New Issue
Block a user