refactor(commons): defensive legacy-parse + FromFlatDict starts from POCO defaults
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,33 +81,38 @@ public static class OpcUaEndpointConfigSerializer
|
||||
|
||||
public static OpcUaEndpointConfig FromFlatDict(IDictionary<string, string> dict)
|
||||
{
|
||||
var c = new OpcUaEndpointConfig
|
||||
{
|
||||
EndpointUrl = dict.TryGetValue("endpoint", out var ep) ? ep
|
||||
: dict.TryGetValue("EndpointUrl", out var ep2) ? ep2 : "",
|
||||
SecurityMode = Enum.TryParse<OpcUaSecurityMode>(
|
||||
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<OpcUaSecurityMode>(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<string, string> d, string key, int defaultValue)
|
||||
=> d.TryGetValue(key, out var s) && int.TryParse(s, out var v) ? v : defaultValue;
|
||||
|
||||
private static bool ParseBool(IDictionary<string, string> d, string key, bool defaultValue)
|
||||
=> d.TryGetValue(key, out var s) && bool.TryParse(s, out var v) ? v : defaultValue;
|
||||
private static void TryAssignInt(IDictionary<string, string> dict, string key, Action<int> assign)
|
||||
{
|
||||
if (dict.TryGetValue(key, out var s) && int.TryParse(s, out var v))
|
||||
assign(v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user