Skip to content

Commit

Permalink
Move code from markdown to snippets (#1540)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Sep 4, 2023
1 parent da9db1b commit 305ac23
Show file tree
Hide file tree
Showing 17 changed files with 593 additions and 73 deletions.
8 changes: 7 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"commands": [
"dotnet-cake"
]
},
"markdownsnippets.tool": {
"version": "25.1.0",
"commands": [
"mdsnippets"
]
}
}
}
}
96 changes: 96 additions & 0 deletions .github/workflows/on-push-do-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: on-push-do-docs

on:
push:
branches: [main]
paths: [ "src/Snippets/**" ]
workflow_dispatch:

permissions:
contents: read

jobs:
update-docs:
name: update-docs
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0

- name: Setup .NET SDK
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0

- name: Update documentation
id: update-docs
shell: pwsh
env:
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
run: |
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
dotnet tool restore
dotnet mdsnippets

$GitStatus = (git status --porcelain)
if ([string]::IsNullOrEmpty($GitStatus)) {
Write-Host "No changes to commit."
exit 0
}

$TimeStamp = Get-Date -Format "yyyy-MM-dd-HH-mm"
$BranchName = "docs/update-docs-$TimeStamp"
"branchName=$BranchName" >> $env:GITHUB_OUTPUT

$GitEmail = "138034000+polly-updater-bot[bot]@users.noreply.github.com"
$GitUser = "polly-updater-bot[bot]"

git config user.email $GitEmail | Out-Null
git config user.name $GitUser | Out-Null
git remote set-url "${{ github.server_url }}/${{ github.repository }}.git" | Out-Null
git fetch origin | Out-Null
git rev-parse --verify --quiet ("remotes/origin/" + $BranchName) | Out-Null

if ($LASTEXITCODE -eq 0) {
Write-Host "Branch $BranchName already exists."
exit 0
}

git checkout -b $BranchName
git add .
git commit -m "Update the code-snippets in the documentation"
git push -u origin $BranchName
"updated-docs=true" >> $env:GITHUB_OUTPUT

- name: Generate GitHub application token
if: steps.update-docs.outputs.updated-docs == 'true'
id: generate-application-token
uses: peter-murray/workflow-application-token-action@8e1ba3bf1619726336414f1014e37f17fbadf1db # v2.1.0
with:
application_id: ${{ secrets.POLLY_UPDATER_BOT_APP_ID }}
application_private_key: ${{ secrets.POLLY_UPDATER_BOT_KEY }}
permissions: "contents:write, pull_requests:write"

- name: Create pull request
if: steps.update-docs.outputs.updated-docs == 'true'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
with:
github-token: ${{ steps.generate-application-token.outputs.token }}
script: |
const { repo, owner } = context.repo;
const workflowUrl = `${{ github.server_url }}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const branchName = "${{ steps.update-docs.outputs.branchName }}";
const result = await github.rest.pulls.create({
title: 'Update the code-snippets in the documentation',
owner,
repo,
head: branchName,
base: 'main',
body: [
'This PR updates the code-snippets in the documentation.',
'',
`This pull request was generated by [GitHub Actions](${workflowUrl}).`
].join('\n')
});
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
<PackageVersion Include="NSubstitute" Version="5.0.0" />
<PackageVersion Include="Polly" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Core" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.RateLimiting" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Extensions" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Testing" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageVersion Include="ReportGenerator" Version="5.1.24" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.8.0.76515" />
Expand Down
7 changes: 7 additions & 0 deletions Polly.sln
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing", "src\Polly.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing.Tests", "test\Polly.Testing.Tests\Polly.Testing.Tests.csproj", "{D333B5CE-982D-4C11-BDAF-4217AA02306E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snippets", "src\Snippets\Snippets.csproj", "{D812B941-79B0-4E1E-BB70-4FAE345B5234}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -114,6 +116,10 @@ Global
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.Build.0 = Release|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -133,6 +139,7 @@ Global
{C04DEE61-C1EA-4028-B457-CDBD304B8ED9} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
{9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
{D333B5CE-982D-4C11-BDAF-4217AA02306E} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
{D812B941-79B0-4E1E-BB70-4FAE345B5234} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E5D54CD-770A-4345-B585-1848FC2EA6F4}
Expand Down
2 changes: 1 addition & 1 deletion bench/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Benchmarks
# Benchmarks

To run the benchmarks, use the `benchmarks.ps1` script in this repository:

Expand Down
11 changes: 11 additions & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,24 @@ Task("__CreateNuGetPackages")
}
});

Task("__ValidateDocs")
.Does(() =>
{
var result = StartProcess("dotnet", "mdsnippets --validate-content");
if (result != 0)
{
throw new InvalidOperationException($"Failed to validate the documentation snippets. Are the links correct?");
}
});

//////////////////////////////////////////////////////////////////////
// BUILD TASKS
//////////////////////////////////////////////////////////////////////

Task("Build")
.IsDependentOn("__Clean")
.IsDependentOn("__RestoreNuGetPackages")
.IsDependentOn("__ValidateDocs")
.IsDependentOn("__BuildSolutions")
.IsDependentOn("__RunTests")
.IsDependentOn("__RunMutationTests")
Expand Down
7 changes: 7 additions & 0 deletions mdsnippets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/schema.json",
"ExcludeDirectories": [ "test", "artifacts", "bench", "eng" ],
"ExcludeSnippetDirectories": [ "src/Polly", "src/Polly.Core", "src/Polly.RateLimiting", "src/Polly.Testing", "src/Polly.Extensions", "artifacts", "test" ],
"OmitSnippetLinks": true,
"Convention": "InPlaceOverwrite"
}
85 changes: 39 additions & 46 deletions src/Polly.Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,31 +71,26 @@ The resilience pipeline may consist of one or more individual resilience strateg

Here's an example of a non-reactive strategy that executes a user-provided callback:

```csharp
<!-- snippet: my-custom-strategy -->
```cs
internal class MyCustomStrategy : ResilienceStrategy
{
private readonly TimeProvider _timeProvider;

public MyCustomStrategy(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}

protected override async ValueTask<T> ExecuteCore<T, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback,
ResilienceContext context,
protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
// Perform actions before execution
var outcome = await callback(context, state).ContinueOnCapturedContext(context.ContinueOnCapturedContext);
var outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);

// Perform actions after execution
return outcome;
}
}
```
<!-- endSnippet -->

### About Synchronous and Asynchronous Executions

Expand All @@ -113,37 +108,51 @@ To construct a resilience pipeline, chain various extensions on the `ResilienceP

### Creating a non-generic pipeline

```csharp
var pipeline = new ResiliencePipelineBuilder()
<!-- snippet: create-generic-pipeline -->
```cs
ResiliencePipeline<string> pipeline = new ResiliencePipelineBuilder<string>()
.AddRetry(new())
.AddCircuitBreaker(new())
.AddTimeout(new TimeoutStrategyOptions() { ... })
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
```
<!-- endSnippet -->

### Creating a generic pipeline

```csharp
var pipeline = new ResiliencePipelineBuilder<string>()
<!-- snippet: create-non-generic-pipeline -->
```cs
ResiliencePipeline<string> pipeline = new ResiliencePipelineBuilder<string>()
.AddRetry(new())
.AddCircuitBreaker(new())
.AddTimeout(new TimeoutStrategyOptions() { ... })
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
```
<!-- endSnippet -->

## Extensibility

Extending the resilience functionality is straightforward. You can create extensions for `ResiliencePipelineBuilder` by leveraging the `AddStrategy` extension methods. If you aim to design a resilience strategy that is compatible with both generic and non-generic builders, consider using `ResiliencePipelineBuilderBase` as your target class.

Here's an example:

```csharp
<!-- snippet: add-my-custom-strategy -->
```cs
public static TBuilder AddMyCustomStrategy<TBuilder>(this TBuilder builder, MyCustomStrategyOptions options)
where TBuilder : ResiliencePipelineBuilderBase
{
return builder.AddStrategy(context => new MyCustomStrategy(), options);
}

public class MyCustomStrategyOptions : ResilienceStrategyOptions
{
public MyCustomStrategyOptions()
{
Name = "MyCustomStrategy";
}
}
```
<!-- endSnippet -->

To gain insights into implementing custom resilience strategies, you can explore the following Polly strategy examples:

Expand Down Expand Up @@ -185,7 +194,8 @@ These delegates accept either `Args` or `Args<TResult>` arguments, which encapsu

For non-reactive strategies, the `Args` structure might resemble:

```csharp
<!-- snippet: on-timeout-args -->
```cs
public readonly struct OnTimeoutArguments
{
public OnTimeoutArguments(ResilienceContext context, TimeSpan timeout)
Expand All @@ -195,39 +205,23 @@ public readonly struct OnTimeoutArguments
}

public ResilienceContext Context { get; } // Include the Context property
public TimeSpan Timeout { get; } // Additional event-related properties
}
```
For reactive strategies, `Args<TResult>` could look like:

```csharp
public readonly struct OnRetryArguments<TResult>
{
public OnRetryArguments(ResilienceContext context, Outcome<TResult> outcome, int attemptNumber)
{
Context = context;
Outcome = outcome;
AttemptNumber = attemptNumber;
}

public ResilienceContext Context { get; } // Include the Context property
public Outcome<TResult> Outcome { get; } // Includes the outcome associated with the event
public int AttemptNumber { get; }
public TimeSpan Timeout { get; } // Additional event-related properties
}
```
<!-- endSnippet -->

### Example: Usage of Delegates

Below are some examples illustrating the usage of these delegates:

```csharp
<!-- snippet: delegate-usage -->
```cs
new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{

// Non-Generic predicate for multiple result types
ShouldHandle = args => args switch
ShouldHandle = args => args.Outcome switch
{
{ Exception: InvalidOperationException } => PredicateResult.True(),
{ Result: string result } when result == "Failure" => PredicateResult.True(),
Expand All @@ -236,27 +230,26 @@ new ResiliencePipelineBuilder()
},
})
.Build();
```

```csharp
new ResiliencePipelineBuilder<string>()
.AddRetry(new RetryStrategyOptions<string>
{
// Generic predicate for a single result type
ShouldHandle = args => args switch
ShouldHandle = args => args.Outcome switch
{
{ Exception: InvalidOperationException } => PredicateResult.True(),
{ Result: result } when result == "Failure" => PredicateResult.True(),
{ Result: { } result } when result == "Failure" => PredicateResult.True(),
_ => PredicateResult.False()
},
})
.Build();
```
<!-- endSnippet -->

## Telemetry

Each resilience strategy can generate telemetry data through the [`ResiliencePipelineTelemetry`](Telemetry/ResiliencePipelineTelemetry.cs) API. Polly encapsulates event details as [`TelemetryEventArguments`](Telemetry/TelemetryEventArguments.cs) and emits them via `TelemetryListener`.

To leverage this telemetry data, users should assign a `TelemetryListener` instance to `ResiliencePipelineBuilder.TelemetryListener` and then consume the `TelemetryEventArguments`.

For common scenarios, it is expected that users would make use of `Polly.Extensions`. This extension enables telemetry configuration through the `ResiliencePipelineBuilder.ConfigureTelemetry(...)` method, which processes `TelemetryEventArguments` to generate logs and metrics.
For common scenarios, it is expected that users would make use of `Polly.Extensions`. This extension enables telemetry configuration through the `ResiliencePipelineBuilder.ConfigureTelemetry(...)` method, which processes `TelemetryEventArguments` to generate logs and metrics.
Loading

0 comments on commit 305ac23

Please sign in to comment.