diff --git a/src/Ramstack.HtmxToolkit/Argument.cs b/src/Ramstack.HtmxToolkit/Argument.cs new file mode 100644 index 0000000..129760b --- /dev/null +++ b/src/Ramstack.HtmxToolkit/Argument.cs @@ -0,0 +1,73 @@ +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Ramstack.HtmxToolkit; + +/// +/// Provides helper methods to validate arguments and throw exceptions if the arguments do not meet specified conditions. +/// +internal static class Argument +{ + /// + /// Throws an if the specified object is . + /// + /// The object to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value is null) + Error_ArgumentNull(paramName); + } + + /// + /// Throws an if the specified string is , + /// or an if it is empty. + /// + /// The string to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (string.IsNullOrEmpty(value)) + Error_ArgumentNullOrEmpty(value, paramName); + } + + /// + /// Throws an if the specified string is , + /// or an if it is empty or consists only of white-space characters. + /// + /// The string to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNullOrWhiteSpace([NotNull] string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (string.IsNullOrWhiteSpace(value)) + Error_ArgumentNullOrWhiteSpace(value, paramName); + } + + [DoesNotReturn] + private static void Error_ArgumentNull(string? paramName) => + throw new ArgumentNullException(paramName); + + [DoesNotReturn] + private static void Error_ArgumentNullOrEmpty(string? value, string? paramName) + { + if (value is null) + throw new ArgumentNullException(paramName); + + throw new ArgumentException( + "Argument should not be empty.", + paramName); + } + + [DoesNotReturn] + private static void Error_ArgumentNullOrWhiteSpace(string? value, string? paramName) + { + if (value is null) + throw new ArgumentNullException(paramName); + + throw new ArgumentException( + "Argument should not be empty or whitespace.", + paramName); + } +} diff --git a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs index a43bd72..a112653 100644 --- a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs +++ b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs @@ -77,7 +77,7 @@ public TValue this[TKey key] /// The comparer to use when comparing keys. public SmallDictionary(IComparer comparer) { - ArgumentNullException.ThrowIfNull(comparer); + Argument.ThrowIfNull(comparer); _comparer = comparer; } @@ -90,7 +90,7 @@ public SmallDictionary(IComparer comparer) /// The comparer to use when comparing keys. public SmallDictionary(IEnumerable> collection, IComparer comparer) : this(comparer) { - ArgumentNullException.ThrowIfNull(collection); + Argument.ThrowIfNull(collection); if (collection is Dictionary dictionary) { diff --git a/src/Ramstack.HtmxToolkit/HtmxResponse.cs b/src/Ramstack.HtmxToolkit/HtmxResponse.cs index 301e319..fc2e33b 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponse.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponse.cs @@ -95,8 +95,7 @@ static HtmxResponse LocationImpl(HtmxResponse response, string path, HtmxLocatio /// public HtmxResponse PushUrl(string url) { - if (string.IsNullOrEmpty(url)) - throw new ArgumentException("URL cannot be null or empty.", nameof(url)); + Argument.ThrowIfNullOrEmpty(url); return SetHistoryHeader(this, HtmxResponseHeaderNames.PushUrl, url, HtmxResponseHeaderNames.ReplaceUrl); } @@ -127,8 +126,7 @@ public HtmxResponse PushUrl(string url) /// public HtmxResponse ReplaceUrl(string url) { - if (string.IsNullOrEmpty(url)) - throw new ArgumentException("URL cannot be null or empty.", nameof(url)); + Argument.ThrowIfNullOrEmpty(url); return SetHistoryHeader(this, HtmxResponseHeaderNames.ReplaceUrl, url, HtmxResponseHeaderNames.PushUrl); } @@ -344,7 +342,7 @@ private static HtmxResponse TriggerEventCore(HtmxResponse response, string ev private static string SerializeEventDetail(T detail, JsonTypeInfo jsonTypeInfo) { - ArgumentNullException.ThrowIfNull(jsonTypeInfo); + Argument.ThrowIfNull(jsonTypeInfo); var buffer = new ArrayBufferWriter(); using (var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Encoder = JsonOptions.Encoder, SkipValidation = true })) diff --git a/src/Ramstack.HtmxToolkit/Internal/PendingEvents.cs b/src/Ramstack.HtmxToolkit/Internal/PendingEvents.cs index 5f02f24..5fa0ac1 100644 --- a/src/Ramstack.HtmxToolkit/Internal/PendingEvents.cs +++ b/src/Ramstack.HtmxToolkit/Internal/PendingEvents.cs @@ -49,11 +49,8 @@ private PendingEvents(HttpResponse response) => /// The event detail as JSON. public void AddEvent(HtmxTriggerTiming timing, string eventName, string detailJson) { - if (string.IsNullOrWhiteSpace(eventName)) - throw new ArgumentException("Event name cannot be null or whitespace.", nameof(eventName)); - - if (string.IsNullOrWhiteSpace(detailJson)) - throw new ArgumentException("Event detail JSON cannot be null or whitespace.", nameof(detailJson)); + Argument.ThrowIfNullOrWhiteSpace(eventName); + Argument.ThrowIfNullOrWhiteSpace(detailJson); if (eventName == ProxyEventName) throw new ArgumentException( diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs index 4940819..3a4be18 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxResponseTests.cs @@ -353,7 +353,7 @@ public void ReplaceUrl_NullOrEmpty_ThrowsArgumentException(string? url) { var context = TestHelper.CreateHtmxRequestContext(); - var exception = Assert.Throws(() => + var exception = Assert.Catch(() => context.Response.Htmx(r => r.ReplaceUrl(url!))); Assert.That(exception?.ParamName, Is.EqualTo("url")); diff --git a/tests/Ramstack.HtmxToolkit.Tests/Internal/PendingEventsTests.cs b/tests/Ramstack.HtmxToolkit.Tests/Internal/PendingEventsTests.cs index cdaa9e9..b6dc210 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/Internal/PendingEventsTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/Internal/PendingEventsTests.cs @@ -357,7 +357,7 @@ public void AddEvent_NullOrWhitespaceDetail_ThrowsArgumentException(string? deta var context = TestHelper.CreateHttpContext(); var pending = PendingEvents.GetOrCreate(context.Response); - var exception = Assert.Throws(() => + var exception = Assert.Catch(() => pending.AddEvent(HtmxTriggerTiming.Receive, "e", detail!)); Assert.That(exception?.ParamName, Is.EqualTo("detailJson")); @@ -371,7 +371,7 @@ public void AddEvent_NullOrWhitespaceEventName_ThrowsArgumentException(string? e var context = TestHelper.CreateHttpContext(); var pending = PendingEvents.GetOrCreate(context.Response); - var exception = Assert.Throws(() => + var exception = Assert.Catch(() => pending.AddEvent(HtmxTriggerTiming.Receive, eventName!, "1")); Assert.That(exception?.ParamName, Is.EqualTo("eventName"));