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 */ }
|
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>
|
/// <summary>
|
||||||
@@ -74,33 +81,38 @@ public static class OpcUaEndpointConfigSerializer
|
|||||||
|
|
||||||
public static OpcUaEndpointConfig FromFlatDict(IDictionary<string, string> dict)
|
public static OpcUaEndpointConfig FromFlatDict(IDictionary<string, string> dict)
|
||||||
{
|
{
|
||||||
var c = new OpcUaEndpointConfig
|
var c = new OpcUaEndpointConfig();
|
||||||
{
|
|
||||||
EndpointUrl = dict.TryGetValue("endpoint", out var ep) ? ep
|
if (dict.TryGetValue("endpoint", out var ep) && !string.IsNullOrWhiteSpace(ep))
|
||||||
: dict.TryGetValue("EndpointUrl", out var ep2) ? ep2 : "",
|
c.EndpointUrl = ep;
|
||||||
SecurityMode = Enum.TryParse<OpcUaSecurityMode>(
|
else if (dict.TryGetValue("EndpointUrl", out var ep2) && !string.IsNullOrWhiteSpace(ep2))
|
||||||
dict.TryGetValue("SecurityMode", out var smStr) ? smStr : null,
|
c.EndpointUrl = ep2;
|
||||||
ignoreCase: true, out var sm)
|
|
||||||
? sm : OpcUaSecurityMode.None,
|
if (dict.TryGetValue("SecurityMode", out var smStr)
|
||||||
AutoAcceptUntrustedCerts = ParseBool(dict, "AutoAcceptUntrustedCerts", true),
|
&& Enum.TryParse<OpcUaSecurityMode>(smStr, ignoreCase: true, out var sm))
|
||||||
SessionTimeoutMs = ParseInt(dict, "SessionTimeoutMs", 60000),
|
c.SecurityMode = sm;
|
||||||
OperationTimeoutMs = ParseInt(dict, "OperationTimeoutMs", 15000),
|
|
||||||
PublishingIntervalMs = ParseInt(dict, "PublishingIntervalMs", 1000),
|
if (dict.TryGetValue("AutoAcceptUntrustedCerts", out var aacStr)
|
||||||
SamplingIntervalMs = ParseInt(dict, "SamplingIntervalMs", 1000),
|
&& bool.TryParse(aacStr, out var aac))
|
||||||
QueueSize = ParseInt(dict, "QueueSize", 10),
|
c.AutoAcceptUntrustedCerts = aac;
|
||||||
KeepAliveCount = ParseInt(dict, "KeepAliveCount", 10),
|
|
||||||
LifetimeCount = ParseInt(dict, "LifetimeCount", 30),
|
TryAssignInt(dict, "SessionTimeoutMs", v => c.SessionTimeoutMs = v);
|
||||||
MaxNotificationsPerPublish = ParseInt(dict, "MaxNotificationsPerPublish", 100),
|
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)
|
if (dict.TryGetValue("HeartbeatTagPath", out var hbPath)
|
||||||
&& !string.IsNullOrWhiteSpace(hbPath))
|
&& !string.IsNullOrWhiteSpace(hbPath))
|
||||||
{
|
{
|
||||||
c.Heartbeat = new OpcUaHeartbeatConfig
|
var hb = new OpcUaHeartbeatConfig { TagPath = hbPath };
|
||||||
{
|
TryAssignInt(dict, "HeartbeatMaxSilence", v => hb.MaxSilenceSeconds = v);
|
||||||
TagPath = hbPath,
|
c.Heartbeat = hb;
|
||||||
MaxSilenceSeconds = ParseInt(dict, "HeartbeatMaxSilence", 30)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +123,9 @@ public static class OpcUaEndpointConfigSerializer
|
|||||||
return FromFlatDict(dict);
|
return FromFlatDict(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ParseInt(IDictionary<string, string> d, string key, int defaultValue)
|
private static void TryAssignInt(IDictionary<string, string> dict, string key, Action<int> assign)
|
||||||
=> d.TryGetValue(key, out var s) && int.TryParse(s, out var v) ? v : defaultValue;
|
{
|
||||||
|
if (dict.TryGetValue(key, out var s) && int.TryParse(s, out var v))
|
||||||
private static bool ParseBool(IDictionary<string, string> d, string key, bool defaultValue)
|
assign(v);
|
||||||
=> d.TryGetValue(key, out var s) && bool.TryParse(s, out var v) ? v : defaultValue;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,21 @@ public class OpcUaEndpointConfigSerializerTests
|
|||||||
Assert.Equal("opc.tcp://x:4840", config.EndpointUrl);
|
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]
|
[Fact]
|
||||||
public void ToFlatDict_OmitsNullHeartbeat()
|
public void ToFlatDict_OmitsNullHeartbeat()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user