diff --git a/src/Polly/Caching/AsyncCacheSyntax.cs b/src/Polly/Caching/AsyncCacheSyntax.cs index 983e74b46d6..da9812acc75 100644 --- a/src/Polly/Caching/AsyncCacheSyntax.cs +++ b/src/Polly/Caching/AsyncCacheSyntax.cs @@ -1,7 +1,6 @@ #nullable enable namespace Polly; -#pragma warning disable CA1062 // Validate arguments of public methods public partial class Policy { /// @@ -46,8 +45,15 @@ public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, ITt /// The policy instance. /// Thrown when is . /// Thrown when is . - public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action? onCacheError = null) => - CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + public static AsyncCachePolicy CacheAsync(IAsyncCacheProvider cacheProvider, TimeSpan ttl, ICacheKeyStrategy cacheKeyStrategy, Action? onCacheError = null) + { + if (cacheKeyStrategy is null) + { + throw new ArgumentNullException(nameof(cacheKeyStrategy)); + } + + return CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheError); + } /// /// Builds an that will function like a result cache for delegate executions returning a result. @@ -222,8 +228,22 @@ public static AsyncCachePolicy CacheAsync( Action onCacheMiss, Action onCachePut, Action? onCacheGetError, - Action? onCachePutError) => - CacheAsync(cacheProvider, new RelativeTtl(ttl), cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + Action? onCachePutError) + { + if (cacheKeyStrategy is null) + { + throw new ArgumentNullException(nameof(cacheKeyStrategy)); + } + + return CacheAsync(cacheProvider, + new RelativeTtl(ttl), + cacheKeyStrategy.GetCacheKey, + onCacheGet, + onCacheMiss, + onCachePut, + onCacheGetError, + onCachePutError); + } /// /// Builds an that will function like a result cache for delegate executions returning a result. @@ -254,8 +274,23 @@ public static AsyncCachePolicy CacheAsync( Action onCacheMiss, Action onCachePut, Action? onCacheGetError, - Action? onCachePutError) => - CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy.GetCacheKey, onCacheGet, onCacheMiss, onCachePut, onCacheGetError, onCachePutError); + Action? onCachePutError) + { + if (cacheKeyStrategy is null) + { + throw new ArgumentNullException(nameof(cacheKeyStrategy)); + } + + return CacheAsync( + cacheProvider, + ttlStrategy, + cacheKeyStrategy.GetCacheKey, + onCacheGet, + onCacheMiss, + onCachePut, + onCacheGetError, + onCachePutError); + } /// /// Builds an that will function like a result cache for delegate executions returning a result. diff --git a/test/Polly.Specs/Caching/CacheAsyncSpecs.cs b/test/Polly.Specs/Caching/CacheAsyncSpecs.cs index a8efe655927..3e9163bfeb7 100644 --- a/test/Polly.Specs/Caching/CacheAsyncSpecs.cs +++ b/test/Polly.Specs/Caching/CacheAsyncSpecs.cs @@ -62,8 +62,49 @@ public void Should_throw_when_action_is_null() public void Should_throw_when_cache_provider_is_null() { IAsyncCacheProvider cacheProvider = null!; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue); - action.Should().Throw().And.ParamName.Should().Be("cacheProvider"); + var ttl = TimeSpan.MaxValue; + ITtlStrategy ttlStrategy = new ContextualTtl(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + Func cacheKeyStrategyFunc = (_) => string.Empty; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string CacheProviderExpected = "cacheProvider"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttl, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheProviderExpected); } [Fact] @@ -71,17 +112,194 @@ public void Should_throw_when_ttl_strategy_is_null() { IAsyncCacheProvider cacheProvider = new StubCacheProvider(); ITtlStrategy ttlStrategy = null!; - Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy); - action.Should().Throw().And.ParamName.Should().Be("ttlStrategy"); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + Func cacheKeyStrategyFunc = (_) => string.Empty; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string TtlStrategyExpected = "ttlStrategy"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCache, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(TtlStrategyExpected); } [Fact] public void Should_throw_when_cache_key_strategy_is_null() { IAsyncCacheProvider cacheProvider = new StubCacheProvider(); - Func cacheKeyStrategy = null!; - Action action = () => Policy.CacheAsync(cacheProvider, TimeSpan.MaxValue, cacheKeyStrategy); - action.Should().Throw().And.ParamName.Should().Be("cacheKeyStrategy"); + var ttl = TimeSpan.MaxValue; + ITtlStrategy ttlStrategy = new ContextualTtl(); + ICacheKeyStrategy cacheKeyStrategy = null!; + Func cacheKeyStrategyFunc = null!; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string CacheKeyStrategyExpected = "cacheKeyStrategy"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync( + cacheProvider, + ttl, + cacheKeyStrategy, + onCache, + onCache, + onCache, + onCacheError, + onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync( + cacheProvider, + ttlStrategy, + cacheKeyStrategy, + onCache, + onCache, + onCache, + onCacheError, + onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync( + cacheProvider, + ttl, + cacheKeyStrategyFunc, + onCache, + onCache, + onCache, + onCacheError, + onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + + action = () => Policy.CacheAsync( + cacheProvider, + ttlStrategy, + cacheKeyStrategyFunc, + onCache, + onCache, + onCache, + onCacheError, + onCacheError); + action.Should().Throw().And.ParamName.Should().Be(CacheKeyStrategyExpected); + } + + [Fact] + public void Should_throw_when_on_cache_get_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.MaxValue; + ITtlStrategy ttlStrategy = new ContextualTtl(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + Func cacheKeyStrategyFunc = (_) => string.Empty; + Action onCacheGet = null!; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string OnCacheGetExpected = "onCacheGet"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttl, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCacheGet, onCache, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheGetExpected); + } + + [Fact] + public void Should_throw_when_on_cache_miss_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.MaxValue; + ITtlStrategy ttlStrategy = new ContextualTtl(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + Func cacheKeyStrategyFunc = (_) => string.Empty; + Action onCacheMiss = null!; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string OnCacheMissExpected = "onCacheMiss"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttl, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCache, onCacheMiss, onCache, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCacheMissExpected); + } + + [Fact] + public void Should_throw_when_on_cache_put_is_null() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + var ttl = TimeSpan.MaxValue; + ITtlStrategy ttlStrategy = new ContextualTtl(); + ICacheKeyStrategy cacheKeyStrategy = new StubCacheKeyStrategy(context => context.OperationKey + context["id"]); + Func cacheKeyStrategyFunc = (_) => string.Empty; + Action onCachePut = null!; + Action onCache = (_, _) => { }; + Action? onCacheError = null; + const string OnCachePutExpected = "onCachePut"; + + Action action = () => Policy.CacheAsync(cacheProvider, ttl, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategy, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategy, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttl, cacheKeyStrategyFunc, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); + + action = () => Policy.CacheAsync(cacheProvider, ttlStrategy, cacheKeyStrategyFunc, onCache, onCache, onCachePut, onCacheError, onCacheError); + action.Should().Throw().And.ParamName.Should().Be(OnCachePutExpected); } #endregion