This document captures project-specific knowledge to speed up advanced development and maintenance of RestSharp. It focuses on build, configuration, testing details, source generation, and conventions unique to this repository.
The solution (RestSharp.slnx) is organized into the following structure:
Core Library:
src/RestSharp/- Main library targeting multiple frameworks
Serializer Extensions:
src/RestSharp.Serializers.NewtonsoftJson/- Newtonsoft.Json serializersrc/RestSharp.Serializers.Xml/- XML serializersrc/RestSharp.Serializers.CsvHelper/- CSV serializer
Source Generator:
gen/SourceGenerator/- Incremental source generator for code generation (see dedicated section below)
Test Projects:
test/RestSharp.Tests/- Core unit teststest/RestSharp.Tests.Integrated/- Integration tests using WireMocktest/RestSharp.Tests.Serializers.Json/- JSON serializer teststest/RestSharp.Tests.Serializers.Xml/- XML serializer teststest/RestSharp.Tests.Serializers.Csv/- CSV serializer teststest/RestSharp.Tests.Shared/- Shared test utilitiestest/RestSharp.InteractiveTests/- Interactive/manual tests
Performance:
benchmarks/RestSharp.Benchmarks/- BenchmarkDotNet performance tests
Library Targets (src/Directory.Build.props):
netstandard2.0- .NET Standard 2.0 for broad compatibilitynet471- .NET Framework 4.7.1net48- .NET Framework 4.8net8.0- .NET 8net9.0- .NET 9net10.0- .NET 10
Test Targets (test/Directory.Build.props):
net48- .NET Framework 4.8 (Windows only)net8.0- .NET 8net9.0- .NET 9net10.0- .NET 10
Source Generator Target:
netstandard2.0- Required for source generators to work across all compiler versions
The build system uses a hierarchical props structure:
-
props/Common.props- Root properties imported by all projects:- Sets
RepoRootvariable - Configures assembly signing (
RestSharp.snk) - Sets
LangVersion=previewandImplicitUsings=enable - Enables nullable reference types (
Nullable=enable) - Adds global
using System.Net.Http;
- Sets
-
src/Directory.Build.props- Source project properties:- Imports
Common.props - Defines multi-targeting for libraries
- Configures NuGet package metadata (icon, license, description)
- Enables SourceLink for debugging
- Uses MinVer for versioning
- Conditionally adds polyfills for older frameworks
- Generates XML documentation files
- Imports
-
test/Directory.Build.props- Test project properties:- Imports
Common.props - Sets
IsTestProject=trueandIsPackable=false - Configures test result output:
test-results/<TFM>/<ProjectName>.trx - Disables nullable (
Nullable=disablefor tests) - Adds global usings for xUnit, FluentAssertions, AutoFixture
- Suppresses warnings:
xUnit1033,CS8002
- Imports
-
Directory.Packages.props- Central Package Management:- All package versions defined centrally
- TFM-specific version overrides (e.g.,
System.Text.Jsonfor .NET 10) - Separate sections for runtime, compile, and testing dependencies
Legacy Framework Support (.NET Framework 4.7.1/4.8, netstandard2.0):
System.Text.Jsonis added as a package reference (newer frameworks have it built-in)- Polyfills are enabled via
Nullablepackage for nullable reference type attributes - Reference assemblies provided by
Microsoft.NETFramework.ReferenceAssemblies.net472
Modern .NET (8/9/10):
- Native support for most features
- Conditional compilation using
#if NET - Platform-specific attributes like
[UnsupportedOSPlatform("browser")]
All assemblies are strong-named using RestSharp.snk (configured in Common.props).
RestSharp includes a custom incremental source generator located in gen/SourceGenerator/ that automates boilerplate code generation.
Project Configuration:
- Targets:
netstandard2.0(required for source generators) - Language: C# preview features enabled
- Output: Not included in build output (
IncludeBuildOutput=false) - Analyzer rules: Extended analyzer rules enforced
- Referenced as analyzer in main project:
OutputItemType="Analyzer"
Dependencies:
Microsoft.CodeAnalysis.Analyzers- Analyzer SDKMicrosoft.CodeAnalysis.CSharp- Roslyn C# APIs
Global Usings:
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;Purpose: Generates immutable (read-only) wrapper classes from mutable classes.
Trigger Attribute: [GenerateImmutable]
How It Works:
- Scans compilation for classes annotated with
[GenerateImmutable] - Identifies all properties with
setaccessors (excluding those marked with[Exclude]) - Generates a
ReadOnly{ClassName}partial class with:- Read-only properties (getters only)
- Constructor accepting the mutable class instance
- Partial method
CopyAdditionalPropertiesfor extensibility - Preserves XML documentation comments
Example Usage:
[GenerateImmutable]
public class RestClientOptions {
public Uri? BaseUrl { get; set; }
public string? UserAgent { get; set; }
[Exclude] // This property won't be in the immutable version
public List<Interceptor> Interceptors { get; set; }
}Generated Output: ReadOnlyRestClientOptions.cs with immutable properties and a constructor that copies values from RestClientOptions.
Location: Generated files appear in obj/<Configuration>/<TFM>/generated/SourceGenerator/SourceGenerator.ImmutableGenerator/
Purpose: Generates static factory methods to clone objects from base types to derived types.
Trigger Attribute: [GenerateClone(BaseType = typeof(BaseClass), Name = "MethodName")]
How It Works:
- Finds classes with
[GenerateClone]attribute - Extracts
BaseTypeandNamefrom attribute parameters - Analyzes properties from the base type and its inheritance chain
- Generates a static factory method that:
- Takes the base type as parameter
- Creates a new instance of the derived type
- Copies all properties from base to derived
- Uses constructor parameters where applicable
Example Usage:
[GenerateClone(BaseType = typeof(RestResponse), Name = "FromResponse")]
public partial class RestResponse<T> : RestResponse {
public T? Data { get; set; }
}Generated Output: RestResponse.Clone.g.cs with a static FromResponse method that creates RestResponse<T> from RestResponse.
Location: Generated files appear in obj/<Configuration>/<TFM>/generated/SourceGenerator/SourceGenerator.InheritedCloneGenerator/
Purpose: Helper extension methods for the generators using C# extension types.
Key Methods:
FindClasses(predicate)- Finds classes matching a predicate across all syntax treesFindAnnotatedClasses(attributeName, strict)- Finds classes with specific attributesGetBaseTypesAndThis()- Traverses type hierarchy to get all base types
Located in src/RestSharp/Extensions/GenerateImmutableAttribute.cs:
[AttributeUsage(AttributeTargets.Class)]
class GenerateImmutableAttribute : Attribute;
[AttributeUsage(AttributeTargets.Class)]
class GenerateCloneAttribute : Attribute {
public Type? BaseType { get; set; }
public string? Name { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
class Exclude : Attribute; // Excludes properties from immutable generationIn src/RestSharp/RestSharp.csproj:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup Label="Source generator">
<ProjectReference Include="$(RepoRoot)\gen\SourceGenerator\SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>Generated files are emitted to the obj directory when EmitCompilerGeneratedFiles=true. To view:
# Example path for net8.0 Debug build
ls src/RestSharp/obj/Debug/net8.0/generated/SourceGenerator/Primary Framework: xUnit
Assertion Library: FluentAssertions
Test Data: AutoFixture
Mocking:
Moq- General mockingRichardSzalay.MockHttp- HTTP message handler mockingWireMock.Net- HTTP server mocking for integration tests
Global Usings (configured in test/Directory.Build.props):
using Xunit;
using Xunit.Abstractions;
using FluentAssertions;
using FluentAssertions.Extensions;
using AutoFixture;These are automatically available in all test files without explicit using statements.
Unit Tests (RestSharp.Tests):
- Tests for core functionality
- Uses mocking for HTTP interactions
- Example:
UrlBuilderTests,ObjectParserTests - Organized with partial classes for large test suites (e.g.,
UrlBuilderTests.Get.cs,UrlBuilderTests.Post.cs)
Integration Tests (RestSharp.Tests.Integrated):
- Uses
WireMockServerfor realistic HTTP scenarios - Tests actual HTTP behavior without external dependencies
- Example:
DownloadFileTestsspins up WireMock server in constructor, disposes inIDisposable.Dispose - Asset files stored in
Assets/directory
Serializer Tests:
- Separate projects for each serializer (JSON, XML, CSV)
- Test serialization/deserialization behavior
All tests for entire solution:
dotnet test RestSharp.slnx -c DebugSpecific test project:
dotnet test test/RestSharp.Tests/RestSharp.Tests.csprojSingle target framework:
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net8.0Single test by fully-qualified name (recommended for precision):
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj \
--filter "FullyQualifiedName=RestSharp.Tests.UrlBuilderTests_Get.Should_build_url_with_query" \
-f net8.0Filter by namespace or class:
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj \
--filter "RestSharp.Tests.UrlBuilderTests"With verbose output:
dotnet test -v nOutput Location: test-results/<TFM>/<ProjectName>.trx
Configuration (in test/Directory.Build.props):
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
<VSTestResultsDirectory>$(RepoRoot)/test-results/$(TargetFramework)</VSTestResultsDirectory>Results are written per target framework, making it easy to identify TFM-specific failures.
Tool: coverlet.collector (data-collector based)
Generate coverage report:
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj \
-f net8.0 \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=coberturaCoverage output is placed in the test results directory.
Best Practices:
- Co-locate tests by feature area
- Use partial classes for large test suites (link via
<DependentUpon>in.csproj) - For HTTP tests, prefer
WireMockServerover live endpoints - Use FluentAssertions for readable assertions:
result.Should().Be(expected) - Avoid time-sensitive or locale-sensitive assertions; pin formats when needed
- Use
#if NET8_0_OR_GREATERfor TFM-specific APIs
Example Test Structure:
public class MyFeatureTests {
[Fact]
public void Should_do_something() {
// Arrange
var fixture = new Fixture();
var input = fixture.Create<string>();
// Act
var result = MyFeature.Process(input);
// Assert
result.Should().NotBeNull();
}
}Location: .github/workflows/
Triggers: Pull requests (excluding docs/** changes)
Test Matrix:
- Windows: Tests against
net48,net8.0,net9.0,net10.0 - Linux: Tests against
net8.0,net9.0,net10.0(no .NET Framework)
SDK Setup:
dotnet-version: |
8.0.x
9.0.x
10.0.xTest Command:
dotnet test -c Debug -f ${{ matrix.dotnet }}Artifacts: Test results uploaded for each TFM and OS combination
Triggers:
- Push to
devbranch - Tags (for releases)
SDK: .NET 10.0.x (for packaging)
Steps:
- Checkout with full history (
git fetch --prune --unshallowfor MinVer) - NuGet login using OIDC (
NuGet/login@v1) - Pack:
dotnet pack -c Release -o nuget -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg - Push to NuGet.org with
--skip-duplicate
Permissions: Requires id-token: write for OIDC authentication
Publishes test results as GitHub checks.
To replicate CI behavior locally:
Windows (all TFMs):
dotnet test -c Debug -f net48
dotnet test -c Debug -f net8.0
dotnet test -c Debug -f net9.0
dotnet test -c Debug -f net10.0Linux/macOS (no .NET Framework):
dotnet test -c Debug -f net8.0
dotnet test -c Debug -f net9.0
dotnet test -c Debug -f net10.0Tool: MinVer (Git-based semantic versioning)
Configuration (in src/Directory.Build.props):
<PackageReference Include="MinVer" PrivateAssets="All"/>Custom Version Target:
<Target Name="CustomVersion" AfterTargets="MinVer">
<PropertyGroup>
<FileVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch)</FileVersion>
<AssemblyVersion>$(MinVerMajor).$(MinVerMinor).$(MinVerPatch)</AssemblyVersion>
</PropertyGroup>
</Target>Version is determined from Git tags and commit history. Requires unshallow clone for accurate versioning.
NuGet Metadata:
- Icon:
restsharp.png - License: Apache-2.0
- Project URL: https://restsharp.dev
- Repository: https://github.com/restsharp/RestSharp.git
- README: Included in package
Symbol Packages: .snupkg format for debugging
SourceLink: Enabled for source debugging
dotnet pack src/RestSharp/RestSharp.csproj -c Release -o nuget \
-p:IncludeSymbols=true -p:SymbolPackageFormat=snupkgOutput: nuget/RestSharp.<version>.nupkg and RestSharp.<version>.snupkg
Partial Classes: Large classes are split using partial classes with <DependentUpon> in .csproj:
<Compile Update="RestClient.Async.cs">
<DependentUpon>RestClient.cs</DependentUpon>
</Compile>Examples:
RestClient.cswithRestClient.Async.cs,RestClient.Extensions.*.csPropertyCache.cswithPropertyCache.Populator.cs,PropertyCache.Populator.RequestProperty.cs
.editorconfigis used for code formatting and style rules- All source files in
/srcmust have a license header:// Copyright (c) .NET Foundation and Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Adapted from Rebus - Test files (all projects located in
/test) don't need the license header
Nullable Reference Types:
- Enabled in source projects (
Nullable=enable) - Disabled in test projects (
Nullable=disable)
Language Version: preview - allows use of latest C# features
Implicit Usings: Enabled globally
Warnings:
- XML documentation warnings suppressed in source (
NoWarn=1591) - Test-specific warnings suppressed (
xUnit1033,CS8002)
Use conditional compilation and attributes:
#if NET
[UnsupportedOSPlatform("browser")]
#endif
public ICredentials? Credentials { get; set; }#if NET8_0_OR_GREATER
await using var stream = ...
#else
using var stream = ...
#endifDebug build:
dotnet build RestSharp.slnx -c DebugRelease build:
dotnet build RestSharp.slnx -c ReleaseView generated files:
# After building
find src/RestSharp/obj -name "*.g.cs" -o -name "ReadOnly*.cs"Debug generator:
- Set
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>in project - Build project
- Check
obj/<Configuration>/<TFM>/generated/SourceGenerator/
Add new generator:
- Create new class implementing
IIncrementalGenerator - Add
[Generator(LanguageNames.CSharp)]attribute - Implement
Initializemethod - Register source output
Build for specific TFM:
dotnet build src/RestSharp/RestSharp.csproj -f net8.0Check TFM-specific behavior:
- Use
#ifdirectives for conditional compilation - Test against all supported TFMs before committing
- Be aware of API differences (e.g.,
Stream.ReadExactlyin .NET 8+)
Issue: Tests fail on specific TFM
- Solution: Run with
-f <TFM>to isolate, check for TFM-specific APIs
Issue: Source generator not running
- Solution: Clean and rebuild, check
EmitCompilerGeneratedFilessetting
Issue: .NET Framework tests fail on non-Windows
- Solution: Expected behavior; run with
-f net8.0or higher on Linux/macOS
Issue: MinVer version incorrect
- Solution: Ensure full Git history with
git fetch --prune --unshallow
# Build solution
dotnet build RestSharp.slnx -c Release
# Run all tests for a single TFM
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net8.0
# Run a single test by FQN
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj \
--filter "FullyQualifiedName=RestSharp.Tests.ObjectParserTests.ShouldUseRequestProperty" \
-f net8.0
# Pack locally
dotnet pack src/RestSharp/RestSharp.csproj -c Release -o nuget \
-p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
# Generate code coverage
dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net8.0 \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
# View generated source files
find src/RestSharp/obj/Debug -name "*.g.cs" -o -name "ReadOnly*.cs"
# Clean all build artifacts
dotnet clean RestSharp.slnx
rm -rf src/*/bin src/*/obj test/*/bin test/*/obj gen/*/bin gen/*/obj- Main Documentation: https://restsharp.dev
- Repository: https://github.com/restsharp/RestSharp
- NuGet Package: https://www.nuget.org/packages/RestSharp
- License: Apache-2.0