Skip to content

Commit d758e40

Browse files
Wrapping up interceptors, fixing typos, removing Moq (#2141)
* Wrapping up interceptors, fixing typos, removing Moq * Fix the readme issue * Added compatibility interceptor * Added docs * Updated packages * Refactoring tests to WireMock * Added matrix
1 parent 1c86286 commit d758e40

File tree

109 files changed

+2154
-1601
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+2154
-1601
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ assignees: ''
99

1010
**DO NOT USE ISSUES FOR QUESTIONS**
1111

12+
Note: New issues raised, where it is clear the submitter has not read the issue template,
13+
are likely to be closed with `invalid` label. Please understand that this is not meant to be rude,
14+
but to keep the issue list clean and useful for everyone. If one opening the issue decides to ignore the issue template,
15+
the maintainers might also decide to ignore the issue.
16+
17+
**Remove** all the text above the next line when submitting your issue.
18+
1219
**Describe the bug**
1320
A clear and concise description of what the bug is.
1421
Hint: use a tool like https://requestbin.com to compare working and non-working requests.
@@ -22,14 +29,13 @@ A clear and concise description of what you expected to happen.
2229
Post the working request here as well if you made it work using Postman, Swagger, or any other client.
2330
You can use https://requestbin.com/r to create a public request bin and share the link in the issue.
2431

25-
2632
**Stack trace**
2733
Copy the full stack trace here if you get an exception.
2834

2935
**Desktop (please complete the following information):**
3036
- OS: [e.g. macOS]
31-
- .NET version [e.g. .NET 5]
32-
- Version [e.g. 107.0.4]
37+
- .NET version [e.g. .NET 6]
38+
- Version [e.g. 110.2.0]
3339

3440
**Additional context**
3541
Add any other context about the problem here.

.github/workflows/pull-request.yml

+60-8
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,76 @@ on: [pull_request]
44

55
permissions:
66
contents: read
7+
checks: write
78

89
jobs:
9-
test:
10+
event_file:
11+
name: "Event File"
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Upload
15+
uses: actions/upload-artifact@v4
16+
with:
17+
name: Event File
18+
path: ${{ github.event_path }}
19+
test-windows:
1020
runs-on: windows-latest
21+
strategy:
22+
matrix:
23+
dotnet: ['net48', 'net6.0', 'net7.0', 'net8.0']
24+
25+
steps:
26+
-
27+
name: Checkout
28+
uses: actions/checkout@v4
29+
# -
30+
# name: Setup .NET
31+
# uses: actions/setup-dotnet@v3
32+
# with:
33+
# dotnet-version: '8.0'
34+
-
35+
name: Run tests
36+
run: dotnet test -f ${{ matrix.dotnet }}
37+
-
38+
name: Upload Test Results
39+
if: always()
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: Test Results Windows ${{ matrix.dotnet }}
43+
path: |
44+
test-results/**/*.xml
45+
test-results/**/*.trx
46+
test-results/**/*.json
47+
48+
test-linux:
49+
runs-on: ubuntu-latest
50+
strategy:
51+
matrix:
52+
dotnet: ['net6.0', 'net7.0', 'net8.0']
1153

1254
steps:
1355
-
1456
name: Checkout
1557
uses: actions/checkout@v3
16-
-
17-
name: Setup .NET
18-
uses: actions/setup-dotnet@v3
19-
with:
20-
dotnet-version: '7.0'
58+
# -
59+
# name: Setup .NET
60+
# uses: actions/setup-dotnet@v3
61+
# with:
62+
# dotnet-version: '8.0'
2163
-
2264
name: Run tests
23-
run: dotnet test -c Release
24-
65+
run: dotnet test -f ${{ matrix.dotnet }}
66+
-
67+
name: Upload Test Results
68+
if: always()
69+
uses: actions/upload-artifact@v4
70+
with:
71+
name: Test Results Ubuntu ${{ matrix.dotnet }}
72+
path: |
73+
test-results/**/*.xml
74+
test-results/**/*.trx
75+
test-results/**/*.json
76+
2577
docs:
2678
runs-on: ubuntu-latest
2779

.github/workflows/test-results.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Test Results
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Build and test PRs"]
6+
types:
7+
- completed
8+
permissions: {}
9+
10+
jobs:
11+
test-results:
12+
name: Test Results
13+
runs-on: ubuntu-latest
14+
if: github.event.workflow_run.conclusion != 'skipped'
15+
16+
permissions:
17+
checks: write
18+
pull-requests: write
19+
actions: read
20+
21+
steps:
22+
-
23+
name: Download and Extract Artifacts
24+
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d
25+
with:
26+
run_id: ${{ github.event.workflow_run.id }}
27+
path: artifacts
28+
-
29+
name: Publish Test Results
30+
uses: EnricoMi/publish-unit-test-result-action@v2
31+
with:
32+
commit: ${{ github.event.workflow_run.head_sha }}
33+
event_file: artifacts/Event File/event.json
34+
event_name: ${{ github.event.workflow_run.event }}
35+
files: |
36+
artifacts/**/*.xml
37+
artifacts/**/*.trx

Directory.Packages.props

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<Project>
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
</PropertyGroup>
5+
<PropertyGroup Label="Package versions for .NET 6" Condition="$(TargetFramework) == 'net6.0'">
6+
<MicrosoftTestHostVer>[6.0.28,7)</MicrosoftTestHostVer>
7+
</PropertyGroup>
8+
<PropertyGroup Label="Package versions for .NET 7" Condition="$(TargetFramework) == 'net7.0'">
9+
<MicrosoftTestHostVer>7.0.17</MicrosoftTestHostVer>
10+
</PropertyGroup>
11+
<PropertyGroup Label="Package versions for .NET 8" Condition="$(TargetFramework) == 'net8.0'">
12+
<MicrosoftTestHostVer>8.0.3</MicrosoftTestHostVer>
13+
</PropertyGroup>
14+
<ItemGroup Label="Runtime dependencies">
15+
<PackageVersion Include="HttpMultipartParser" Version="8.3.0" />
16+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
17+
<PackageVersion Include="CsvHelper" Version="30.0.1" />
18+
<PackageVersion Include="PolySharp" Version="1.14.1" />
19+
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
20+
<PackageVersion Include="WireMock.Net" Version="1.5.51" />
21+
<PackageVersion Include="WireMock.Net.FluentAssertions" Version="1.5.51" />
22+
</ItemGroup>
23+
<ItemGroup Label="Compile dependencies">
24+
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
25+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
26+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
27+
<PackageVersion Include="MinVer" Version="5.0.0" />
28+
<PackageVersion Include="Nullable" Version="1.3.1" />
29+
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" />
30+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
31+
<PackageVersion Include="JetBrains.Annotations" Version="2023.2.0" />
32+
</ItemGroup>
33+
<ItemGroup Label="Testing dependencies">
34+
<PackageVersion Include="AutoFixture" Version="4.18.0" />
35+
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
36+
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
37+
<PackageVersion Include="HttpTracer" Version="2.1.1" />
38+
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftTestHostVer)" />
39+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
40+
<PackageVersion Include="Moq" Version="4.20.69" />
41+
<PackageVersion Include="Polly" Version="8.3.1" />
42+
<PackageVersion Include="rest-mock-core" Version="0.7.12" />
43+
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
44+
<PackageVersion Include="System.Net.Http.Json" Version="8.0.0" />
45+
<PackageVersion Include="Xunit.Extensions.Logging" Version="1.1.0" />
46+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7" PrivateAssets="All" />
47+
<PackageVersion Include="xunit" Version="2.7.0" />
48+
</ItemGroup>
49+
</Project>

RestSharp.sln.DotSettings

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/PreferQualifiedReference/@EntryValue">False</s:Boolean>
6868
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FDIC/@EntryIndexedValue">FDIC</s:String>
6969
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;</s:String>
70+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
71+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;</s:String>
72+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
7073
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
7174
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
7275
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
@@ -77,6 +80,7 @@
7780
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
7881
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
7982
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
83+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
8084
<s:Boolean x:Key="/Default/UserDictionary/Words/=advisor/@EntryIndexedValue">True</s:Boolean>
8185
<s:Boolean x:Key="/Default/UserDictionary/Words/=Advisors/@EntryIndexedValue">True</s:Boolean>
8286
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net7.0</TargetFramework>
55
<IsPackable>false</IsPackable>
6-
<LangVersion>10</LangVersion>
6+
<LangVersion>preview</LangVersion>
77
<ImplicitUsings>enable</ImplicitUsings>
8+
<RepoRoot>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'RestSharp.sln'))</RepoRoot>
89
</PropertyGroup>
910

1011
<ItemGroup>
11-
<PackageReference Include="AutoFixture" Version="4.18.0" />
12-
<PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
13-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
12+
<PackageReference Include="AutoFixture"/>
13+
<PackageReference Include="BenchmarkDotNet"/>
14+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
1415
</ItemGroup>
1516
<ItemGroup>
16-
<ProjectReference Include="..\..\src\RestSharp.Serializers.NewtonsoftJson\RestSharp.Serializers.NewtonsoftJson.csproj" />
17-
<ProjectReference Include="..\..\test\RestSharp.Tests.Shared\RestSharp.Tests.Shared.csproj" />
17+
<ProjectReference Include="$(RepoRoot)\src\RestSharp.Serializers.NewtonsoftJson\RestSharp.Serializers.NewtonsoftJson.csproj"/>
18+
<ProjectReference Include="$(RepoRoot)\test\RestSharp.Tests.Shared\RestSharp.Tests.Shared.csproj"/>
1819
</ItemGroup>
1920
<ItemGroup>
20-
<Compile Remove="BenchmarkDotNet.Artifacts\**" />
21-
<EmbeddedResource Remove="BenchmarkDotNet.Artifacts\**" />
22-
<None Remove="BenchmarkDotNet.Artifacts\**" />
21+
<Compile Remove="BenchmarkDotNet.Artifacts\**"/>
22+
<EmbeddedResource Remove="BenchmarkDotNet.Artifacts\**"/>
23+
<None Remove="BenchmarkDotNet.Artifacts\**"/>
2324
</ItemGroup>
2425
</Project>

docs/.vuepress/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module.exports = {
2727
"usage.md",
2828
"serialization.md",
2929
"authenticators.md",
30+
"interceptors.md",
3031
"error-handling.md"
3132
]
3233
}

docs/interceptors.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
title: Interceptors
3+
---
4+
5+
## Intercepting requests and responses
6+
7+
Interceptors are a powerful feature of RestSharp that allows you to modify requests and responses before they are sent or received. You can use interceptors to add headers, modify the request body, or even cancel the request. You can also use interceptors to modify the response before it is returned to the caller.
8+
9+
### Implementing an interceptor
10+
11+
To implement an interceptor, you need to create a class that inherits the `Interceptor` base class. The base class implements all interceptor methods as virtual, so you can override them in your derived class.
12+
13+
Methods that you can override are:
14+
- `BeforeRequest(RestRequest request, CancellationToken cancellationToken)`
15+
- `AfterRequest(RestResponse response, CancellationToken cancellationToken)`
16+
- `BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken)`
17+
- `AfterHttpResponse(HttpResponseMessage responseMessage, CancellationToken cancellationToken)`
18+
- `BeforeDeserialization(RestResponse response, CancellationToken cancellationToken)`
19+
20+
All those functions must return a `ValueTask` instance.
21+
22+
Here's an example of an interceptor that adds a header to a request:
23+
24+
```csharp
25+
// This interceptor adds a header to the request
26+
// You'd not normally use this interceptor, as RestSharp already has a method
27+
// to add headers to the request
28+
class HeaderInterceptor(string headerName, string headerValue) : Interceptors.Interceptor {
29+
public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) {
30+
requestMessage.Headers.Add(headerName, headerValue);
31+
return ValueTask.CompletedTask;
32+
}
33+
}
34+
```
35+
36+
Because interceptor functions return `ValueTask`, you can use `async` and `await` inside them.
37+
38+
### Using an interceptor
39+
40+
It's possible to add as many interceptors as you want, both to the client and to the request. The interceptors are executed in the order they were added.
41+
42+
Adding interceptors to the client is done via the client options:
43+
44+
```csharp
45+
var options = new RestClientOptions("https://api.example.com") {
46+
Interceptors = [new HeaderInterceptor("Authorization", token)]
47+
};
48+
var client = new RestClient(options);
49+
```
50+
51+
When you add an interceptor to the client, it will be executed for every request made by that client.
52+
53+
You can also add an interceptor to a specific request:
54+
55+
```csharp
56+
var request = new RestRequest("resource") {
57+
Interceptors = [new HeaderInterceptor("Authorization", token)]
58+
};
59+
```
60+
61+
In this case, the interceptor will only be executed for that specific request.
62+
63+
### Deprecation notice
64+
65+
Interceptors aim to replace the existing request hooks available in RestSharp prior to version 111.0. Those hooks are marked with `Obsolete` attribute and will be removed in the future. If you are using those hooks, we recommend migrating to interceptors as soon as possible.
66+
67+
To make the migration easier, RestSharp provides a class called `CompatibilityInterceptor`. It has properties for the hooks available in RestSharp 110.0 and earlier. You can use it to migrate your code to interceptors without changing the existing logic.
68+
69+
For example, a code that uses `OnBeforeRequest` hook:
70+
71+
```csharp
72+
var request = new RestRequest("success");
73+
request.OnBeforeDeserialization += _ => throw new Exception(exceptionMessage);
74+
```
75+
76+
Can be migrated to interceptors like this:
77+
78+
```csharp
79+
var request = new RestRequest("success") {
80+
Interceptors = [new CompatibilityInterceptor {
81+
OnBeforeDeserialization = _ => throw new Exception(exceptionMessage)
82+
}]
83+
};
84+
```

docs/serialization.md

+27-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ In previous versions of RestSharp, the default XML serializer was a custom RestS
3737
You can add it back if necessary by installing the package and adding it to the client:
3838

3939
```csharp
40-
client.UseXmlSerializer();
40+
var client = new RestClient(
41+
options,
42+
configureSerialization: s => s.UseXmlSerializer()
43+
);
4144
```
4245

4346
As before, you can supply three optional arguments for a custom namespace, custom root element, and if you want to use `SerializeAs` and `DeserializeAs` attributed.
@@ -77,6 +80,29 @@ JsonSerializerSettings DefaultSettings = new JsonSerializerSettings {
7780
If you need to use different settings, you can supply your instance of
7881
`JsonSerializerSettings` as a parameter for the extension method.
7982

83+
## CSV
84+
85+
A separate package `RestSharp.Serializers.CsvHelper` provides a CSV serializer for RestSharp. It is based on the
86+
`CsvHelper` library.
87+
88+
Use the extension method provided by the package to configure the client:
89+
90+
```csharp
91+
var client = new RestClient(
92+
options,
93+
configureSerialization: s => s.UseCsvHelper()
94+
);
95+
```
96+
97+
You can also supply your instance of `CsvConfiguration` as a parameter for the extension method.
98+
99+
```csharp
100+
var client = new RestClient(
101+
options,
102+
configureSerialization: s => s.UseCsvHelper(new CsvConfiguration(CultureInfo.InvariantCulture) {...})
103+
);
104+
```
105+
80106
## Custom
81107

82108
You can also implement your custom serializer. To support both serialization and

0 commit comments

Comments
 (0)