Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should update my automation PR to move things to the Shipped file too...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static Polly.ResiliencePipelineBuilderExtensions.AddStrategy(this Polly.ResiliencePipelineBuilder! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ResilienceStrategy<object!>!>! factory) -> Polly.ResiliencePipelineBuilder!
static Polly.ResiliencePipelineBuilderExtensions.AddStrategy<TBuilder>(this TBuilder! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ResilienceStrategy!>! factory) -> TBuilder!
static Polly.ResiliencePipelineBuilderExtensions.AddStrategy<TResult>(this Polly.ResiliencePipelineBuilder<TResult>! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ResilienceStrategy<TResult>!>! factory) -> Polly.ResiliencePipelineBuilder<TResult>!
89 changes: 83 additions & 6 deletions src/Polly.Core/ResiliencePipelineBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,16 @@ public static ResiliencePipelineBuilder<TResult> AddPipeline<TResult>(this Resil
/// <typeparam name="TBuilder">The builder type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The factory that creates a resilience strategy.</param>
/// <param name="options">The options associated with the strategy. If none are provided the default instance of <see cref="ResilienceStrategyOptions"/> is created.</param>
/// <param name="options">The options associated with the strategy.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="factory"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> is invalid.</exception>
[RequiresUnreferencedCode(Constants.OptionsValidation)]
public static TBuilder AddStrategy<TBuilder>(this TBuilder builder, Func<StrategyBuilderContext, ResilienceStrategy> factory, ResilienceStrategyOptions options)
public static TBuilder AddStrategy<TBuilder>(
this TBuilder builder,
Func<StrategyBuilderContext, ResilienceStrategy> factory,
ResilienceStrategyOptions options)
where TBuilder : ResiliencePipelineBuilderBase
{
Guard.NotNull(builder);
Expand All @@ -82,14 +85,15 @@ public static TBuilder AddStrategy<TBuilder>(this TBuilder builder, Func<Strateg
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The factory that creates a resilience strategy.</param>
/// <param name="options">The options associated with the strategy. If none are provided the default instance of <see cref="ResilienceStrategyOptions"/> is created.</param>
/// <param name="options">The options associated with the strategy.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="factory"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> is invalid.</exception>
[RequiresUnreferencedCode(Constants.OptionsValidation)]
public static ResiliencePipelineBuilder AddStrategy(
this ResiliencePipelineBuilder builder, Func<StrategyBuilderContext, ResilienceStrategy<object>> factory,
this ResiliencePipelineBuilder builder,
Func<StrategyBuilderContext, ResilienceStrategy<object>> factory,
ResilienceStrategyOptions options)
{
Guard.NotNull(builder);
Expand All @@ -106,14 +110,15 @@ public static ResiliencePipelineBuilder AddStrategy(
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The factory that creates a resilience strategy.</param>
/// <param name="options">The options associated with the strategy. If none are provided the default instance of <see cref="ResilienceStrategyOptions"/> is created.</param>
/// <param name="options">The options associated with the strategy.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="factory"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> is invalid.</exception>
[RequiresUnreferencedCode(Constants.OptionsValidation)]
public static ResiliencePipelineBuilder<TResult> AddStrategy<TResult>(
this ResiliencePipelineBuilder<TResult> builder, Func<StrategyBuilderContext, ResilienceStrategy<TResult>> factory,
this ResiliencePipelineBuilder<TResult> builder,
Func<StrategyBuilderContext, ResilienceStrategy<TResult>> factory,
ResilienceStrategyOptions options)
{
Guard.NotNull(builder);
Expand All @@ -124,6 +129,78 @@ public static ResiliencePipelineBuilder<TResult> AddStrategy<TResult>(
return builder;
}

/// <summary>
/// Adds a reactive resilience strategy to the builder.
/// </summary>
/// <typeparam name="TBuilder">The builder type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The strategy factory.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(EmptyOptions))]
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved for the empty options.")]
public static TBuilder AddStrategy<TBuilder>(
this TBuilder builder,
Func<StrategyBuilderContext, ResilienceStrategy> factory)
where TBuilder : ResiliencePipelineBuilderBase
{
Guard.NotNull(builder);
Guard.NotNull(factory);

return builder.AddStrategy(factory, EmptyOptions.Instance);
}

/// <summary>
/// Adds a proactive resilience strategy to the builder.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The strategy instance.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(EmptyOptions))]
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved for the empty options.")]
public static ResiliencePipelineBuilder AddStrategy(
this ResiliencePipelineBuilder builder,
Func<StrategyBuilderContext, ResilienceStrategy<object>> factory)
{
Guard.NotNull(builder);
Guard.NotNull(factory);

return builder.AddStrategy(factory, EmptyOptions.Instance);
}

/// <summary>
/// Adds a reactive resilience strategy to the builder.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="factory">The strategy instance.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a pipeline. The builder cannot be modified after it has been used.</exception>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(EmptyOptions))]
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved for the empty options.")]
public static ResiliencePipelineBuilder<TResult> AddStrategy<TResult>(
this ResiliencePipelineBuilder<TResult> builder,
Func<StrategyBuilderContext, ResilienceStrategy<TResult>> factory)
{
Guard.NotNull(builder);
Guard.NotNull(factory);

return builder.AddStrategy(factory, EmptyOptions.Instance);
}

internal sealed class EmptyOptions : ResilienceStrategyOptions
{
public static readonly EmptyOptions Instance = new();
Expand Down
16 changes: 16 additions & 0 deletions test/Polly.Core.Tests/GenericResiliencePipelineBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Polly.Testing;
using Polly.Utils.Pipeline;

namespace Polly.Core.Tests;
Expand Down Expand Up @@ -63,4 +64,19 @@ public void AddGenericStrategy_Ok()
.Should()
.Be(strategy);
}

[Fact]
public void AddStrategy_ExplicitInstance_Ok()
{
var builder = new ResiliencePipelineBuilder<string>();
var strategy = Substitute.For<ResilienceStrategy<string>>();

builder.AddStrategy(_ => strategy);

builder
.Build()
.GetPipelineDescriptor()
.FirstStrategy.StrategyInstance.Should()
.BeSameAs(strategy);
}
}
30 changes: 30 additions & 0 deletions test/Polly.Core.Tests/ResiliencePipelineBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ public void AddPipeline_Multiple_Ok()
executions.Should().HaveCount(7);
}

[Fact]
public void AddStrategy_ExplicitProactiveInstance_Ok()
{
var builder = new ResiliencePipelineBuilder();
var strategy = new TestResilienceStrategy();

builder.AddStrategy(_ => strategy);

builder
.Build()
.GetPipelineDescriptor()
.FirstStrategy.StrategyInstance.Should()
.BeSameAs(strategy);
}

[Fact]
public void AddStrategy_ExplicitReactiveInstance_Ok()
{
var builder = new ResiliencePipelineBuilder();
var strategy = Substitute.For<ResilienceStrategy<object>>();

builder.AddStrategy(_ => strategy);

builder
.Build()
.GetPipelineDescriptor()
.FirstStrategy.StrategyInstance.Should()
.BeSameAs(strategy);
}

[Fact]
public void Validator_Ok()
{
Expand Down