diff --git a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs
index ff4cf9e351a..19f49deb0ae 100644
--- a/src/Polly/RateLimit/AsyncRateLimitPolicy.cs
+++ b/src/Polly/RateLimit/AsyncRateLimitPolicy.cs
@@ -4,7 +4,6 @@ namespace Polly.RateLimit;
///
/// A rate-limit policy that can be applied to asynchronous delegates.
///
-#pragma warning disable CA1062 // Validate arguments of public methods
public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy
{
private readonly IRateLimiter _rateLimiter;
@@ -14,9 +13,25 @@ internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) =>
///
[DebuggerStepThrough]
- protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken,
- bool continueOnCapturedContext) =>
- AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, null, action, context, continueOnCapturedContext, cancellationToken);
+ protected override Task ImplementationAsync(
+ Func> action,
+ Context context,
+ CancellationToken cancellationToken,
+ bool continueOnCapturedContext)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return AsyncRateLimitEngine.ImplementationAsync(
+ _rateLimiter,
+ null,
+ action,
+ context,
+ continueOnCapturedContext,
+ cancellationToken);
+ }
}
///
@@ -38,7 +53,23 @@ internal AsyncRateLimitPolicy(
///
[DebuggerStepThrough]
- protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken,
- bool continueOnCapturedContext) =>
- AsyncRateLimitEngine.ImplementationAsync(_rateLimiter, _retryAfterFactory, action, context, continueOnCapturedContext, cancellationToken);
+ protected override Task ImplementationAsync(
+ Func> action,
+ Context context,
+ CancellationToken cancellationToken,
+ bool continueOnCapturedContext)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return AsyncRateLimitEngine.ImplementationAsync(
+ _rateLimiter,
+ _retryAfterFactory,
+ action,
+ context,
+ continueOnCapturedContext,
+ cancellationToken);
+ }
}
diff --git a/test/Polly.Specs/NoOp/NoOpSpecs.cs b/test/Polly.Specs/NoOp/NoOpSpecs.cs
index 6ff214de8c7..1bc37b87b5b 100644
--- a/test/Polly.Specs/NoOp/NoOpSpecs.cs
+++ b/test/Polly.Specs/NoOp/NoOpSpecs.cs
@@ -18,7 +18,8 @@ public void Should_throw_when_action_is_null()
var exceptionAssertions = func.Should().Throw();
exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
- exceptionAssertions.WithInnerException("action");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
}
[Fact]
diff --git a/test/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs b/test/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs
index 03e24cf3c16..3121fd9ae80 100644
--- a/test/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs
+++ b/test/Polly.Specs/NoOp/NoOpTResultAsyncSpecs.cs
@@ -17,7 +17,8 @@ public void Should_throw_when_action_is_null()
var exceptionAssertions = func.Should().Throw();
exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
- exceptionAssertions.WithInnerException("action");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
}
[Fact]
diff --git a/test/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs b/test/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs
index 85d2b1dc413..cf1321d244c 100644
--- a/test/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs
+++ b/test/Polly.Specs/RateLimit/AsyncRateLimitPolicySpecs.cs
@@ -31,4 +31,30 @@ protected override (bool, TimeSpan) TryExecuteThroughPolicy(IRateLimitPolicy pol
throw new InvalidOperationException("Unexpected policy type in test construction.");
}
}
+
+ [Fact]
+ public void Should_throw_when_action_is_null()
+ {
+ var flags = BindingFlags.NonPublic | BindingFlags.Instance;
+ Func> action = null!;
+ IRateLimiter rateLimiter = RateLimiterFactory.Create(TimeSpan.FromSeconds(1), 1);
+
+ var instance = Activator.CreateInstance(
+ typeof(AsyncRateLimitPolicy),
+ flags,
+ null,
+ [rateLimiter],
+ null)!;
+ var instanceType = instance.GetType();
+ var methods = instanceType.GetMethods(flags);
+ var methodInfo = methods.First(method => method is { Name: "ImplementationAsync", ReturnType.Name: "Task`1" });
+ var generic = methodInfo.MakeGenericMethod(typeof(EmptyStruct));
+
+ var func = () => generic.Invoke(instance, [action, new Context(), CancellationToken.None, false]);
+
+ var exceptionAssertions = func.Should().Throw();
+ exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
+ }
}
diff --git a/test/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs b/test/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs
index 00bbe741218..2fc2250fe4d 100644
--- a/test/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs
+++ b/test/Polly.Specs/RateLimit/AsyncRateLimitPolicyTResultSpecs.cs
@@ -47,4 +47,30 @@ protected override TResult TryExecuteThroughPolicy(IRateLimitPolicy> action = null!;
+ IRateLimiter rateLimiter = RateLimiterFactory.Create(TimeSpan.FromSeconds(1), 1);
+ Func? retryAfterFactory = null!;
+
+ var instance = Activator.CreateInstance(
+ typeof(AsyncRateLimitPolicy),
+ flags,
+ null,
+ [rateLimiter, retryAfterFactory],
+ null)!;
+ var instanceType = instance.GetType();
+ var methods = instanceType.GetMethods(flags);
+ var methodInfo = methods.First(method => method is { Name: "ImplementationAsync", ReturnType.Name: "Task`1" });
+
+ var func = () => methodInfo.Invoke(instance, [action, new Context(), CancellationToken.None, false]);
+
+ var exceptionAssertions = func.Should().Throw();
+ exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
+ }
}