Skip to content

Commit

Permalink
Wrapping up interceptors, fixing typos, removing Moq (#2141)
Browse files Browse the repository at this point in the history
* Wrapping up interceptors, fixing typos, removing Moq
* Fix the readme issue
* Added compatibility interceptor
* Added docs
* Updated packages
* Refactoring tests to WireMock
* Added matrix
  • Loading branch information
alexeyzimarev committed Apr 5, 2024
1 parent 1c86286 commit d758e40
Show file tree
Hide file tree
Showing 109 changed files with 2,154 additions and 1,601 deletions.
12 changes: 9 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Expand Up @@ -9,6 +9,13 @@ assignees: ''

**DO NOT USE ISSUES FOR QUESTIONS**

Note: New issues raised, where it is clear the submitter has not read the issue template,
are likely to be closed with `invalid` label. Please understand that this is not meant to be rude,
but to keep the issue list clean and useful for everyone. If one opening the issue decides to ignore the issue template,
the maintainers might also decide to ignore the issue.

**Remove** all the text above the next line when submitting your issue.

**Describe the bug**
A clear and concise description of what the bug is.
Hint: use a tool like https://requestbin.com to compare working and non-working requests.
Expand All @@ -22,14 +29,13 @@ A clear and concise description of what you expected to happen.
Post the working request here as well if you made it work using Postman, Swagger, or any other client.
You can use https://requestbin.com/r to create a public request bin and share the link in the issue.


**Stack trace**
Copy the full stack trace here if you get an exception.

**Desktop (please complete the following information):**
- OS: [e.g. macOS]
- .NET version [e.g. .NET 5]
- Version [e.g. 107.0.4]
- .NET version [e.g. .NET 6]
- Version [e.g. 110.2.0]

**Additional context**
Add any other context about the problem here.
68 changes: 60 additions & 8 deletions .github/workflows/pull-request.yml
Expand Up @@ -4,24 +4,76 @@ on: [pull_request]

permissions:
contents: read
checks: write

jobs:
test:
event_file:
name: "Event File"
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Event File
path: ${{ github.event_path }}
test-windows:
runs-on: windows-latest
strategy:
matrix:
dotnet: ['net48', 'net6.0', 'net7.0', 'net8.0']

steps:
-
name: Checkout
uses: actions/checkout@v4
# -
# name: Setup .NET
# uses: actions/setup-dotnet@v3
# with:
# dotnet-version: '8.0'
-
name: Run tests
run: dotnet test -f ${{ matrix.dotnet }}
-
name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: Test Results Windows ${{ matrix.dotnet }}
path: |
test-results/**/*.xml
test-results/**/*.trx
test-results/**/*.json
test-linux:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet: ['net6.0', 'net7.0', 'net8.0']

steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.0'
# -
# name: Setup .NET
# uses: actions/setup-dotnet@v3
# with:
# dotnet-version: '8.0'
-
name: Run tests
run: dotnet test -c Release

run: dotnet test -f ${{ matrix.dotnet }}
-
name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: Test Results Ubuntu ${{ matrix.dotnet }}
path: |
test-results/**/*.xml
test-results/**/*.trx
test-results/**/*.json
docs:
runs-on: ubuntu-latest

Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/test-results.yml
@@ -0,0 +1,37 @@
name: Test Results

on:
workflow_run:
workflows: ["Build and test PRs"]
types:
- completed
permissions: {}

jobs:
test-results:
name: Test Results
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion != 'skipped'

permissions:
checks: write
pull-requests: write
actions: read

steps:
-
name: Download and Extract Artifacts
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d
with:
run_id: ${{ github.event.workflow_run.id }}
path: artifacts
-
name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
with:
commit: ${{ github.event.workflow_run.head_sha }}
event_file: artifacts/Event File/event.json
event_name: ${{ github.event.workflow_run.event }}
files: |
artifacts/**/*.xml
artifacts/**/*.trx
49 changes: 49 additions & 0 deletions Directory.Packages.props
@@ -0,0 +1,49 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<PropertyGroup Label="Package versions for .NET 6" Condition="$(TargetFramework) == 'net6.0'">
<MicrosoftTestHostVer>[6.0.28,7)</MicrosoftTestHostVer>
</PropertyGroup>
<PropertyGroup Label="Package versions for .NET 7" Condition="$(TargetFramework) == 'net7.0'">
<MicrosoftTestHostVer>7.0.17</MicrosoftTestHostVer>
</PropertyGroup>
<PropertyGroup Label="Package versions for .NET 8" Condition="$(TargetFramework) == 'net8.0'">
<MicrosoftTestHostVer>8.0.3</MicrosoftTestHostVer>
</PropertyGroup>
<ItemGroup Label="Runtime dependencies">
<PackageVersion Include="HttpMultipartParser" Version="8.3.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="CsvHelper" Version="30.0.1" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
<PackageVersion Include="WireMock.Net" Version="1.5.51" />
<PackageVersion Include="WireMock.Net.FluentAssertions" Version="1.5.51" />
</ItemGroup>
<ItemGroup Label="Compile dependencies">
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="MinVer" Version="5.0.0" />
<PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.2.0" />
</ItemGroup>
<ItemGroup Label="Testing dependencies">
<PackageVersion Include="AutoFixture" Version="4.18.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="HttpTracer" Version="2.1.1" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftTestHostVer)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Moq" Version="4.20.69" />
<PackageVersion Include="Polly" Version="8.3.1" />
<PackageVersion Include="rest-mock-core" Version="0.7.12" />
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
<PackageVersion Include="System.Net.Http.Json" Version="8.0.0" />
<PackageVersion Include="Xunit.Extensions.Logging" Version="1.1.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7" PrivateAssets="All" />
<PackageVersion Include="xunit" Version="2.7.0" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions RestSharp.sln.DotSettings
Expand Up @@ -67,6 +67,9 @@
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/PreferQualifiedReference/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FDIC/@EntryIndexedValue">FDIC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;</s:String>
<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>
<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>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -77,6 +80,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=advisor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Advisors/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
Expand Down
21 changes: 11 additions & 10 deletions benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj
@@ -1,24 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>10</LangVersion>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<RepoRoot>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'RestSharp.sln'))</RepoRoot>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.18.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
<PackageReference Include="AutoFixture"/>
<PackageReference Include="BenchmarkDotNet"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\RestSharp.Serializers.NewtonsoftJson\RestSharp.Serializers.NewtonsoftJson.csproj" />
<ProjectReference Include="..\..\test\RestSharp.Tests.Shared\RestSharp.Tests.Shared.csproj" />
<ProjectReference Include="$(RepoRoot)\src\RestSharp.Serializers.NewtonsoftJson\RestSharp.Serializers.NewtonsoftJson.csproj"/>
<ProjectReference Include="$(RepoRoot)\test\RestSharp.Tests.Shared\RestSharp.Tests.Shared.csproj"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="BenchmarkDotNet.Artifacts\**" />
<EmbeddedResource Remove="BenchmarkDotNet.Artifacts\**" />
<None Remove="BenchmarkDotNet.Artifacts\**" />
<Compile Remove="BenchmarkDotNet.Artifacts\**"/>
<EmbeddedResource Remove="BenchmarkDotNet.Artifacts\**"/>
<None Remove="BenchmarkDotNet.Artifacts\**"/>
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Expand Up @@ -27,6 +27,7 @@ module.exports = {
"usage.md",
"serialization.md",
"authenticators.md",
"interceptors.md",
"error-handling.md"
]
}
Expand Down
84 changes: 84 additions & 0 deletions docs/interceptors.md
@@ -0,0 +1,84 @@
---
title: Interceptors
---

## Intercepting requests and responses

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.

### Implementing an interceptor

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.

Methods that you can override are:
- `BeforeRequest(RestRequest request, CancellationToken cancellationToken)`
- `AfterRequest(RestResponse response, CancellationToken cancellationToken)`
- `BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken)`
- `AfterHttpResponse(HttpResponseMessage responseMessage, CancellationToken cancellationToken)`
- `BeforeDeserialization(RestResponse response, CancellationToken cancellationToken)`

All those functions must return a `ValueTask` instance.

Here's an example of an interceptor that adds a header to a request:

```csharp
// This interceptor adds a header to the request
// You'd not normally use this interceptor, as RestSharp already has a method
// to add headers to the request
class HeaderInterceptor(string headerName, string headerValue) : Interceptors.Interceptor {
public override ValueTask BeforeHttpRequest(HttpRequestMessage requestMessage, CancellationToken cancellationToken) {
requestMessage.Headers.Add(headerName, headerValue);
return ValueTask.CompletedTask;
}
}
```

Because interceptor functions return `ValueTask`, you can use `async` and `await` inside them.

### Using an interceptor

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.

Adding interceptors to the client is done via the client options:

```csharp
var options = new RestClientOptions("https://api.example.com") {
Interceptors = [new HeaderInterceptor("Authorization", token)]
};
var client = new RestClient(options);
```

When you add an interceptor to the client, it will be executed for every request made by that client.

You can also add an interceptor to a specific request:

```csharp
var request = new RestRequest("resource") {
Interceptors = [new HeaderInterceptor("Authorization", token)]
};
```

In this case, the interceptor will only be executed for that specific request.

### Deprecation notice

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.

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.

For example, a code that uses `OnBeforeRequest` hook:

```csharp
var request = new RestRequest("success");
request.OnBeforeDeserialization += _ => throw new Exception(exceptionMessage);
```

Can be migrated to interceptors like this:

```csharp
var request = new RestRequest("success") {
Interceptors = [new CompatibilityInterceptor {
OnBeforeDeserialization = _ => throw new Exception(exceptionMessage)
}]
};
```
28 changes: 27 additions & 1 deletion docs/serialization.md
Expand Up @@ -37,7 +37,10 @@ In previous versions of RestSharp, the default XML serializer was a custom RestS
You can add it back if necessary by installing the package and adding it to the client:

```csharp
client.UseXmlSerializer();
var client = new RestClient(
options,
configureSerialization: s => s.UseXmlSerializer()
);
```

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.
Expand Down Expand Up @@ -77,6 +80,29 @@ JsonSerializerSettings DefaultSettings = new JsonSerializerSettings {
If you need to use different settings, you can supply your instance of
`JsonSerializerSettings` as a parameter for the extension method.

## CSV

A separate package `RestSharp.Serializers.CsvHelper` provides a CSV serializer for RestSharp. It is based on the
`CsvHelper` library.

Use the extension method provided by the package to configure the client:

```csharp
var client = new RestClient(
options,
configureSerialization: s => s.UseCsvHelper()
);
```

You can also supply your instance of `CsvConfiguration` as a parameter for the extension method.

```csharp
var client = new RestClient(
options,
configureSerialization: s => s.UseCsvHelper(new CsvConfiguration(CultureInfo.InvariantCulture) {...})
);
```

## Custom

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

0 comments on commit d758e40

Please sign in to comment.