Skip to content

Commit bdfcd66

Browse files
Jim8yWi1l-B0tshargoncschuchardt88
authored
Improve StringExtensions exception messages (#4151)
* Improve StringExtensions exception messages and add comprehensive unit tests - Enhanced exception messages with detailed input information and actionable guidance - Added proper parameter names for ArgumentException and ArgumentNullException - Included input data display with appropriate truncation for debugging - Improved error messages for UTF-8 encoding/decoding operations - Enhanced hex conversion error reporting with clear validation guidance - Added comprehensive unit tests covering all exception scenarios - Verified backward compatibility (null hex strings still return empty arrays) - All 78 tests in Neo.Extensions.Tests now pass successfully Exception messages now include: 1. Specific parameter names for better debugging 2. Input data information with smart truncation 3. Actionable guidance for resolution 4. Consistent formatting across the codebase Fixes improve developer experience and debugging efficiency. * Update src/Neo.Extensions/StringExtensions.cs Co-authored-by: Christopher Schuchardt <[email protected]> --------- Co-authored-by: Will <[email protected]> Co-authored-by: Shargon <[email protected]> Co-authored-by: Christopher Schuchardt <[email protected]>
1 parent bc6f34e commit bdfcd66

File tree

2 files changed

+436
-12
lines changed

2 files changed

+436
-12
lines changed

src/Neo.Extensions/StringExtensions.cs

Lines changed: 203 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
using System;
1313
using System.Diagnostics.CodeAnalysis;
14+
using System.Linq;
1415
using System.Runtime.CompilerServices;
1516
using System.Text;
1617

@@ -56,15 +57,56 @@ public static bool TryToStrictUtf8String(this ReadOnlySpan<byte> bytes, [NotNull
5657
/// <param name="value">The byte span to convert.</param>
5758
/// <returns>The converted string.</returns>
5859
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59-
public static string ToStrictUtf8String(this ReadOnlySpan<byte> value) => StrictUTF8.GetString(value);
60+
public static string ToStrictUtf8String(this ReadOnlySpan<byte> value)
61+
{
62+
try
63+
{
64+
return StrictUTF8.GetString(value);
65+
}
66+
catch (DecoderFallbackException ex)
67+
{
68+
var bytesInfo = value.Length <= 32 ? $"Bytes: [{string.Join(", ", value.ToArray().Select(b => $"0x{b:X2}"))}]" : $"Length: {value.Length} bytes";
69+
throw new DecoderFallbackException($"Failed to decode byte span to UTF-8 string (strict mode): The input contains invalid UTF-8 byte sequences. {bytesInfo}. Ensure all bytes form valid UTF-8 character sequences.", ex);
70+
}
71+
catch (ArgumentException ex)
72+
{
73+
throw new ArgumentException("Invalid byte span provided for UTF-8 decoding. The span may be corrupted or contain invalid data.", nameof(value), ex);
74+
}
75+
catch (Exception ex)
76+
{
77+
throw new InvalidOperationException("An unexpected error occurred while decoding byte span to UTF-8 string in strict mode. This may indicate a system-level encoding issue.", ex);
78+
}
79+
}
6080

6181
/// <summary>
6282
/// Converts a byte array to a strict UTF8 string.
6383
/// </summary>
6484
/// <param name="value">The byte array to convert.</param>
6585
/// <returns>The converted string.</returns>
6686
[MethodImpl(MethodImplOptions.AggressiveInlining)]
67-
public static string ToStrictUtf8String(this byte[] value) => StrictUTF8.GetString(value);
87+
public static string ToStrictUtf8String(this byte[] value)
88+
{
89+
if (value == null)
90+
throw new ArgumentNullException(nameof(value), "Cannot decode null byte array to UTF-8 string.");
91+
92+
try
93+
{
94+
return StrictUTF8.GetString(value);
95+
}
96+
catch (DecoderFallbackException ex)
97+
{
98+
var bytesInfo = value.Length <= 32 ? $"Bytes: {BitConverter.ToString(value)}" : $"Length: {value.Length} bytes, First 16: {BitConverter.ToString(value, 0, Math.Min(16, value.Length))}...";
99+
throw new DecoderFallbackException($"Failed to decode byte array to UTF-8 string (strict mode): The input contains invalid UTF-8 byte sequences. {bytesInfo}. Ensure all bytes form valid UTF-8 character sequences.", ex);
100+
}
101+
catch (ArgumentException ex)
102+
{
103+
throw new ArgumentException("Invalid byte array provided for UTF-8 decoding. The array may be corrupted or contain invalid data.", nameof(value), ex);
104+
}
105+
catch (Exception ex)
106+
{
107+
throw new InvalidOperationException("An unexpected error occurred while decoding byte array to UTF-8 string in strict mode. This may indicate a system-level encoding issue.", ex);
108+
}
109+
}
68110

69111
/// <summary>
70112
/// Converts a byte array to a strict UTF8 string.
@@ -75,23 +117,96 @@ public static bool TryToStrictUtf8String(this ReadOnlySpan<byte> bytes, [NotNull
75117
/// <returns>The converted string.</returns>
76118
[MethodImpl(MethodImplOptions.AggressiveInlining)]
77119
public static string ToStrictUtf8String(this byte[] value, int start, int count)
78-
=> StrictUTF8.GetString(value, start, count);
120+
{
121+
if (value == null)
122+
throw new ArgumentNullException(nameof(value), "Cannot decode null byte array to UTF-8 string.");
123+
if (start < 0)
124+
throw new ArgumentOutOfRangeException(nameof(start), start, "Start index cannot be negative.");
125+
if (count < 0)
126+
throw new ArgumentOutOfRangeException(nameof(count), count, "Count cannot be negative.");
127+
if (start + count > value.Length)
128+
throw new ArgumentOutOfRangeException(nameof(count), $"The specified range [{start}, {start + count}) exceeds the array bounds (length: {value.Length}). Ensure start + count <= array.Length.");
129+
130+
try
131+
{
132+
return StrictUTF8.GetString(value, start, count);
133+
}
134+
catch (DecoderFallbackException ex)
135+
{
136+
var rangeBytes = new byte[count];
137+
Array.Copy(value, start, rangeBytes, 0, count);
138+
var bytesInfo = count <= 32 ? $"Bytes: {BitConverter.ToString(rangeBytes)}" : $"Length: {count} bytes, First 16: {BitConverter.ToString(rangeBytes, 0, Math.Min(16, count))}...";
139+
throw new DecoderFallbackException($"Failed to decode byte array range [{start}, {start + count}) to UTF-8 string (strict mode): The input contains invalid UTF-8 byte sequences. {bytesInfo}. Ensure all bytes form valid UTF-8 character sequences.", ex);
140+
}
141+
catch (ArgumentException ex)
142+
{
143+
throw new ArgumentException($"Invalid parameters provided for UTF-8 decoding. Array length: {value.Length}, Start: {start}, Count: {count}.", ex);
144+
}
145+
catch (Exception ex)
146+
{
147+
throw new InvalidOperationException($"An unexpected error occurred while decoding byte array range [{start}, {start + count}) to UTF-8 string in strict mode. This may indicate a system-level encoding issue.", ex);
148+
}
149+
}
79150

80151
/// <summary>
81152
/// Converts a string to a strict UTF8 byte array.
82153
/// </summary>
83154
/// <param name="value">The string to convert.</param>
84155
/// <returns>The converted byte array.</returns>
85156
[MethodImpl(MethodImplOptions.AggressiveInlining)]
86-
public static byte[] ToStrictUtf8Bytes(this string value) => StrictUTF8.GetBytes(value);
157+
public static byte[] ToStrictUtf8Bytes(this string value)
158+
{
159+
if (value == null)
160+
throw new ArgumentNullException(nameof(value), "Cannot encode null string to UTF-8 bytes.");
161+
162+
try
163+
{
164+
return StrictUTF8.GetBytes(value);
165+
}
166+
catch (EncoderFallbackException ex)
167+
{
168+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters, First 50: '{value[..50]}...'";
169+
throw new EncoderFallbackException($"Failed to encode string to UTF-8 bytes (strict mode): The input contains characters that cannot be encoded in UTF-8. {valueInfo}. Ensure the string contains only valid Unicode characters.", ex);
170+
}
171+
catch (ArgumentException ex)
172+
{
173+
throw new ArgumentException("Invalid string provided for UTF-8 encoding. The string may contain unsupported characters.", nameof(value), ex);
174+
}
175+
catch (Exception ex)
176+
{
177+
throw new InvalidOperationException("An unexpected error occurred while encoding string to UTF-8 bytes in strict mode. This may indicate a system-level encoding issue.", ex);
178+
}
179+
}
87180

88181
/// <summary>
89182
/// Gets the size of the specified <see cref="string"/> encoded in strict UTF8.
90183
/// </summary>
91184
/// <param name="value">The specified <see cref="string"/>.</param>
92185
/// <returns>The size of the <see cref="string"/>.</returns>
93186
[MethodImpl(MethodImplOptions.AggressiveInlining)]
94-
public static int GetStrictUtf8ByteCount(this string value) => StrictUTF8.GetByteCount(value);
187+
public static int GetStrictUtf8ByteCount(this string value)
188+
{
189+
if (value == null)
190+
throw new ArgumentNullException(nameof(value), "Cannot get UTF-8 byte count for null string.");
191+
192+
try
193+
{
194+
return StrictUTF8.GetByteCount(value);
195+
}
196+
catch (EncoderFallbackException ex)
197+
{
198+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters, First 50: '{value[..50]}...'";
199+
throw new EncoderFallbackException($"Failed to get UTF-8 byte count for string (strict mode): The input contains characters that cannot be encoded in UTF-8. {valueInfo}. Ensure the string contains only valid Unicode characters.", ex);
200+
}
201+
catch (ArgumentException ex)
202+
{
203+
throw new ArgumentException("Invalid string provided for UTF-8 byte count calculation. The string may contain unsupported characters.", nameof(value), ex);
204+
}
205+
catch (Exception ex)
206+
{
207+
throw new InvalidOperationException("An unexpected error occurred while calculating UTF-8 byte count for string in strict mode. This may indicate a system-level encoding issue.", ex);
208+
}
209+
}
95210

96211
/// <summary>
97212
/// Determines if the specified <see cref="string"/> is a valid hex string.
@@ -119,7 +234,31 @@ public static bool IsHex(this string value)
119234
/// <param name="value">The hex <see cref="string"/> to convert.</param>
120235
/// <returns>The converted byte array.</returns>
121236
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122-
public static byte[] HexToBytes(this string? value) => HexToBytes(value.AsSpan());
237+
public static byte[] HexToBytes(this string? value)
238+
{
239+
if (value == null)
240+
return [];
241+
242+
try
243+
{
244+
return HexToBytes(value.AsSpan());
245+
}
246+
catch (ArgumentException ex)
247+
{
248+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
249+
throw new ArgumentException($"Failed to convert hex string to bytes: The input has an invalid length (must be even) or contains non-hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", nameof(value), ex);
250+
}
251+
catch (FormatException ex)
252+
{
253+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
254+
throw new FormatException($"Failed to convert hex string to bytes: The input contains invalid hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", ex);
255+
}
256+
catch (Exception ex)
257+
{
258+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
259+
throw new InvalidOperationException($"An unexpected error occurred while converting hex string to bytes. {valueInfo}. This may indicate a system-level parsing issue.", ex);
260+
}
261+
}
123262

124263
/// <summary>
125264
/// Converts a hex <see cref="string"/> to byte array then reverses the order of the bytes.
@@ -129,9 +268,27 @@ public static bool IsHex(this string value)
129268
[MethodImpl(MethodImplOptions.AggressiveInlining)]
130269
public static byte[] HexToBytesReversed(this ReadOnlySpan<char> value)
131270
{
132-
var data = HexToBytes(value);
133-
Array.Reverse(data);
134-
return data;
271+
try
272+
{
273+
var data = HexToBytes(value);
274+
Array.Reverse(data);
275+
return data;
276+
}
277+
catch (ArgumentException ex)
278+
{
279+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
280+
throw new ArgumentException($"Failed to convert hex span to reversed bytes: The input has an invalid length (must be even) or contains non-hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", ex);
281+
}
282+
catch (FormatException ex)
283+
{
284+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
285+
throw new FormatException($"Failed to convert hex span to reversed bytes: The input contains invalid hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", ex);
286+
}
287+
catch (Exception ex)
288+
{
289+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
290+
throw new InvalidOperationException($"An unexpected error occurred while converting hex span to reversed bytes. {valueInfo}. This may indicate a system-level parsing or array manipulation issue.", ex);
291+
}
135292
}
136293

137294
/// <summary>
@@ -141,7 +298,25 @@ public static byte[] HexToBytesReversed(this ReadOnlySpan<char> value)
141298
/// <returns>The converted byte array.</returns>
142299
public static byte[] HexToBytes(this ReadOnlySpan<char> value)
143300
{
144-
return Convert.FromHexString(value);
301+
try
302+
{
303+
return Convert.FromHexString(value);
304+
}
305+
catch (ArgumentException ex)
306+
{
307+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
308+
throw new ArgumentException($"Failed to convert hex span to bytes: The input has an invalid length (must be even) or contains non-hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", ex);
309+
}
310+
catch (FormatException ex)
311+
{
312+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
313+
throw new FormatException($"Failed to convert hex span to bytes: The input contains invalid hexadecimal characters. {valueInfo}. Valid hex characters are 0-9, A-F, and a-f.", ex);
314+
}
315+
catch (Exception ex)
316+
{
317+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
318+
throw new InvalidOperationException($"An unexpected error occurred while converting hex span to bytes. {valueInfo}. This may indicate a system-level parsing issue.", ex);
319+
}
145320
}
146321

147322
/// <summary>
@@ -151,8 +326,24 @@ public static byte[] HexToBytes(this ReadOnlySpan<char> value)
151326
/// <returns>The size of the <see cref="string"/>.</returns>
152327
public static int GetVarSize(this string value)
153328
{
154-
var size = value.GetStrictUtf8ByteCount();
155-
return size.GetVarSize() + size;
329+
if (value == null)
330+
throw new ArgumentNullException(nameof(value), "Cannot calculate variable size for null string.");
331+
332+
try
333+
{
334+
var size = value.GetStrictUtf8ByteCount();
335+
return size.GetVarSize() + size;
336+
}
337+
catch (EncoderFallbackException ex)
338+
{
339+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters, First 50: '{value[..50]}...'";
340+
throw new EncoderFallbackException($"Failed to calculate variable size: The string contains characters that cannot be encoded in UTF-8 (strict mode). {valueInfo}. Ensure the string contains only valid Unicode characters.", ex);
341+
}
342+
catch (Exception ex)
343+
{
344+
var valueInfo = value.Length <= 100 ? $"Input: '{value}'" : $"Input length: {value.Length} characters";
345+
throw new InvalidOperationException($"An unexpected error occurred while calculating variable size for string. {valueInfo}. This may indicate an issue with the string encoding or variable size calculation.", ex);
346+
}
156347
}
157348

158349
/// <summary>

0 commit comments

Comments
 (0)