diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs index a53e4390..3bb727e5 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs @@ -194,8 +194,14 @@ public static class SqlDriverFactoryExtensions } catch (JsonException ex) { + // The parser's message AND the exception carrying it are withheld (R06:S-8), matching + // SqlDriverProbe and SqlDriverBrowser: System.Text.Json quotes the offending token, and a pasted + // connection-string literal is exactly the malformed text that lands here. This message becomes + // InitializeFailed -> health LastError -> /hosts. Line/byte position carry no content, so they stay. throw new InvalidOperationException( - $"Sql driver config for '{driverInstanceId}' is not valid JSON: {ex.Message}", ex); + $"Sql driver config for '{driverInstanceId}' is not valid JSON (line {ex.LineNumber + 1}, byte " + + $"{ex.BytePositionInLine}). The parser's message is withheld because the malformed text may " + + "carry a connection string."); } } diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs index 65d6a281..9d1265de 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs @@ -25,6 +25,28 @@ public sealed class SqlDriverFactoryTests { // ---- the plan's headline legs ---- + /// + /// R06:S-8. A malformed config used to quote System.Text.Json's message — which echoes the + /// offending token — into the exception that becomes InitializeFailed → health + /// LastError → /hosts. A pasted connection string is exactly the malformed text that + /// reaches here, so the parser text (and the inner exception carrying it) is withheld, as the probe and + /// browser already do. The message still names the instance and says what is wrong. + /// + [Fact] + public void CreateInstance_malformedJson_withholdsTheParserText() + { + const string secret = "hunter2-do-not-log"; + var ex = Should.Throw(() => + SqlDriverFactoryExtensions.CreateInstance( + "d1", $$"""{"provider":"SqlServer","connectionStringRef": Password={{secret}} }""", null)); + + ex.Message.ShouldContain("d1"); + ex.Message.ShouldContain("not valid JSON"); + ex.Message.ShouldNotContain(secret); + ex.Message.ShouldNotContain("Password"); + ex.InnerException.ShouldBeNull(); + } + [Fact] public void CreateInstance_missingConnectionStringRef_throwsActionable() => Should.Throw(() =>