diff --git a/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs b/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs index 3dbc00a..79f6ff3 100644 --- a/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs +++ b/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs @@ -40,7 +40,14 @@ public static class OpcUaEndpointConfigSerializer } catch (JsonException) { /* fall through to legacy */ } - return (LoadLegacy(json!), IsLegacy: true); + try + { + return (LoadLegacy(json!), IsLegacy: true); + } + catch (JsonException) + { + return (new OpcUaEndpointConfig(), IsLegacy: true); + } } /// @@ -74,33 +81,38 @@ public static class OpcUaEndpointConfigSerializer public static OpcUaEndpointConfig FromFlatDict(IDictionary dict) { - var c = new OpcUaEndpointConfig - { - EndpointUrl = dict.TryGetValue("endpoint", out var ep) ? ep - : dict.TryGetValue("EndpointUrl", out var ep2) ? ep2 : "", - SecurityMode = Enum.TryParse( - dict.TryGetValue("SecurityMode", out var smStr) ? smStr : null, - ignoreCase: true, out var sm) - ? sm : OpcUaSecurityMode.None, - AutoAcceptUntrustedCerts = ParseBool(dict, "AutoAcceptUntrustedCerts", true), - SessionTimeoutMs = ParseInt(dict, "SessionTimeoutMs", 60000), - OperationTimeoutMs = ParseInt(dict, "OperationTimeoutMs", 15000), - PublishingIntervalMs = ParseInt(dict, "PublishingIntervalMs", 1000), - SamplingIntervalMs = ParseInt(dict, "SamplingIntervalMs", 1000), - QueueSize = ParseInt(dict, "QueueSize", 10), - KeepAliveCount = ParseInt(dict, "KeepAliveCount", 10), - LifetimeCount = ParseInt(dict, "LifetimeCount", 30), - MaxNotificationsPerPublish = ParseInt(dict, "MaxNotificationsPerPublish", 100), - }; + var c = new OpcUaEndpointConfig(); + + if (dict.TryGetValue("endpoint", out var ep) && !string.IsNullOrWhiteSpace(ep)) + c.EndpointUrl = ep; + else if (dict.TryGetValue("EndpointUrl", out var ep2) && !string.IsNullOrWhiteSpace(ep2)) + c.EndpointUrl = ep2; + + if (dict.TryGetValue("SecurityMode", out var smStr) + && Enum.TryParse(smStr, ignoreCase: true, out var sm)) + c.SecurityMode = sm; + + if (dict.TryGetValue("AutoAcceptUntrustedCerts", out var aacStr) + && bool.TryParse(aacStr, out var aac)) + c.AutoAcceptUntrustedCerts = aac; + + TryAssignInt(dict, "SessionTimeoutMs", v => c.SessionTimeoutMs = v); + TryAssignInt(dict, "OperationTimeoutMs", v => c.OperationTimeoutMs = v); + TryAssignInt(dict, "PublishingIntervalMs", v => c.PublishingIntervalMs = v); + TryAssignInt(dict, "SamplingIntervalMs", v => c.SamplingIntervalMs = v); + TryAssignInt(dict, "QueueSize", v => c.QueueSize = v); + TryAssignInt(dict, "KeepAliveCount", v => c.KeepAliveCount = v); + TryAssignInt(dict, "LifetimeCount", v => c.LifetimeCount = v); + TryAssignInt(dict, "MaxNotificationsPerPublish", v => c.MaxNotificationsPerPublish = v); + if (dict.TryGetValue("HeartbeatTagPath", out var hbPath) && !string.IsNullOrWhiteSpace(hbPath)) { - c.Heartbeat = new OpcUaHeartbeatConfig - { - TagPath = hbPath, - MaxSilenceSeconds = ParseInt(dict, "HeartbeatMaxSilence", 30) - }; + var hb = new OpcUaHeartbeatConfig { TagPath = hbPath }; + TryAssignInt(dict, "HeartbeatMaxSilence", v => hb.MaxSilenceSeconds = v); + c.Heartbeat = hb; } + return c; } @@ -111,9 +123,9 @@ public static class OpcUaEndpointConfigSerializer return FromFlatDict(dict); } - private static int ParseInt(IDictionary d, string key, int defaultValue) - => d.TryGetValue(key, out var s) && int.TryParse(s, out var v) ? v : defaultValue; - - private static bool ParseBool(IDictionary d, string key, bool defaultValue) - => d.TryGetValue(key, out var s) && bool.TryParse(s, out var v) ? v : defaultValue; + private static void TryAssignInt(IDictionary dict, string key, Action assign) + { + if (dict.TryGetValue(key, out var s) && int.TryParse(s, out var v)) + assign(v); + } } diff --git a/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs b/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs index 9edfd7f..ea3b660 100644 --- a/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs +++ b/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs @@ -114,6 +114,21 @@ public class OpcUaEndpointConfigSerializerTests Assert.Equal("opc.tcp://x:4840", config.EndpointUrl); } + [Theory] + [InlineData("not json at all")] + [InlineData("[1,2,3]")] + [InlineData("{\"foo\":123}")] + [InlineData("\"just a string\"")] + public void Deserialize_Malformed_ReturnsDefaultsAsLegacy(string input) + { + var (config, isLegacy) = OpcUaEndpointConfigSerializer.Deserialize(input); + + Assert.True(isLegacy); + Assert.Equal("", config.EndpointUrl); + Assert.Equal(60000, config.SessionTimeoutMs); + Assert.Null(config.Heartbeat); + } + [Fact] public void ToFlatDict_OmitsNullHeartbeat() {