Skip to content

Commit 30f4fa2

Browse files
Copilotromanett
andauthored
Include failed value in XmlDecoder error messages (#3374)
* Initial plan * Add enhanced error messages for XML decoding failures - Updated CreateBadDecodingError to optionally include failed value - Modified SafeXmlConvert to pass XML value to error handler - Updated ReadFloat, ReadDouble, ReadDateTime, ReadGuid, ReadNodeId, ReadExpandedNodeId, and ReadEnumerated to include values in error messages - Added comprehensive tests to validate enhanced error messages Co-authored-by: romanett <[email protected]> * Fix parameter order consistency in SafeXmlConvert Use named parameter for value argument to maintain consistency with other calls Co-authored-by: romanett <[email protected]> * Use named parameters consistently in SafeXmlConvert Use named parameters for both functionName and value for better code clarity Co-authored-by: romanett <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: romanett <[email protected]>
1 parent 6693b12 commit 30f4fa2

File tree

2 files changed

+113
-10
lines changed

2 files changed

+113
-10
lines changed

Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ public DateTime ReadDateTime(string fieldName)
957957
}
958958
catch (FormatException fe)
959959
{
960-
throw CreateBadDecodingError(fieldName, fe);
960+
throw CreateBadDecodingError(fieldName, fe, value: xml);
961961
}
962962
}
963963
}
@@ -985,7 +985,7 @@ public Uuid ReadGuid(string fieldName)
985985
}
986986
catch (FormatException fe)
987987
{
988-
throw CreateBadDecodingError(fieldName, fe);
988+
throw CreateBadDecodingError(fieldName, fe, value: guidString);
989989
}
990990

991991
EndField(fieldName);
@@ -1092,11 +1092,11 @@ public NodeId ReadNodeId(string fieldName)
10921092
catch (ServiceResultException sre) when (sre.StatusCode == StatusCodes
10931093
.BadNodeIdInvalid)
10941094
{
1095-
throw CreateBadDecodingError(fieldName, sre);
1095+
throw CreateBadDecodingError(fieldName, sre, value: identifierText);
10961096
}
10971097
catch (ArgumentException ae)
10981098
{
1099-
throw CreateBadDecodingError(fieldName, ae);
1099+
throw CreateBadDecodingError(fieldName, ae, value: identifierText);
11001100
}
11011101

11021102
EndField(fieldName);
@@ -1132,11 +1132,11 @@ public ExpandedNodeId ReadExpandedNodeId(string fieldName)
11321132
catch (ServiceResultException sre) when (sre.StatusCode == StatusCodes
11331133
.BadNodeIdInvalid)
11341134
{
1135-
throw CreateBadDecodingError(fieldName, sre);
1135+
throw CreateBadDecodingError(fieldName, sre, value: identifierText);
11361136
}
11371137
catch (ArgumentException ae)
11381138
{
1139-
throw CreateBadDecodingError(fieldName, ae);
1139+
throw CreateBadDecodingError(fieldName, ae, value: identifierText);
11401140
}
11411141

11421142
EndField(fieldName);
@@ -1531,7 +1531,7 @@ public Enum ReadEnumerated(string fieldName, Type enumType)
15311531
}
15321532
catch (Exception ex) when (ex is ArgumentException or FormatException or OverflowException)
15331533
{
1534-
throw CreateBadDecodingError(fieldName, ex);
1534+
throw CreateBadDecodingError(fieldName, ex, value: xml);
15351535
}
15361536
}
15371537

@@ -3145,8 +3145,19 @@ private void CheckAndIncrementNestingLevel([CallerMemberName] string functionNam
31453145
private static ServiceResultException CreateBadDecodingError(
31463146
string fieldName,
31473147
Exception ex,
3148-
[CallerMemberName] string functionName = null)
3148+
[CallerMemberName] string functionName = null,
3149+
string value = null)
31493150
{
3151+
if (!string.IsNullOrEmpty(value))
3152+
{
3153+
return ServiceResultException.Create(
3154+
StatusCodes.BadDecodingError,
3155+
"Unable to read field {0} in function {1}: {2}. Value: '{3}'",
3156+
fieldName,
3157+
functionName,
3158+
ex.Message,
3159+
value);
3160+
}
31503161
return ServiceResultException.Create(
31513162
StatusCodes.BadDecodingError,
31523163
"Unable to read field {0} in function {1}: {2}",
@@ -3175,11 +3186,11 @@ private static T SafeXmlConvert<T>(
31753186
}
31763187
catch (OverflowException ove)
31773188
{
3178-
throw CreateBadDecodingError(fieldName, ove, functionName);
3189+
throw CreateBadDecodingError(fieldName, ove, functionName: functionName, value: xml);
31793190
}
31803191
catch (FormatException fe)
31813192
{
3182-
throw CreateBadDecodingError(fieldName, fe, functionName);
3193+
throw CreateBadDecodingError(fieldName, fe, functionName: functionName, value: xml);
31833194
}
31843195
}
31853196

Tests/Opc.Ua.Types.Tests/Encoders/XmlEncoderTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,5 +260,97 @@ public void EncodeDecodeVariantNil()
260260
// Check decode result against input value
261261
Assert.AreEqual(actualVariant, Variant.Null);
262262
}
263+
264+
/// <summary>
265+
/// Validate that decoding errors include the failed value in the error message for Float.
266+
/// </summary>
267+
[Test]
268+
public void DecodeInvalidFloatIncludesValueInError()
269+
{
270+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
271+
var context = new ServiceMessageContext(telemetry);
272+
const string invalidValue = "not-a-number";
273+
string xmlContent = $"<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
274+
$"<FloatTest xmlns:uax=\"http://opcfoundation.org/UA/2008/02/Types.xsd\" " +
275+
$"xmlns=\"http://opcfoundation.org/UA/2008/02/Types.xsd\">" +
276+
$"<Value>{invalidValue}</Value></FloatTest>";
277+
278+
using (var reader = XmlReader.Create(new StringReader(xmlContent)))
279+
using (var xmlDecoder = new XmlDecoder(null, reader, context))
280+
{
281+
var ex = Assert.Throws<ServiceResultException>(() => xmlDecoder.ReadFloat("Value"));
282+
Assert.That(ex.Message, Does.Contain(invalidValue));
283+
Assert.That(ex.Message, Does.Contain("Value:"));
284+
}
285+
}
286+
287+
/// <summary>
288+
/// Validate that decoding errors include the failed value in the error message for Double.
289+
/// </summary>
290+
[Test]
291+
public void DecodeInvalidDoubleIncludesValueInError()
292+
{
293+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
294+
var context = new ServiceMessageContext(telemetry);
295+
const string invalidValue = "invalid-double";
296+
string xmlContent = $"<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
297+
$"<DoubleTest xmlns:uax=\"http://opcfoundation.org/UA/2008/02/Types.xsd\" " +
298+
$"xmlns=\"http://opcfoundation.org/UA/2008/02/Types.xsd\">" +
299+
$"<Value>{invalidValue}</Value></DoubleTest>";
300+
301+
using (var reader = XmlReader.Create(new StringReader(xmlContent)))
302+
using (var xmlDecoder = new XmlDecoder(null, reader, context))
303+
{
304+
var ex = Assert.Throws<ServiceResultException>(() => xmlDecoder.ReadDouble("Value"));
305+
Assert.That(ex.Message, Does.Contain(invalidValue));
306+
Assert.That(ex.Message, Does.Contain("Value:"));
307+
}
308+
}
309+
310+
/// <summary>
311+
/// Validate that decoding errors include the failed value in the error message for DateTime.
312+
/// </summary>
313+
[Test]
314+
public void DecodeInvalidDateTimeIncludesValueInError()
315+
{
316+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
317+
var context = new ServiceMessageContext(telemetry);
318+
const string invalidValue = "not-a-date";
319+
string xmlContent = $"<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
320+
$"<DateTimeTest xmlns:uax=\"http://opcfoundation.org/UA/2008/02/Types.xsd\" " +
321+
$"xmlns=\"http://opcfoundation.org/UA/2008/02/Types.xsd\">" +
322+
$"<Value>{invalidValue}</Value></DateTimeTest>";
323+
324+
using (var reader = XmlReader.Create(new StringReader(xmlContent)))
325+
using (var xmlDecoder = new XmlDecoder(null, reader, context))
326+
{
327+
var ex = Assert.Throws<ServiceResultException>(() => xmlDecoder.ReadDateTime("Value"));
328+
Assert.That(ex.Message, Does.Contain(invalidValue));
329+
Assert.That(ex.Message, Does.Contain("Value:"));
330+
}
331+
}
332+
333+
/// <summary>
334+
/// Validate that decoding errors include the failed value in the error message for Int32.
335+
/// </summary>
336+
[Test]
337+
public void DecodeInvalidInt32IncludesValueInError()
338+
{
339+
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
340+
var context = new ServiceMessageContext(telemetry);
341+
const string invalidValue = "not-an-integer";
342+
string xmlContent = $"<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
343+
$"<Int32Test xmlns:uax=\"http://opcfoundation.org/UA/2008/02/Types.xsd\" " +
344+
$"xmlns=\"http://opcfoundation.org/UA/2008/02/Types.xsd\">" +
345+
$"<Value>{invalidValue}</Value></Int32Test>";
346+
347+
using (var reader = XmlReader.Create(new StringReader(xmlContent)))
348+
using (var xmlDecoder = new XmlDecoder(null, reader, context))
349+
{
350+
var ex = Assert.Throws<ServiceResultException>(() => xmlDecoder.ReadInt32("Value"));
351+
Assert.That(ex.Message, Does.Contain(invalidValue));
352+
Assert.That(ex.Message, Does.Contain("Value:"));
353+
}
354+
}
263355
}
264356
}

0 commit comments

Comments
 (0)