feat(adminui): S7 tag editor rejects wide-type and writable-Bool arrays at author time (#610)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 06:59:29 -04:00
parent e3cb2dd591
commit 439752d3b8
2 changed files with 60 additions and 0 deletions
@@ -112,6 +112,26 @@ public sealed class S7TagConfigModel
+ string.Join(", ", Enum.GetNames<S7DataType>()) + ".";
}
// S7-016: array shape is judged against the driver's INIT guards, which THROW (failing the whole
// driver's init, not just this tag) for a wide-type array and for a writable Bool array. The
// isArray / arrayLength keys themselves belong to the TagModal's generic array controls
// (TagArrayConfig) and are intentionally not fields of this model, so read them off the key bag.
if (TagConfigJson.GetBool(_bag, "isArray"))
{
var dt = DataType;
if (dt is S7DataType.Int64 or S7DataType.UInt64 or S7DataType.Float64
or S7DataType.String or S7DataType.DateTime)
{
return $"S7 does not support {dt} arrays — use Bool (read-only), Byte, Int16, UInt16, Int32, "
+ "UInt32 or Float32 for an array tag, or a scalar tag.";
}
if (dt == S7DataType.Bool && (Writable ?? true))
{
return "A Bool array cannot be writable (a byte-granular write would clobber neighbouring bits) — "
+ "uncheck Writable, or use scalar Bool tags.";
}
}
// Gitea #554: the reader's remaining, non-enum refusal — a String tag whose 'stringLength' is
// negative or above 254 (S7 REJECTS it rather than clamping) — is not restated here. The verdict and
// the wording both come from the driver's own reader, via TagConfigParseGate.
@@ -103,4 +103,44 @@ public sealed class S7TagConfigModelTests
[InlineData("""{"address":"DB1.DBW0","dataType":7}""")]
public void Validate_accepts_every_dataType_the_driver_accepts(string json)
=> S7TagConfigModel.FromJson(json).Validate().ShouldBeNull();
// ---- S7-016: array shape vs the driver's init guards (which fail the WHOLE driver init) ----
[Theory]
[InlineData("Int64")]
[InlineData("UInt64")]
[InlineData("Float64")]
[InlineData("String")]
[InlineData("DateTime")]
public void Validate_rejects_wide_type_arrays(string dataType)
{
var m = S7TagConfigModel.FromJson(
$$"""{"address":"DB1.DBB0","dataType":"{{dataType}}","isArray":true,"arrayLength":4,"writable":false}""");
m.Validate().ShouldNotBeNull().ShouldContain("arrays");
}
[Fact]
public void Validate_rejects_a_writable_bool_array_but_accepts_it_read_only()
{
var writable = S7TagConfigModel.FromJson(
"""{"address":"DB1.DBX0.0","dataType":"Bool","isArray":true,"arrayLength":4}""");
writable.Validate().ShouldNotBeNull().ShouldContain("Bool array");
var readOnly = S7TagConfigModel.FromJson(
"""{"address":"DB1.DBX0.0","dataType":"Bool","isArray":true,"arrayLength":4,"writable":false}""");
readOnly.Validate().ShouldBeNull();
}
[Theory]
[InlineData("Byte", "DB1.DBB0")]
[InlineData("Int16", "DB1.DBW0")]
[InlineData("Float32", "DB1.DBD0")]
public void Validate_accepts_writable_arrays_of_supported_element_types(string dataType, string address)
{
var m = S7TagConfigModel.FromJson(
$$"""{"address":"{{address}}","dataType":"{{dataType}}","isArray":true,"arrayLength":4}""");
m.Validate().ShouldBeNull();
}
}