From 2c1c58848c15141f829bdf80634f58010e38f562 Mon Sep 17 00:00:00 2001 From: apptade Date: Wed, 5 Aug 2026 20:28:45 +0300 Subject: [PATCH 01/11] Update CrError.cs --- CR.Exceptions/CrError.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CR.Exceptions/CrError.cs b/CR.Exceptions/CrError.cs index 76dfcb3..2159cc6 100644 --- a/CR.Exceptions/CrError.cs +++ b/CR.Exceptions/CrError.cs @@ -2,8 +2,8 @@ public record class CrError { - public string Code { get; init; } - public string Message { get; init; } + public string Code { get; } + public string Message { get; } public CrError(string code, string message) { From 413c3c0e97e5fca20c82c4f578f82fb8f5ce4fd1 Mon Sep 17 00:00:00 2001 From: apptade Date: Wed, 5 Aug 2026 20:29:41 +0300 Subject: [PATCH 02/11] Update base mapping --- CR.Exceptions/Mapping/ExceptionFactory.cs | 2 +- CR.Exceptions/Mapping/ExceptionTranslator.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CR.Exceptions/Mapping/ExceptionFactory.cs b/CR.Exceptions/Mapping/ExceptionFactory.cs index e1b7913..774f20d 100644 --- a/CR.Exceptions/Mapping/ExceptionFactory.cs +++ b/CR.Exceptions/Mapping/ExceptionFactory.cs @@ -15,6 +15,6 @@ public bool TryCreate(string code, [MaybeNullWhen(false)] out CrException except private static CrException ExecuteFactory(Func factory) { - return factory() ?? throw new InvalidOperationException($"{nameof(factory)} '{factory.Method.Name}' returned null."); + return factory() ?? throw new InvalidOperationException($"{nameof(factory)} returned null."); } } \ No newline at end of file diff --git a/CR.Exceptions/Mapping/ExceptionTranslator.cs b/CR.Exceptions/Mapping/ExceptionTranslator.cs index 23ebb75..74eb70d 100644 --- a/CR.Exceptions/Mapping/ExceptionTranslator.cs +++ b/CR.Exceptions/Mapping/ExceptionTranslator.cs @@ -15,8 +15,6 @@ public bool TryTranslate(Exception exception, [MaybeNullWhen(false)] out CrExcep private static CrException ExecuteTranslator(Exception innerException, Func translator) { - return - translator(innerException) ?? - throw new InvalidOperationException($"{nameof(translator)} '{translator.Method.Name}' returned null."); + return translator(innerException) ?? throw new InvalidOperationException($"{nameof(translator)} returned null."); } } \ No newline at end of file From a8749291e9c42622bc17422913603aae254b4728 Mon Sep 17 00:00:00 2001 From: apptade Date: Wed, 5 Aug 2026 20:34:42 +0300 Subject: [PATCH 03/11] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79efd4f..c17e16a 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A lightweight framework for defining application errors, creating typed exceptio Core library for defining application errors, exception categories and their creation. -Documentation: -[CR.Exceptions README](./CR.Exceptions/README.md) +[README](./CR.Exceptions/README.md) +[NuGet](https://www.nuget.org/packages/CrCore.Exceptions/) --- @@ -17,5 +17,5 @@ Documentation: ASP.NET Core integration for handling exceptions and converting them into RFC 7807 ProblemDetails responses. -Documentation: -[CR.Exceptions.AspNet README](./CR.Exceptions.AspNet/README.md) \ No newline at end of file +[README](./CR.Exceptions.AspNet/README.md) +[NuGet](https://www.nuget.org/packages/CrCore.Exceptions.AspNet/) \ No newline at end of file From 58458a057da2980642656a342f64feda2613ce30 Mon Sep 17 00:00:00 2001 From: apptade Date: Thu, 6 Aug 2026 16:29:40 +0300 Subject: [PATCH 04/11] Update readmes --- CR.Exceptions.AspNet/README.md | 6 ------ CR.Exceptions/README.md | 8 -------- 2 files changed, 14 deletions(-) diff --git a/CR.Exceptions.AspNet/README.md b/CR.Exceptions.AspNet/README.md index c644228..07fc2f1 100644 --- a/CR.Exceptions.AspNet/README.md +++ b/CR.Exceptions.AspNet/README.md @@ -16,12 +16,6 @@ This package provides automatic handling of `CrException` instances, converts th # Installation -```bash -dotnet add package CrCore.Exceptions.AspNet -``` - -Register the default exception handling during application startup. - ```csharp builder.Services.AddCrExceptionsCore(); diff --git a/CR.Exceptions/README.md b/CR.Exceptions/README.md index 7c12da4..8919145 100644 --- a/CR.Exceptions/README.md +++ b/CR.Exceptions/README.md @@ -15,14 +15,6 @@ This package contains only the core exception model and has no ASP.NET Core depe --- -# Installation - -```bash -dotnet add package CrCore.Exceptions -``` - ---- - # Error Model Every application error is represented by `CrError`. From 8479993ccfed85513a3002b371a553e7c06dc2ea Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 20:51:40 +0300 Subject: [PATCH 05/11] Update CrError.cs --- CR.Exceptions/CrError.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CR.Exceptions/CrError.cs b/CR.Exceptions/CrError.cs index 2159cc6..26805f2 100644 --- a/CR.Exceptions/CrError.cs +++ b/CR.Exceptions/CrError.cs @@ -3,12 +3,11 @@ public record class CrError { public string Code { get; } - public string Message { get; } + public string? Message { get; } - public CrError(string code, string message) + public CrError(string code, string? message = null) { ArgumentException.ThrowIfNullOrEmpty(code); - ArgumentException.ThrowIfNullOrEmpty(message); Code = code; Message = message; From b19a9368457affa1819be3dfdc213d4ea496454d Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 20:53:23 +0300 Subject: [PATCH 06/11] Update CrException.cs --- CR.Exceptions/CrException.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CR.Exceptions/CrException.cs b/CR.Exceptions/CrException.cs index 1f12fc6..a1c5d99 100644 --- a/CR.Exceptions/CrException.cs +++ b/CR.Exceptions/CrException.cs @@ -6,9 +6,8 @@ public abstract class CrException : Exception { public ImmutableArray Errors { get; } - protected CrException(ImmutableArray errors, string message, Exception? innerException = null) : base(message, innerException) + protected CrException(ImmutableArray errors, string? message = null, Exception? innerException = null) : base(message, innerException) { - ArgumentException.ThrowIfNullOrEmpty(message); errors.ThrowIfEmptyOrContainsNull(); Errors = errors; From bc729f47839191267727fd833231c5b877d1ae05 Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 20:57:45 +0300 Subject: [PATCH 07/11] Update base categories --- CR.Exceptions/ConflictException.cs | 2 +- CR.Exceptions/ForbiddenException.cs | 2 +- CR.Exceptions/InternalException.cs | 2 +- CR.Exceptions/NotFoundException.cs | 2 +- CR.Exceptions/UnauthorizedException.cs | 2 +- CR.Exceptions/UnprocessableException.cs | 2 +- CR.Exceptions/ValidationException.cs | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CR.Exceptions/ConflictException.cs b/CR.Exceptions/ConflictException.cs index ef55ccf..3e56219 100644 --- a/CR.Exceptions/ConflictException.cs +++ b/CR.Exceptions/ConflictException.cs @@ -9,7 +9,7 @@ protected ConflictException(ImmutableArray errors, Exception? innerExce { } - protected ConflictException(ImmutableArray errors, string message, Exception? innerException = null) + protected ConflictException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/ForbiddenException.cs b/CR.Exceptions/ForbiddenException.cs index 674adb0..1f42b18 100644 --- a/CR.Exceptions/ForbiddenException.cs +++ b/CR.Exceptions/ForbiddenException.cs @@ -9,7 +9,7 @@ protected ForbiddenException(ImmutableArray errors, Exception? innerExc { } - protected ForbiddenException(ImmutableArray errors, string message, Exception? innerException = null) + protected ForbiddenException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/InternalException.cs b/CR.Exceptions/InternalException.cs index a5f0673..fdbb554 100644 --- a/CR.Exceptions/InternalException.cs +++ b/CR.Exceptions/InternalException.cs @@ -9,7 +9,7 @@ protected InternalException(ImmutableArray errors, Exception? innerExce { } - protected InternalException(ImmutableArray errors, string message, Exception? innerException = null) + protected InternalException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/NotFoundException.cs b/CR.Exceptions/NotFoundException.cs index 2957699..4844c86 100644 --- a/CR.Exceptions/NotFoundException.cs +++ b/CR.Exceptions/NotFoundException.cs @@ -9,7 +9,7 @@ protected NotFoundException(ImmutableArray errors, Exception? innerExce { } - protected NotFoundException(ImmutableArray errors, string message, Exception? innerException = null) + protected NotFoundException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/UnauthorizedException.cs b/CR.Exceptions/UnauthorizedException.cs index f45ddac..4bbb8d4 100644 --- a/CR.Exceptions/UnauthorizedException.cs +++ b/CR.Exceptions/UnauthorizedException.cs @@ -9,7 +9,7 @@ protected UnauthorizedException(ImmutableArray errors, Exception? inner { } - protected UnauthorizedException(ImmutableArray errors, string message, Exception? innerException = null) + protected UnauthorizedException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/UnprocessableException.cs b/CR.Exceptions/UnprocessableException.cs index 58bf754..525247f 100644 --- a/CR.Exceptions/UnprocessableException.cs +++ b/CR.Exceptions/UnprocessableException.cs @@ -9,7 +9,7 @@ protected UnprocessableException(ImmutableArray errors, Exception? inne { } - protected UnprocessableException(ImmutableArray errors, string message, Exception? innerException = null) + protected UnprocessableException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } diff --git a/CR.Exceptions/ValidationException.cs b/CR.Exceptions/ValidationException.cs index 689336a..fb07f39 100644 --- a/CR.Exceptions/ValidationException.cs +++ b/CR.Exceptions/ValidationException.cs @@ -5,11 +5,11 @@ namespace CR.Exceptions; public abstract class ValidationException : CrException { protected ValidationException(ImmutableArray errors, Exception? innerException = null) - : base(errors, "The provided data is invalid. Check the specific errors list.", innerException) + : base(errors, "The provided data is invalid.", innerException) { } - protected ValidationException(ImmutableArray errors, string message, Exception? innerException = null) + protected ValidationException(ImmutableArray errors, string? message, Exception? innerException = null) : base(errors, message, innerException) { } From 4da66f08874f38db476c617b98bb49508afc8ab7 Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 20:58:49 +0300 Subject: [PATCH 08/11] Update CrExceptionHandler.cs --- CR.Exceptions.AspNet/CrExceptionHandler.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/CR.Exceptions.AspNet/CrExceptionHandler.cs b/CR.Exceptions.AspNet/CrExceptionHandler.cs index 058aa8b..afe63b0 100644 --- a/CR.Exceptions.AspNet/CrExceptionHandler.cs +++ b/CR.Exceptions.AspNet/CrExceptionHandler.cs @@ -38,16 +38,14 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e return false; } + var errors = DefaultInternalErrors; var statusCode = StatusCodes.Status500InternalServerError; + var exceptionType = exception.GetType(); var exceptionTypeName = exceptionType.FullName ?? exceptionType.Name; - var errors = DefaultInternalErrors; - var detail = "An unexpected error occurred."; - if (exception is CrException crException) { - detail = crException.Message; errors = crException.Errors; if (_statusCodeMap.TryFind(crException, out var code)) @@ -78,12 +76,7 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e { HttpContext = httpContext, Exception = exception, - ProblemDetails = - { - Status = statusCode, - Detail = detail, - Instance = httpContext.Request.Path - }, + ProblemDetails = { Status = statusCode, }, }; AddProblemDetailsExtension(problemDetailsContext.ProblemDetails, ProblemDetailsExtensionNames.Errors, errors); From 6e174d22c54cb6f68fe1f544907794b042f3a9ba Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 21:05:53 +0300 Subject: [PATCH 09/11] Update CrExceptionHandlerTests.cs --- .../Component/CrExceptionHandlerTests.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs b/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs index 716a7b0..e3973e6 100644 --- a/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs +++ b/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs @@ -66,7 +66,7 @@ private async Task AssertHandlerResult(Exception exception, int expectedStatusCo var problem = await JsonSerializer.DeserializeAsync(responseStream, JsonSerializerOptions.Web); var expectedTraceId = activity?.TraceId.ToHexString() ?? context.TraceIdentifier; - AssertProblemDetails(problem, context, expectedStatusCode, expectedTraceId); + AssertProblemDetails(problem, expectedStatusCode, expectedTraceId); _output.WriteLine(JsonSerializer.Serialize(problem, options: _prettyJsonOptions)); } @@ -77,16 +77,14 @@ private static void AssertHttpContext(HttpContext context, int expectedStatusCod Assert.Contains("application/problem+json", context.Response.ContentType); } - private static void AssertProblemDetails(TestProblemDetails? problem, HttpContext context, int expectedStatusCode, string? expectedTraceId) + private static void AssertProblemDetails(TestProblemDetails? problem, int expectedStatusCode, string? expectedTraceId) { Assert.NotNull(problem); Assert.False(string.IsNullOrEmpty(problem.Type)); Assert.False(string.IsNullOrEmpty(problem.Title)); - Assert.False(string.IsNullOrEmpty(problem.Detail)); Assert.Equal(expectedStatusCode, problem.Status); - Assert.Equal(context.Request.Path, problem.Instance); Assert.True(problem.Extensions.TryGetValue(ProblemDetailsExtensionNames.TraceId, out var traceId)); Assert.Equal(expectedTraceId, traceId!.ToString()); @@ -105,9 +103,8 @@ private static ServiceProvider CreateServiceProvider() private static DefaultHttpContext CreateContext(MemoryStream responseStream) { - return new DefaultHttpContext + return new() { - Request = { Path = "/api/test" }, Response = { Body = responseStream } }; } From 9ba73993a32ea985ce6c78d433fcd8b3430bf15c Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 21:11:03 +0300 Subject: [PATCH 10/11] Update tests --- .../Component/CrExceptionHandlerTests.cs | 6 +++--- CR.Exceptions.Tests.Shared/TestInternalException.cs | 2 +- CR.Exceptions.Tests.Shared/TestNotFoundException.cs | 10 ++++++++++ CR.Exceptions.Tests.Shared/TestUnknownException.cs | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 CR.Exceptions.Tests.Shared/TestNotFoundException.cs diff --git a/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs b/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs index e3973e6..2e2f148 100644 --- a/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs +++ b/CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs @@ -20,11 +20,11 @@ public CrExceptionHandlerTests(ITestOutputHelper output) } [Fact] - public Task Should_Return_500_For_InternalException() + public Task Should_Return_404_For_NotFoundException() { return AssertHandlerResult( - new TestInternalException(), - StatusCodes.Status500InternalServerError, + new TestNotFoundException(), + StatusCodes.Status404NotFound, canCreateActivity: true); } diff --git a/CR.Exceptions.Tests.Shared/TestInternalException.cs b/CR.Exceptions.Tests.Shared/TestInternalException.cs index 3836a57..d67c6e5 100644 --- a/CR.Exceptions.Tests.Shared/TestInternalException.cs +++ b/CR.Exceptions.Tests.Shared/TestInternalException.cs @@ -4,7 +4,7 @@ namespace CR.Exceptions.Tests.Shared; public sealed class TestInternalException : InternalException { - private static readonly ImmutableArray _errors = [new("TestInternalCode", "TestInternalMessage")]; + private static readonly ImmutableArray _errors = [new("TestInternalCode", "Test internal message")]; public TestInternalException() : base(_errors) { } } \ No newline at end of file diff --git a/CR.Exceptions.Tests.Shared/TestNotFoundException.cs b/CR.Exceptions.Tests.Shared/TestNotFoundException.cs new file mode 100644 index 0000000..658e8d5 --- /dev/null +++ b/CR.Exceptions.Tests.Shared/TestNotFoundException.cs @@ -0,0 +1,10 @@ +using System.Collections.Immutable; + +namespace CR.Exceptions.Tests.Shared; + +public sealed class TestNotFoundException : NotFoundException +{ + private static readonly ImmutableArray _errors = [new("TestNotFoundCode", "Test not found message")]; + + public TestNotFoundException(Exception? innerException = null) : base(_errors, innerException) { } +} \ No newline at end of file diff --git a/CR.Exceptions.Tests.Shared/TestUnknownException.cs b/CR.Exceptions.Tests.Shared/TestUnknownException.cs index 53b36ab..82ed37a 100644 --- a/CR.Exceptions.Tests.Shared/TestUnknownException.cs +++ b/CR.Exceptions.Tests.Shared/TestUnknownException.cs @@ -4,7 +4,7 @@ namespace CR.Exceptions.Tests.Shared; public sealed class TestUnknownException : CrException { - private static readonly ImmutableArray _errors = [new("TestUnknownCode", "TestUnknownMessage")]; + private static readonly ImmutableArray _errors = [new("TestUnknownCode", "Test unknown message")]; public TestUnknownException(Exception? innerException = null) : base(_errors, "Test unknown exception message", innerException) { } } \ No newline at end of file From 3ac2f4401c981a2f9f7a8a9fab91e5408ef1f6f9 Mon Sep 17 00:00:00 2001 From: apptade Date: Fri, 7 Aug 2026 21:15:33 +0300 Subject: [PATCH 11/11] Update README.md --- CR.Exceptions.AspNet/README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/CR.Exceptions.AspNet/README.md b/CR.Exceptions.AspNet/README.md index 07fc2f1..d5efc40 100644 --- a/CR.Exceptions.AspNet/README.md +++ b/CR.Exceptions.AspNet/README.md @@ -82,18 +82,16 @@ Example: ```json { - "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1", - "title": "An error occurred while processing your request.", - "status": 500, - "detail": "An unexpected internal error occurred.", - "instance": "/api/test", + "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5", + "title": "Not Found", + "status": 404, "errors": [ { - "code": "TestInternalCode", - "message": "TestInternalMessage" + "code": "TestNotFoundCode", + "message": "Test not found message" } ], - "traceId": "1ca274bed877413cefd8094fc63bd559" + "traceId": "c771b28502648371f14885924e6d1767" } ``` @@ -112,14 +110,12 @@ Example: "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1", "title": "An error occurred while processing your request.", "status": 500, - "detail": "An unexpected error occurred.", - "instance": "/api/test", "errors": [ { "code": "InternalError", "message": "An unexpected internal error occurred." } ], - "traceId": "b217277ea131750f161bc6e8d8b33302" + "traceId": "9bf8d72774b6f2d97cac7607bb19f408" } ``` \ No newline at end of file