diff --git a/CHANGELOG.md b/CHANGELOG.md index 4638b9e..8a21db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - .NET 8.0: `System.Net.IPNetwork` - Moved `MessagePack` formatter extensions to own package `AMWD.Common.MessagePack` - `GetAlignedInterval()` (without Local/Utc) is now public accessible +- Using native `Convert.FromHexString` on .NET 8.0 on `HexToBytes` extension ### Removed diff --git a/src/AMWD.Common/Extensions/StringExtensions.cs b/src/AMWD.Common/Extensions/StringExtensions.cs index bb50e3a..4952ce4 100644 --- a/src/AMWD.Common/Extensions/StringExtensions.cs +++ b/src/AMWD.Common/Extensions/StringExtensions.cs @@ -22,6 +22,8 @@ namespace System public static class StringExtensions #endif { + private const string EmailRegex = @"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$"; + /// /// Converts a hex string into a byte array. /// @@ -30,23 +32,28 @@ namespace System /// The bytes. public static IEnumerable HexToBytes(this string hexString, string delimiter = "") { - if (string.IsNullOrWhiteSpace(hexString)) - yield break; + if (hexString == null) + throw new ArgumentNullException(nameof(hexString)); - string str = string.IsNullOrEmpty(delimiter) ? hexString : hexString.Replace(delimiter, ""); - if (str.Length % 2 == 1) - yield break; + string str = string.IsNullOrEmpty(delimiter) + ? hexString + : hexString.Replace(delimiter, ""); #if NET8_0_OR_GREATER - if (InvalidHexCharRegex().IsMatch(str)) - yield break; + return Convert.FromHexString(str); #else if (Regex.IsMatch(str, "[^0-9a-fA-F]", RegexOptions.Compiled)) - yield break; -#endif + throw new FormatException("The input is not a valid hex string as it contains a non-hex character."); - for (int i = 0; i < str.Length; i += 2) - yield return Convert.ToByte(str.Substring(i, 2), 16); + if (str.Length % 2 != 0) + throw new FormatException("The input is not a valid hex string as its length is not a multiple of 2."); + + byte[] bytes = new byte[str.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16); + + return bytes; +#endif } /// @@ -193,8 +200,7 @@ namespace System if (!ValidEmailRegex().IsMatch(emailAddress)) return false; #else - string emailRegex = @"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$"; - if (!Regex.IsMatch(emailAddress, emailRegex, RegexOptions.Compiled)) + if (!Regex.IsMatch(emailAddress, EmailRegex, RegexOptions.Compiled)) return false; #endif @@ -248,10 +254,7 @@ namespace System => sb.Append(value).Append(newLine); #if NET8_0_OR_GREATER - [GeneratedRegex("[^0-9a-fA-F]", RegexOptions.Compiled)] - private static partial Regex InvalidHexCharRegex(); - - [GeneratedRegex(@"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$", RegexOptions.Compiled)] + [GeneratedRegex(EmailRegex, RegexOptions.Compiled)] private static partial Regex ValidEmailRegex(); #endif } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 21669d9..0c023be 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -38,7 +38,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs b/test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs index 49fbfe2..87f718a 100644 --- a/test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs +++ b/test/AMWD.Common.Tests/Extensions/StringExtensionsTest.cs @@ -8,6 +8,19 @@ namespace AMWD.Common.Tests.Extensions [TestClass] public class StringExtensionsTest { + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionWhenNull() + { + // arrange + string hex = null; + + // act + var bytes = hex.HexToBytes(); + + // assert - ArgumentNullException + } + [TestMethod] public void ShouldReturnEmptyList() { @@ -22,28 +35,19 @@ namespace AMWD.Common.Tests.Extensions Assert.IsFalse(bytes.Any()); } - [TestMethod] - public void ShouldReturnEmptyListWhenInvalid() + [DataTestMethod] + [DataRow("aff", null)] + [DataRow("de:ad:be:e", ":")] + [DataRow("hell", "")] + [ExpectedException(typeof(FormatException))] + public void ShouldThrowFormatExceptionWhenInvalid(string hex, string delimiter) { // arrange - string hex1 = "aff"; - string hex2 = "de:ad:be:e"; - string hex3 = "hell"; // act - var bytes1 = hex1.HexToBytes(); - var bytes2 = hex2.HexToBytes(":"); - var bytes3 = hex3.HexToBytes(); + var bytes = hex.HexToBytes(delimiter); - // assert - Assert.IsNotNull(bytes1); - Assert.IsFalse(bytes1.Any()); - - Assert.IsNotNull(bytes2); - Assert.IsFalse(bytes2.Any()); - - Assert.IsNotNull(bytes3); - Assert.IsFalse(bytes3.Any()); + // assert - FormatException } [TestMethod]