fix(adminui): MTConnect editor must read driverDataType at the runtime's precedence and drop the alias on save (#550)

MTConnectTagDefinitionFactory.TryFromTagConfig resolves the coercion type as
FirstNonBlank(blob.DriverDataType, blob.DataType) — driverDataType OUT-RANKS
dataType — while MTConnectTagConfigModel read and wrote dataType alone. That
broke in both directions at once:

  * a blob carrying "driverDataType": "Strng" was accepted at authoring while
    the runtime rejected the tag (the residual slice of #549), and
  * a VALID driverDataType silently overrode whatever type the editor displayed
    and saved, so the editor showed a lie and a save RETYPED the tag.

Both halves land together, mirroring the existing IdKeys pattern:

  * FromJson reads DataTypeKeys = [driverDataType, dataType] in the runtime's
    precedence, with the runtime's own per-key semantics — absent/JSON-null and
    present-but-blank fall through, a non-string token stops the walk (the
    runtime deserialise throws JsonException on it and rejects the whole tag),
    and the raw TOKEN is captured so Validate() judges what the driver judges.
  * ToJson drops every non-canonical DataTypeKeys entry and writes dataType as
    the enum NAME, so the alias can never out-rank the key the editor writes on
    the next load.
  * Validate() now names the offending spelling rather than always saying
    "dataType".

Doing only one half silently retypes existing tags on their next save; the
falsification runs recorded in the PR confirm each half-fix reddens a distinct
test.
This commit is contained in:
Joseph Doherty
2026-08-09 23:44:14 -04:00
parent b0d2fa30e3
commit 8971861b7c
4 changed files with 267 additions and 25 deletions
@@ -74,7 +74,9 @@ public static class MTConnectTagDefinitionFactory
dataItemId = id;
// "driverDataType" (the driver's own tags[] field) or "dataType" (what the AdminUI's typed
// MTConnectTagConfigModel writes) — see the DTO remarks.
// MTConnectTagConfigModel writes) — see the DTO remarks. THIS PRECEDENCE IS MIRRORED by that
// model's own load (Gitea #550): a non-blank driverDataType out-ranks dataType there too, so the
// editor displays the type the driver will actually read rather than a key it is overriding.
var authoredType = FirstNonBlank(blob.DriverDataType, blob.DataType);
if (authoredType is null) return true;
@@ -230,6 +232,15 @@ public static class MTConnectTagDefinitionFactory
/// and the driver disagree about the type of every tag.
/// </para>
/// <para>
/// <b>The alias out-ranks the canonical key, and the editor mirrors that</b> (Gitea #550).
/// <c>MTConnectTagConfigModel</c> used to read <c>dataType</c> alone, so a blob carrying a
/// valid <c>driverDataType</c> was displayed and saved under a type the driver then
/// overrode — the editor showed a lie — while an INVALID one sailed through authoring and
/// was rejected here. It now reads both spellings in this precedence and normalises onto
/// <c>dataType</c> on save, dropping the alias, so a tag edited through the editor keeps
/// the type this parse read for it.
/// </para>
/// <para>
/// Kept separate from the driver's strict <c>tags[]</c> DTO rather than reusing it: there a
/// missing <c>fullName</c> is a config error worth failing the deployment over. A raw tag's
/// blob is authored elsewhere and its failure mode is per-tag, not per-driver.