Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8318a16
Fix codegen
MelbourneDeveloper Oct 17, 2025
16ca4d8
Refactor to use Outcome
MelbourneDeveloper Oct 17, 2025
643da51
Use the proper generator
MelbourneDeveloper Oct 17, 2025
b17421d
Fix the nuclia db generator
MelbourneDeveloper Oct 18, 2025
14f4517
Fixes for source gen
MelbourneDeveloper Oct 18, 2025
2477e31
Fix codegen
MelbourneDeveloper Oct 18, 2025
eb55a76
Stuff
MelbourneDeveloper Oct 18, 2025
3635d7a
Fixes
MelbourneDeveloper Oct 18, 2025
ed207b0
Fix generation
MelbourneDeveloper Oct 18, 2025
402c526
Pretty close
MelbourneDeveloper Oct 18, 2025
c56e59d
Pretty close?
MelbourneDeveloper Oct 18, 2025
bd97efe
Looks close
MelbourneDeveloper Oct 18, 2025
55e40a0
Werks
MelbourneDeveloper Oct 18, 2025
190b420
Works?
MelbourneDeveloper Oct 18, 2025
bfb0724
All nuclia db tests pass
MelbourneDeveloper Oct 18, 2025
34116ce
All tests pass!
MelbourneDeveloper Oct 19, 2025
4a75395
Tests spin up container and then spin it down
MelbourneDeveloper Oct 19, 2025
14af39f
Switch to rekuds
MelbourneDeveloper Oct 19, 2025
35ea394
Add MCP
MelbourneDeveloper Oct 19, 2025
0ddf9bf
MCP
MelbourneDeveloper Oct 19, 2025
04e0415
MCP
MelbourneDeveloper Oct 19, 2025
0736960
MCP
MelbourneDeveloper Oct 19, 2025
2e80e70
MCP
MelbourneDeveloper Oct 19, 2025
8fa4ce9
MCP
MelbourneDeveloper Oct 19, 2025
7d75541
Fix formatting
MelbourneDeveloper Oct 19, 2025
f303f0c
format
MelbourneDeveloper Oct 19, 2025
1de66a0
fix action
MelbourneDeveloper Oct 19, 2025
955aee4
format
MelbourneDeveloper Oct 19, 2025
56a5a29
delete unused file
MelbourneDeveloper Oct 19, 2025
a198336
fix
MelbourneDeveloper Oct 19, 2025
ce389b5
remove f# project
MelbourneDeveloper Oct 19, 2025
b938400
fix
MelbourneDeveloper Oct 19, 2025
1b844d2
fix
MelbourneDeveloper Oct 19, 2025
b8abbd7
fix
MelbourneDeveloper Oct 19, 2025
c51d059
fix
MelbourneDeveloper Oct 19, 2025
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
7 changes: 7 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "0.30.2",
"commands": [
"dotnet-csharpier"
],
"rollForward": false
},
"fantomas": {
"version": "6.3.15",
"commands": [
Expand Down
8 changes: 6 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ dotnet_analyzer_diagnostic.category-Globalization.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Readability.severity = error
dotnet_analyzer_diagnostic.category-Ordering.severity = error
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.OrderingRules.severity = none

# Nullability
# CS8602: Dereference of a possibly null reference.
Expand Down Expand Up @@ -41,7 +40,6 @@ dotnet_diagnostic.CA2000.severity = error
dotnet_diagnostic.CA2201.severity = error
dotnet_diagnostic.CS1591.severity = error
dotnet_diagnostic.IDE0022.severity = error
dotnet_diagnostic.CA1054.severity = error
dotnet_diagnostic.CS8600.severity = error
dotnet_diagnostic.CS8601.severity = error
dotnet_diagnostic.CS8603.severity = error
Expand Down Expand Up @@ -373,9 +371,15 @@ dotnet_diagnostic.SA1400.severity = none
dotnet_diagnostic.SA1114.severity = none
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.CA1054.severity = none

dotnet_diagnostic.SA1200.severity = none


# TODO: these are nice. Put back
dotnet_diagnostic.SA1201.severity = none
dotnet_diagnostic.SA1202.severity = none
dotnet_diagnostic.SA1204.severity = none



Expand Down
31 changes: 21 additions & 10 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,36 @@ jobs:
8.0.x
9.0.x

- name: Restore .NET tools
run: dotnet tool restore

- name: Restore dependencies
run: dotnet restore RestClient.sln

- name: Check code formatting with CSharpier
run: |
dotnet tool install --global csharpier
dotnet csharpier --check .
run: dotnet csharpier --check .

- name: Build solution
run: dotnet build RestClient.sln --configuration Release --no-restore /warnaserror

- name: Run code analysis
run: dotnet build RestClient.sln --configuration Release --no-restore /p:RunAnalyzers=true /p:TreatWarningsAsErrors=true

- name: Run Stryker Mutation Testing
working-directory: RestClient.Net.CsTest
run: dotnet stryker --break-at 100

- name: Verify Docker is available
run: |
docker --version
docker compose version

- name: Run all tests with code coverage
run: dotnet test RestClient.sln --configuration Release --no-build --verbosity normal --logger "console;verbosity=detailed" --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Threshold=100 DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ThresholdType=line,branch,method DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ThresholdStat=total

- name: Install Stryker Mutator
run: dotnet tool install --global dotnet-stryker

- name: Run Stryker Mutation Testing
run: dotnet stryker --break-at 100 --reporter "console" --reporter "html"
- name: Cleanup Docker containers
if: always()
run: |
cd Samples/NucliaDbClient
docker compose down -v --remove-orphans || true

- name: Run code analysis
run: dotnet build RestClient.sln --configuration Release --no-restore /p:RunAnalyzers=true /p:TreatWarningsAsErrors=true
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"-u",
"${workspaceFolder}/Samples/NucliaDbClient/api.yaml",
"-o",
"${workspaceFolder}/Samples/RestClient.OpenApiGenerator.Sample.NucliaDB/Generated",
"${workspaceFolder}/Samples/NucliaDbClient/Generated",
"-n",
"NucliaDB.Generated",
"-c",
Expand Down
15 changes: 8 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Code Rules

- NO DUPLICATION - EVER!!!!
- Reduce the AMOUNT of code wherever possible
- NO DUPLICATION - EVER!!!! REMOVING DUPLICATION is the absolute HIGHEST PRIORITY!!!
- YOU ARE NOT ALLOWED TO SKIP TESTS
- No throwing exceptions, except for in tests
- FP style code. Pure functions with immutable types.
- 100% test coverage everywhere
- Don't use Git unless I explicitly ask you to
- Keep functions under 20 LOC
- Keep files under 300 LOC
- Use StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers for code quality
- Ccode analysis warnings are always errors
- Nullable reference types are enabled
- NEVER copy files. Only MOVE files
- Don't use Git unless I explicitly ask you to
- Promote code analysis warnings to errors
- EXHAUSTION001 is a critical error and must be turned on everywhere
- Nullable reference types are enabled and MUST be obeyed
- Do not back files up
- Aggressively pursue these aims, even when it means taking more time on a task

## Build, Test, and Development Commands

Expand Down
212 changes: 212 additions & 0 deletions RestClient.Net.CsTest/HttpClientFactoryExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1686,4 +1686,216 @@ public async Task CreatePatch_CancellationToken_CancelsRequest()

Assert.IsInstanceOfType<TaskCanceledException>(exception);
}

[TestMethod]
public async Task CreateHead_ReturnsSuccessResult()
{
// Arrange
var expectedContent = "Head Success";
using var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(expectedContent),
};
using var httpClient = CreateMockHttpClientFactory(response: response).CreateClient();

var head = CreateHead<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

// Act
var result = await head(httpClient, 123).ConfigureAwait(false);

// Assert
var successValue = +result;
Assert.AreEqual(expectedContent, successValue);
}

[TestMethod]
public async Task CreateHead_ErrorResponse_ReturnsFailureResult()
{
// Arrange
var expectedErrorContent = "Head Failed";
using var errorResponse = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent( /*lang=json,strict*/
"{\"message\":\"Head Failed\"}"
),
};
using var httpClient = CreateMockHttpClientFactory(response: errorResponse).CreateClient();

var head = CreateHead<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

// Act
var result = await head(httpClient, 123).ConfigureAwait(false);

// Assert
var httpError = !result;
if (httpError is not ResponseError(var body, var statusCode, var headers))
{
throw new InvalidOperationException("Expected error response");
}

Assert.AreEqual(HttpStatusCode.BadRequest, statusCode);
Assert.AreEqual(expectedErrorContent, body.Message);
}

[TestMethod]
public async Task CreateHead_CancellationToken_CancelsRequest()
{
// Arrange
using var cts = new CancellationTokenSource();
using var httpClient = CreateMockHttpClientFactory(
exceptionToThrow: new TaskCanceledException()
)
.CreateClient();

var head = CreateHead<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

await cts.CancelAsync().ConfigureAwait(false);

// Act
var result = await head(httpClient, 123, cts.Token).ConfigureAwait(false);

// Assert
var exception = !result switch
{
ExceptionError(var ex) => ex,
ResponseError(var b, var sc, var h) => throw new InvalidOperationException(
"Expected exception error"
),
};

Assert.IsInstanceOfType<TaskCanceledException>(exception);
}

[TestMethod]
public async Task CreateOptions_ReturnsSuccessResult()
{
// Arrange
var expectedContent = "Options Success";
using var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(expectedContent),
};
using var httpClient = CreateMockHttpClientFactory(response: response).CreateClient();

var options = CreateOptions<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

// Act
var result = await options(httpClient, 123).ConfigureAwait(false);

// Assert
var successValue = +result;
Assert.AreEqual(expectedContent, successValue);
}

[TestMethod]
public async Task CreateOptions_ErrorResponse_ReturnsFailureResult()
{
// Arrange
var expectedErrorContent = "Options Failed";
using var errorResponse = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent( /*lang=json,strict*/
"{\"message\":\"Options Failed\"}"
),
};
using var httpClient = CreateMockHttpClientFactory(response: errorResponse).CreateClient();

var options = CreateOptions<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

// Act
var result = await options(httpClient, 123).ConfigureAwait(false);

// Assert
var httpError = !result;
if (httpError is not ResponseError(var body, var statusCode, var headers))
{
throw new InvalidOperationException("Expected error response");
}

Assert.AreEqual(HttpStatusCode.BadRequest, statusCode);
Assert.AreEqual(expectedErrorContent, body.Message);
}

[TestMethod]
public async Task CreateOptions_CancellationToken_CancelsRequest()
{
// Arrange
using var cts = new CancellationTokenSource();
using var httpClient = CreateMockHttpClientFactory(
exceptionToThrow: new TaskCanceledException()
)
.CreateClient();

var options = CreateOptions<string, MyErrorModel, int>(
url: "http://test.com".ToAbsoluteUrl(),
buildRequest: id => new HttpRequestParts(
RelativeUrl: new RelativeUrl($"/items/{id}"),
Body: null,
Headers: null
),
deserializeSuccess: TestDeserializer.Deserialize<string>,
deserializeError: TestDeserializer.Deserialize<MyErrorModel>
);

await cts.CancelAsync().ConfigureAwait(false);

// Act
var result = await options(httpClient, 123, cts.Token).ConfigureAwait(false);

// Assert
var exception = !result switch
{
ExceptionError(var ex) => ex,
ResponseError(var b, var sc, var h) => throw new InvalidOperationException(
"Expected exception error"
),
};

Assert.IsInstanceOfType<TaskCanceledException>(exception);
}
}
10 changes: 0 additions & 10 deletions RestClient.Net.CsTest/Models/User.cs

This file was deleted.

Loading