diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml
index 6bae9ce..5eb273c 100644
--- a/.github/workflows/ci_build.yml
+++ b/.github/workflows/ci_build.yml
@@ -10,14 +10,11 @@ on:
- 'releases/**'
pull_request:
branches: [ main ]
- paths:
- - '**/*.cs'
- - '**/*.csproj'
jobs:
build:
- name: build-${{matrix.os}}-${{matrix.dotnet}}
+ name: build-${{matrix.os}}
runs-on: ${{ matrix.os }}
defaults:
run:
@@ -25,24 +22,29 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
- dotnet: ['8.x'] # Just a single target for now
steps:
- uses: actions/checkout@v3
- - name: Setup .NET Core
+
+ - name: Setup .NET 8
uses: actions/setup-dotnet@v3
with:
- dotnet-version: ${{ matrix.dotnet }}
+ dotnet-version: 8.x
+
+ - name: Setup .NET 9
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 9.x
- name: Install dependencies
run: dotnet restore
-
+
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
- run: dotnet test --no-restore --verbosity normal --logger trx --collect:"XPlat Code Coverage"
-
+ run: dotnet test --no-build --configuration Release --verbosity normal --logger trx --collect:"XPlat Code Coverage"
+
- name: Combine Coverage Reports # This is because one report is produced per project, and we want one result for all of them.
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4
if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act
@@ -55,7 +57,7 @@ jobs:
tag: "${{ github.run_number }}_${{ github.run_id }}" # Optional tag or build version.
customSettings: "" # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings.
toolpath: "reportgeneratortool" # Default directory for installing the dotnet tool.
-
+
- name: Upload Combined Coverage XML
uses: actions/upload-artifact@v4
if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act
@@ -63,7 +65,7 @@ jobs:
name: coverage
path: ${{ github.workspace }}/Cobertura.xml
retention-days: 5
-
+
- name: Publish Code Coverage Report
uses: irongut/CodeCoverageSummary@v1.3.0
if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act
@@ -77,7 +79,7 @@ jobs:
indicators: true
output: both
thresholds: "10 30" # Red, Yellow
-
+
- name: Add Coverage PR Comment
uses: marocchino/sticky-pull-request-comment@v2
if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' # Skip if running locally with act, or if not a pull request
@@ -92,10 +94,9 @@ jobs:
name: test-results
path: ${{ github.workspace }}/**/TestResults/**/*
retention-days: 5
-
+
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2.16.1
if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' && always() # Skip if running locally with act
with:
trx_files: "${{ github.workspace }}/**/*.trx"
-
\ No newline at end of file
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index ee96bcf..79ee29b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -4,13 +4,6 @@ name: publish
on:
workflow_dispatch: # Allow running the workflow manually from the GitHub UI
- push:
- branches:
- - main # Run the workflow when pushing to the main branch
- - 'releases/**' # Run the workflow when pushing to a release branch
- pull_request:
- branches:
- - '*' # Run the workflow for all pull requests
release:
types:
- published # Run the workflow when a new GitHub release is published
@@ -32,8 +25,15 @@ jobs:
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer
- - name: Setup .NET
+ - name: Setup .NET 8
uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - name: Setup .NET 9
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.x
- run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }}
@@ -52,8 +52,17 @@ jobs:
working-directory: src
steps:
- uses: actions/checkout@v3
- - name: Setup .NET
+
+ - name: Setup .NET 8
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - name: Setup .NET 9
uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.x
+
- name: Run tests
run: dotnet test --configuration Release
@@ -74,8 +83,15 @@ jobs:
name: nuget
path: ${{ env.NuGetDirectory }}
- - name: Setup .NET Core
+ - name: Setup .NET 8
uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - name: Setup .NET 9
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.x
# Publish all NuGet packages to NuGet.org
# Use --skip-duplicate to prevent errors if a package with the same version already exists.
@@ -84,4 +100,4 @@ jobs:
run: |
foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
- }
\ No newline at end of file
+ }
diff --git a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj
index d6a6073..666262b 100644
--- a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj
+++ b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj
@@ -4,7 +4,8 @@
net8.0
enable
enable
- 0.1.0
+ true
+ 0.2.0
feature[23]
feature[23]
https://github.com/feature23/kernel
@@ -23,18 +24,17 @@
-
+
-
+
-
-
+
diff --git a/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs b/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs
index 000e300..c5c8853 100644
--- a/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs
+++ b/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs
@@ -97,7 +97,7 @@ public static IResult ToMinimalApiResult(this Result result, Func success when successMap != null
=> successMap(success.Value),
SuccessResult success
- => HttpResults.Ok(new HypermediaResponse(success.Value)),
+ => HttpResults.Ok(new HypermediaResponse(success.Value!)), // [!]: TODO: Fix nullability issue in F23.Hateoas
PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when useProblemDetails
=> result.ToProblemHttpResult(HttpStatusCode.NotFound),
PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when !useProblemDetails
diff --git a/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs b/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs
index adc8010..21e6ea3 100644
--- a/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs
+++ b/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs
@@ -104,7 +104,7 @@ public static IActionResult ToActionResult(this Result result, Func success when successMap != null
=> successMap(success.Value),
SuccessResult success
- => new OkObjectResult(new HypermediaResponse(success.Value)),
+ => new OkObjectResult(new HypermediaResponse(success.Value!)), // [!]: TODO: Fix nullability issue in F23.Hateoas
PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when useProblemDetails
=> result.ToProblemDetailsResult(HttpStatusCode.NotFound),
PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when !useProblemDetails
diff --git a/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs b/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs
index 57d8c44..109328e 100644
--- a/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs
+++ b/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs
@@ -1,8 +1,6 @@
-using F23.Kernel;
using F23.Kernel.AspNetCore;
using F23.Kernel.Results;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Mvc;
namespace F23.Kernel.Examples.AspNetCore.Core;
@@ -18,43 +16,49 @@ public static class ResultsEndpoints
public static void MapResultsEndpoints(this WebApplication app)
{
var group = app.MapGroup("/minimal-apis/results")
- .WithTags("Results - Minimal APIs")
- .WithOpenApi();
+ .WithTags("Results - Minimal APIs");
group.MapGet("/success-no-value", SuccessNoValue)
.WithName("MinimalApiSuccessNoValue")
.WithSummary("Demonstrates a successful result with no value")
- .WithDescription("Returns 204 No Content");
+ .WithDescription("Returns 204 No Content")
+ .Produces(StatusCodes.Status204NoContent);
group.MapGet("/success-with-value", SuccessWithValue)
.WithName("MinimalApiSuccessWithValue")
.WithSummary("Demonstrates a successful result with a value")
- .WithDescription("Returns 200 OK with the value in the response body");
+ .WithDescription("Returns 200 OK with the value in the response body")
+ .Produces(StatusCodes.Status200OK);
group.MapGet("/validation-failed", ValidationFailed)
.WithName("MinimalApiValidationFailed")
.WithSummary("Demonstrates a validation failed result")
- .WithDescription("Returns 400 Bad Request with validation errors");
+ .WithDescription("Returns 400 Bad Request with validation errors")
+ .Produces(StatusCodes.Status400BadRequest);
group.MapGet("/unauthorized", Unauthorized)
.WithName("MinimalApiUnauthorized")
.WithSummary("Demonstrates an unauthorized result")
- .WithDescription("Returns 403 Forbidden");
+ .WithDescription("Returns 403 Forbidden")
+ .Produces(StatusCodes.Status403Forbidden);
group.MapGet("/not-found", NotFound)
.WithName("MinimalApiNotFound")
.WithSummary("Demonstrates a precondition failed result (not found)")
- .WithDescription("Returns 404 Not Found");
+ .WithDescription("Returns 404 Not Found")
+ .Produces(StatusCodes.Status404NotFound);
group.MapGet("/concurrency-mismatch", ConcurrencyMismatch)
.WithName("MinimalApiConcurrencyMismatch")
.WithSummary("Demonstrates a precondition failed result (concurrency mismatch)")
- .WithDescription("Returns 412 Precondition Failed");
+ .WithDescription("Returns 412 Precondition Failed")
+ .Produces(StatusCodes.Status412PreconditionFailed);
group.MapGet("/conflict", Conflict)
.WithName("MinimalApiConflict")
.WithSummary("Demonstrates a precondition failed result (conflict)")
- .WithDescription("Returns 409 Conflict");
+ .WithDescription("Returns 409 Conflict")
+ .Produces(StatusCodes.Status409Conflict);
}
private static IResult SuccessNoValue()
diff --git a/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj b/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj
index c9353ea..d11c080 100644
--- a/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj
+++ b/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj
@@ -7,8 +7,7 @@
-
-
+
diff --git a/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs b/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs
index 85b80f7..c082d04 100644
--- a/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs
+++ b/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs
@@ -1,4 +1,3 @@
-using F23.Kernel;
using F23.Kernel.AspNetCore;
using F23.Kernel.Results;
using Microsoft.AspNetCore.Mvc;
@@ -18,6 +17,7 @@ public class ResultsController : ControllerBase
///
/// 204 No Content
[HttpGet("success-no-value")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult SuccessNoValue()
{
var result = Result.Success();
@@ -29,6 +29,7 @@ public IActionResult SuccessNoValue()
///
/// 200 OK with the value in the response body
[HttpGet("success-with-value")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult SuccessWithValue()
{
var data = new { message = "Operation completed successfully", timestamp = DateTime.UtcNow };
@@ -41,6 +42,7 @@ public IActionResult SuccessWithValue()
///
/// 400 Bad Request with validation errors
[HttpGet("validation-failed")]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult ValidationFailed()
{
var errors = new[]
@@ -58,6 +60,7 @@ public IActionResult ValidationFailed()
///
/// 403 Forbidden
[HttpGet("unauthorized")]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
public IActionResult UnauthorizedDemo()
{
var result = Result.Unauthorized("User does not have permission to access this resource");
@@ -69,6 +72,7 @@ public IActionResult UnauthorizedDemo()
///
/// 404 Not Found
[HttpGet("not-found")]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult NotFoundDemo()
{
var result = Result.PreconditionFailed(
@@ -83,6 +87,7 @@ public IActionResult NotFoundDemo()
///
/// 412 Precondition Failed
[HttpGet("concurrency-mismatch")]
+ [ProducesResponseType(StatusCodes.Status412PreconditionFailed)]
public IActionResult ConcurrencyMismatch()
{
var result = Result.PreconditionFailed(
@@ -97,6 +102,7 @@ public IActionResult ConcurrencyMismatch()
///
/// 409 Conflict
[HttpGet("conflict")]
+ [ProducesResponseType(StatusCodes.Status409Conflict)]
public IActionResult ConflictDemo()
{
var result = Result.PreconditionFailed(
diff --git a/src/F23.Kernel.Examples.AspNetCore/Program.cs b/src/F23.Kernel.Examples.AspNetCore/Program.cs
index 2f13e86..fae616a 100644
--- a/src/F23.Kernel.Examples.AspNetCore/Program.cs
+++ b/src/F23.Kernel.Examples.AspNetCore/Program.cs
@@ -1,16 +1,28 @@
+using System.Reflection;
using F23.Kernel;
using F23.Kernel.AspNetCore;
using F23.Kernel.Examples.AspNetCore.Core;
using F23.Kernel.Examples.AspNetCore.Infrastructure;
using F23.Kernel.Examples.AspNetCore.UseCases.GetWeatherForecast;
+using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
+var version = typeof(Result).Assembly.GetCustomAttribute()?.InformationalVersion
+ ?? "0.0.0";
+
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+builder.Services.AddSwaggerGen(options =>
+{
+ options.SwaggerDoc($"v{version}", new OpenApiInfo
+ {
+ Title = "F23.Kernel Examples",
+ Version = $"v{version}",
+ });
+});
builder.Services.AddSingleton();
builder.Services.RegisterQueryHandler();
@@ -21,7 +33,10 @@
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
- app.UseSwaggerUI();
+ app.UseSwaggerUI(options =>
+ {
+ options.SwaggerEndpoint($"/swagger/v{version}/swagger.json", $"F23.Kernel Examples v{version}");
+ });
}
app.UseHttpsRedirection();
@@ -34,8 +49,7 @@
return result.ToMinimalApiResult();
})
- .WithName("GetWeatherForecast")
- .WithOpenApi();
+ .WithName("GetWeatherForecast");
app.MapResultsEndpoints();
diff --git a/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj b/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj
index 7fe333b..e61b2ae 100644
--- a/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj
+++ b/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj
@@ -1,19 +1,24 @@
- net8.0
+ net8.0;net9.0
enable
enable
-
false
true
-
-
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/F23.Kernel/EventSourcing/IApplyEvent.cs b/src/F23.Kernel/EventSourcing/IApplyEvent.cs
index 985f0e0..9d3bbb0 100644
--- a/src/F23.Kernel/EventSourcing/IApplyEvent.cs
+++ b/src/F23.Kernel/EventSourcing/IApplyEvent.cs
@@ -21,6 +21,5 @@ public interface IApplyEvent
/// Applies the specified event to the aggregate root and updates its state accordingly.
///
/// The event to be applied to the aggregate root.
- [UsedImplicitly]
TSnapshot Apply(TEvent e);
}
diff --git a/src/F23.Kernel/F23.Kernel.csproj b/src/F23.Kernel/F23.Kernel.csproj
index 36f791b..be984fa 100644
--- a/src/F23.Kernel/F23.Kernel.csproj
+++ b/src/F23.Kernel/F23.Kernel.csproj
@@ -4,7 +4,8 @@
net8.0
enable
enable
- 0.1.0
+ true
+ 0.2.0
feature[23]
feature[23]
https://github.com/feature23/kernel
@@ -25,7 +26,6 @@
-
diff --git a/src/F23.Kernel/GlobalUsings.cs b/src/F23.Kernel/GlobalUsings.cs
index de79f00..be9ca72 100644
--- a/src/F23.Kernel/GlobalUsings.cs
+++ b/src/F23.Kernel/GlobalUsings.cs
@@ -1,2 +1 @@
-global using JetBrains.Annotations;
global using Microsoft.Extensions.DependencyInjection;