Merge B1-WP2: TagConfigIntent successor + RawPaths tests + key normalization
This commit is contained in:
@@ -8,28 +8,17 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
/// (<c>AddressSpaceComposer</c>), the artifact-decode seam (<c>DeploymentArtifact</c>), the draft
|
||||
/// gate (<c>DraftValidator</c>), and the walker (<c>EquipmentNodeWalker</c>). One
|
||||
/// <see cref="JsonDocument.Parse(string)"/> per blob. Never throws.
|
||||
/// <para>Two deliberate variations are carried on the record so every seam can be served from one
|
||||
/// parse:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="FullName"/> is the driver-side wire reference: the top-level
|
||||
/// <c>FullName</c> string when present, else the RAW blob (an equipment tag's TagConfig JSON
|
||||
/// <em>is</em> its wire reference for the six protocol drivers). <c>Parse(null)</c> ⇒
|
||||
/// <c>FullName == ""</c>.</description></item>
|
||||
/// <item><description><see cref="ExplicitFullName"/> is the <c>FullName</c> property ONLY when
|
||||
/// present-and-string, else <see langword="null"/> — the DraftValidator Galaxy-rule semantics
|
||||
/// (it wants the explicit reference, not the raw-blob fallback).</description></item>
|
||||
/// </list>
|
||||
/// <para>Under the v3 identity contract, <c>TagConfig</c> no longer carries identity — the
|
||||
/// RawPath (computed elsewhere) is the single identity string at every seam. This type parses
|
||||
/// only the cross-driver PLATFORM intents (alarm / historize / array); the driver-specific
|
||||
/// address fields stay opaque inside the blob and are read by the per-driver resolvers.</para>
|
||||
/// </summary>
|
||||
/// <param name="FullName">Top-level <c>FullName</c> string, else the raw blob (wire-reference fallback).</param>
|
||||
/// <param name="ExplicitFullName">The <c>FullName</c> property when present-and-string, else <see langword="null"/>.</param>
|
||||
/// <param name="Alarm">The optional native-alarm intent parsed from the <c>alarm</c> object; <see langword="null"/> ⇒ plain value variable.</param>
|
||||
/// <param name="IsHistorized"><c>isHistorized</c> — true only when the value is a bool <c>true</c>.</param>
|
||||
/// <param name="HistorianTagname">Non-whitespace <c>historianTagname</c> string override, else <see langword="null"/> (not trimmed).</param>
|
||||
/// <param name="IsArray"><c>isArray</c> bool.</param>
|
||||
/// <param name="ArrayLength"><c>arrayLength</c> honoured ONLY when <see cref="IsArray"/> and a JSON number fitting <see cref="uint"/>; else <see langword="null"/>.</param>
|
||||
public sealed record TagConfigIntent(
|
||||
string FullName,
|
||||
string? ExplicitFullName,
|
||||
TagAlarmIntent? Alarm,
|
||||
bool IsHistorized,
|
||||
string? HistorianTagname,
|
||||
@@ -38,40 +27,33 @@ public sealed record TagConfigIntent(
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse a Tag's schemaless <c>TagConfig</c> JSON into the cross-driver platform intents.
|
||||
/// Never throws: malformed JSON / non-object root / blank / null ⇒ all intents default with
|
||||
/// <see cref="FullName"/> = the raw blob (or "" for null). Semantics are ported verbatim from the
|
||||
/// historical <c>AddressSpaceComposer.Extract*</c> statics (the canonical copy).
|
||||
/// Never throws: malformed JSON / non-object root / blank / null ⇒ all intents default.
|
||||
/// Semantics are ported verbatim from the historical <c>AddressSpaceComposer.Extract*</c>
|
||||
/// statics (the canonical copy).
|
||||
/// </summary>
|
||||
/// <param name="tagConfig">The tag's raw <c>TagConfig</c> JSON (nullable/blank tolerated).</param>
|
||||
/// <returns>The parsed intents; never <see langword="null"/>.</returns>
|
||||
public static TagConfigIntent Parse(string? tagConfig)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagConfig))
|
||||
return new TagConfigIntent(tagConfig ?? string.Empty, null, null, false, null, false, null);
|
||||
return new TagConfigIntent(null, false, null, false, null);
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(tagConfig);
|
||||
var root = doc.RootElement;
|
||||
if (root.ValueKind != JsonValueKind.Object)
|
||||
return new TagConfigIntent(tagConfig, null, null, false, null, false, null);
|
||||
|
||||
var explicitFullName =
|
||||
root.TryGetProperty("FullName", out var fn) && fn.ValueKind == JsonValueKind.String
|
||||
? fn.GetString()
|
||||
: null;
|
||||
var fullName = explicitFullName ?? tagConfig;
|
||||
return new TagConfigIntent(null, false, null, false, null);
|
||||
|
||||
var (isHistorized, historianTagname) = ParseHistorize(root);
|
||||
var (isArray, arrayLength) = ParseArray(root);
|
||||
|
||||
return new TagConfigIntent(
|
||||
fullName, explicitFullName, ParseAlarm(root),
|
||||
isHistorized, historianTagname, isArray, arrayLength);
|
||||
ParseAlarm(root), isHistorized, historianTagname, isArray, arrayLength);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return new TagConfigIntent(tagConfig, null, null, false, null, false, null);
|
||||
return new TagConfigIntent(null, false, null, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user