fix(drivers): flip runtime tag parsers to strict enum parsing (#457)
R2-11 Phase C. All six equipment-tag parsers (Modbus/S7/AbCip/AbLegacy/ TwinCAT/Focas) now read enum fields via the new TagConfigJson.TryReadEnumStrict: absent -> fallback, valid -> parsed, present-but-invalid (typo) -> TryParse returns false -> EquipmentTagRefResolver.TryResolve false -> driver surfaces BadNodeIdUnknown, instead of the old lenient path that silently defaulted a typo to a wrong-width Good. Modbus flips all three enum fields (region/dataType/byteOrder); the other five flip dataType. The deploy-time Deployment:TagConfigValidationMode=Error gate is unchanged and remains the operator pre-flight. Coverage: - Six *EquipmentTagParserStrictnessTests inverted from Freeze_typo_* (lenient) to Typo_*_rejects_the_tag + Valid_*_still_parses. - TagConfigJsonTests.TryReadEnumStrict_rejects_only_invalid matrix. - Driver-level end-to-end proof: AbCipEquipmentTagTests.Driver_read_of_a_typod_dataType_ref_surfaces_BadNodeIdUnknown drives the real AbCipDriver.ReadAsync through resolve->status. Golden parity corpus has no typo'd enums, so the flip is a no-op there. Full solution builds clean; all six driver suites + core + parity + gate green.
This commit is contained in:
@@ -68,6 +68,35 @@ public static class TagConfigJson
|
||||
where TEnum : struct, Enum
|
||||
=> TryReadEnum<TEnum>(o, name, out var v) == JsonEnumRead.Valid ? v : fallback;
|
||||
|
||||
/// <summary>
|
||||
/// Strict enum read (R2-11 Phase C — the runtime flip). Absent ⇒ <paramref name="fallback"/>;
|
||||
/// a present, case-insensitively-parseable string ⇒ the parsed value; a present-but-invalid
|
||||
/// string (a typo) ⇒ the read FAILS (returns <see langword="false"/>) so the runtime parser can
|
||||
/// reject the whole tag and the driver surfaces <c>BadNodeIdUnknown</c> — instead of silently
|
||||
/// substituting a wrong-width default and publishing a misleading <c>Good</c>. This is the runtime
|
||||
/// counterpart of <see cref="ReadEnumOrDefault{TEnum}"/>: identical absent/valid handling, but
|
||||
/// <see cref="JsonEnumRead.Invalid"/> is fatal rather than defaulted. The deploy-time gate
|
||||
/// (<c>TagConfigValidationMode.Error</c>) is expected to have already surfaced the same typo via
|
||||
/// <see cref="DescribeInvalidEnum{TEnum}"/>; a typo reaching this fatal path means it was authored
|
||||
/// after the gate, or the gate ran in Warn mode.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to parse.</typeparam>
|
||||
/// <param name="o">The TagConfig root object.</param>
|
||||
/// <param name="name">The property name to read.</param>
|
||||
/// <param name="fallback">The value assigned when the field is absent.</param>
|
||||
/// <param name="value">The parsed enum (valid), the fallback (absent), or <c>default</c> (invalid).</param>
|
||||
/// <returns><see langword="false"/> only when the field is a present-but-invalid string; otherwise <see langword="true"/>.</returns>
|
||||
public static bool TryReadEnumStrict<TEnum>(JsonElement o, string name, TEnum fallback, out TEnum value)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
switch (TryReadEnum<TEnum>(o, name, out value))
|
||||
{
|
||||
case JsonEnumRead.Valid: return true;
|
||||
case JsonEnumRead.Absent: value = fallback; return true;
|
||||
default: value = default; return false; // Invalid (typo) ⇒ reject the tag
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the optional <c>"writable"</c> flag with AbCip semantics (the model): an explicit JSON
|
||||
/// <c>false</c> ⇒ <see langword="false"/>; anything else (absent, <c>true</c>, or a non-bool token)
|
||||
|
||||
Reference in New Issue
Block a user