feat(driver-ablegacy): shared TagConfigJson readers, writable key, Inspect warnings (R2-11)
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
{ "id": "T12", "subject": "Modbus equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T12", "subject": "Modbus equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T13", "subject": "S7 equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings (+ amend Contracts banner)", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T13", "subject": "S7 equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings (+ amend Contracts banner)", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T14", "subject": "AbCip equipment-tag parser: freeze test, shared readers (writable already honoured), Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
{ "id": "T14", "subject": "AbCip equipment-tag parser: freeze test, shared readers (writable already honoured), Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T15", "subject": "AbLegacy equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "pending", "blockedBy": ["T11"] },
|
{ "id": "T15", "subject": "AbLegacy equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "completed", "blockedBy": ["T11"] },
|
||||||
{ "id": "T16", "subject": "TwinCAT equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "pending", "blockedBy": ["T11"] },
|
{ "id": "T16", "subject": "TwinCAT equipment-tag parser: freeze test, shared readers, honour writable key, Inspect warnings", "status": "pending", "blockedBy": ["T11"] },
|
||||||
{ "id": "T17", "subject": "FOCAS equipment-tag parser: force Writable:false (05/UNDER-1 correction), shared readers, Inspect warnings (+ amend Contracts banner)", "status": "pending", "blockedBy": ["T11"] },
|
{ "id": "T17", "subject": "FOCAS equipment-tag parser: force Writable:false (05/UNDER-1 correction), shared readers, Inspect warnings (+ amend Contracts banner)", "status": "pending", "blockedBy": ["T11"] },
|
||||||
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "pending", "blockedBy": ["T17"] },
|
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "pending", "blockedBy": ["T17"] },
|
||||||
|
|||||||
+35
-5
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ public static class AbLegacyEquipmentTagParser
|
|||||||
return false;
|
return false;
|
||||||
var address = addr.GetString();
|
var address = addr.GetString();
|
||||||
if (string.IsNullOrWhiteSpace(address)) return false;
|
if (string.IsNullOrWhiteSpace(address)) return false;
|
||||||
var dataType = ReadEnum(root, "dataType", AbLegacyDataType.Int);
|
var dataType = TagConfigJson.ReadEnumOrDefault(root, "dataType", AbLegacyDataType.Int);
|
||||||
var deviceHostAddress = ReadString(root, "deviceHostAddress");
|
var deviceHostAddress = ReadString(root, "deviceHostAddress");
|
||||||
int? arrayLength = null;
|
int? arrayLength = null;
|
||||||
if (IsArrayFlag(root))
|
if (IsArrayFlag(root))
|
||||||
@@ -47,9 +48,12 @@ public static class AbLegacyEquipmentTagParser
|
|||||||
if (rawLength >= 1)
|
if (rawLength >= 1)
|
||||||
arrayLength = Math.Min(rawLength, AbLegacyArray.MaxElements);
|
arrayLength = Math.Min(rawLength, AbLegacyArray.MaxElements);
|
||||||
}
|
}
|
||||||
|
// "writable" defaults to true when absent (today's value) — makes read-only equipment tags
|
||||||
|
// authorable (UNDER-6).
|
||||||
|
var writable = TagConfigJson.ReadWritable(root);
|
||||||
def = new AbLegacyTagDefinition(
|
def = new AbLegacyTagDefinition(
|
||||||
Name: reference, DeviceHostAddress: deviceHostAddress, Address: address,
|
Name: reference, DeviceHostAddress: deviceHostAddress, Address: address,
|
||||||
DataType: dataType, Writable: true, ArrayLength: arrayLength);
|
DataType: dataType, Writable: writable, ArrayLength: arrayLength);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (JsonException) { return false; }
|
catch (JsonException) { return false; }
|
||||||
@@ -57,9 +61,35 @@ public static class AbLegacyEquipmentTagParser
|
|||||||
catch (InvalidOperationException) { return false; }
|
catch (InvalidOperationException) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TEnum ReadEnum<TEnum>(JsonElement o, string name, TEnum fallback) where TEnum : struct, Enum
|
/// <summary>
|
||||||
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
|
/// Deploy-time inspection (05/CONV-2): warns on a present-but-invalid <c>dataType</c> (silently
|
||||||
&& Enum.TryParse<TEnum>(e.GetString(), ignoreCase: true, out var v) ? v : fallback;
|
/// defaulted by the lenient runtime) and on a structurally unparseable TagConfig. Empty when clean
|
||||||
|
/// or not an equipment-tag object. Never throws.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reference">The equipment tag's TagConfig JSON.</param>
|
||||||
|
/// <returns>The warnings; empty when clean.</returns>
|
||||||
|
public static IReadOnlyList<string> Inspect(string reference)
|
||||||
|
{
|
||||||
|
var warnings = new List<string>();
|
||||||
|
if (string.IsNullOrWhiteSpace(reference) || reference[0] != '{') return warnings;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(reference);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
if (root.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
warnings.Add("AbLegacy TagConfig root is not a JSON object — the tag will not resolve (BadNodeIdUnknown).");
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
var w = TagConfigJson.DescribeInvalidEnum<AbLegacyDataType>(root, "dataType");
|
||||||
|
if (w is not null) warnings.Add(w);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
warnings.Add("AbLegacy TagConfig is not valid JSON — the tag will not resolve (BadNodeIdUnknown).");
|
||||||
|
}
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ReadString(JsonElement o, string name)
|
private static string ReadString(JsonElement o, string name)
|
||||||
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
|
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
|
||||||
|
|||||||
+5
-1
@@ -5,5 +5,9 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- NO PackageReference. -->
|
<!-- NO PackageReference. ProjectReference only to the zero-dependency Core.Abstractions leaf
|
||||||
|
(R2-11 05/CONV-2 — shared TagConfigJson readers). -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.Abstractions\ZB.MOM.WW.OtOpcUa.Core.Abstractions.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
|
||||||
|
|
||||||
|
/// <summary>R2-11 (05/CONV-2) strictness surface for the AbLegacy equipment-tag parser: lenient runtime
|
||||||
|
/// freeze, <c>Inspect</c> reports the typo, and the <c>writable</c> key is honoured (default true).</summary>
|
||||||
|
public sealed class AbLegacyEquipmentTagParserStrictnessTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Freeze_typo_dataType_still_defaults_to_Int()
|
||||||
|
{
|
||||||
|
AbLegacyEquipmentTagParser.TryParse("{\"address\":\"N7:0\",\"dataType\":\"Intt\"}", out var def)
|
||||||
|
.ShouldBeTrue();
|
||||||
|
def.DataType.ShouldBe(AbLegacyDataType.Int);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Inspect_reports_typo_dataType()
|
||||||
|
{
|
||||||
|
var warnings = AbLegacyEquipmentTagParser.Inspect("{\"address\":\"N7:0\",\"dataType\":\"Intt\"}");
|
||||||
|
warnings.ShouldHaveSingleItem();
|
||||||
|
warnings[0].ShouldContain("Intt");
|
||||||
|
warnings[0].ShouldContain("dataType");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Inspect_clean_config_has_no_warnings()
|
||||||
|
{
|
||||||
|
AbLegacyEquipmentTagParser.Inspect("{\"address\":\"N7:0\",\"dataType\":\"Int\"}").ShouldBeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Writable_false_is_honoured()
|
||||||
|
{
|
||||||
|
AbLegacyEquipmentTagParser.TryParse("{\"address\":\"N7:0\",\"dataType\":\"Int\",\"writable\":false}", out var def)
|
||||||
|
.ShouldBeTrue();
|
||||||
|
def.Writable.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Writable_absent_defaults_to_true()
|
||||||
|
{
|
||||||
|
AbLegacyEquipmentTagParser.TryParse("{\"address\":\"N7:0\",\"dataType\":\"Int\"}", out var def)
|
||||||
|
.ShouldBeTrue();
|
||||||
|
def.Writable.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user