Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 6 additions & 9 deletions CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -66,7 +66,7 @@ private async Task AssertHandlerResult(Exception exception, int expectedStatusCo
var problem = await JsonSerializer.DeserializeAsync<TestProblemDetails>(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));
}
Expand All @@ -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());
Expand All @@ -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 }
};
}
Expand Down
13 changes: 3 additions & 10 deletions CR.Exceptions.AspNet/CrExceptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,14 @@ public async ValueTask<bool> 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))
Expand Down Expand Up @@ -78,12 +76,7 @@ public async ValueTask<bool> 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);
Expand Down
24 changes: 7 additions & 17 deletions CR.Exceptions.AspNet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -88,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"
}
```

Expand All @@ -118,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"
}
```
2 changes: 1 addition & 1 deletion CR.Exceptions.Tests.Shared/TestInternalException.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Collections.Immutable;

namespace CR.Exceptions.Tests.Shared;

Check warning on line 3 in CR.Exceptions.Tests.Shared/TestInternalException.cs

View workflow job for this annotation

GitHub Actions / test

Rename namespace CR.Exceptions.Tests.Shared so that it no longer conflicts with the reserved language keyword 'Shared'. Using a reserved keyword as the name of a namespace makes it harder for consumers in other languages to use the namespace. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1716)

public sealed class TestInternalException : InternalException
{
private static readonly ImmutableArray<CrError> _errors = [new("TestInternalCode", "TestInternalMessage")];
private static readonly ImmutableArray<CrError> _errors = [new("TestInternalCode", "Test internal message")];

public TestInternalException() : base(_errors) { }
}
10 changes: 10 additions & 0 deletions CR.Exceptions.Tests.Shared/TestNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Immutable;

namespace CR.Exceptions.Tests.Shared;

public sealed class TestNotFoundException : NotFoundException
{
private static readonly ImmutableArray<CrError> _errors = [new("TestNotFoundCode", "Test not found message")];

public TestNotFoundException(Exception? innerException = null) : base(_errors, innerException) { }
}
2 changes: 1 addition & 1 deletion CR.Exceptions.Tests.Shared/TestUnknownException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace CR.Exceptions.Tests.Shared;

public sealed class TestUnknownException : CrException
{
private static readonly ImmutableArray<CrError> _errors = [new("TestUnknownCode", "TestUnknownMessage")];
private static readonly ImmutableArray<CrError> _errors = [new("TestUnknownCode", "Test unknown message")];

public TestUnknownException(Exception? innerException = null) : base(_errors, "Test unknown exception message", innerException) { }
}
2 changes: 1 addition & 1 deletion CR.Exceptions/ConflictException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected ConflictException(ImmutableArray<CrError> errors, Exception? innerExce
{
}

protected ConflictException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected ConflictException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
7 changes: 3 additions & 4 deletions CR.Exceptions/CrError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

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)
public CrError(string code, string? message = null)
{
ArgumentException.ThrowIfNullOrEmpty(code);
ArgumentException.ThrowIfNullOrEmpty(message);

Code = code;
Message = message;
Expand Down
3 changes: 1 addition & 2 deletions CR.Exceptions/CrException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ public abstract class CrException : Exception
{
public ImmutableArray<CrError> Errors { get; }

protected CrException(ImmutableArray<CrError> errors, string message, Exception? innerException = null) : base(message, innerException)
protected CrException(ImmutableArray<CrError> errors, string? message = null, Exception? innerException = null) : base(message, innerException)
{
ArgumentException.ThrowIfNullOrEmpty(message);
errors.ThrowIfEmptyOrContainsNull();

Errors = errors;
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions/ForbiddenException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected ForbiddenException(ImmutableArray<CrError> errors, Exception? innerExc
{
}

protected ForbiddenException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected ForbiddenException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions/InternalException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected InternalException(ImmutableArray<CrError> errors, Exception? innerExce
{
}

protected InternalException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected InternalException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions/Mapping/ExceptionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ public bool TryCreate(string code, [MaybeNullWhen(false)] out CrException except

private static CrException ExecuteFactory(Func<CrException> factory)
{
return factory() ?? throw new InvalidOperationException($"{nameof(factory)} '{factory.Method.Name}' returned null.");
return factory() ?? throw new InvalidOperationException($"{nameof(factory)} returned null.");
}
}
4 changes: 1 addition & 3 deletions CR.Exceptions/Mapping/ExceptionTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ public bool TryTranslate(Exception exception, [MaybeNullWhen(false)] out CrExcep

private static CrException ExecuteTranslator(Exception innerException, Func<Exception, CrException> translator)
{
return
translator(innerException) ??
throw new InvalidOperationException($"{nameof(translator)} '{translator.Method.Name}' returned null.");
return translator(innerException) ?? throw new InvalidOperationException($"{nameof(translator)} returned null.");
}
}
2 changes: 1 addition & 1 deletion CR.Exceptions/NotFoundException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected NotFoundException(ImmutableArray<CrError> errors, Exception? innerExce
{
}

protected NotFoundException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected NotFoundException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
8 changes: 0 additions & 8 deletions CR.Exceptions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions/UnauthorizedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected UnauthorizedException(ImmutableArray<CrError> errors, Exception? inner
{
}

protected UnauthorizedException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected UnauthorizedException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions/UnprocessableException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ protected UnprocessableException(ImmutableArray<CrError> errors, Exception? inne
{
}

protected UnprocessableException(ImmutableArray<CrError> errors, string message, Exception? innerException = null)
protected UnprocessableException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
4 changes: 2 additions & 2 deletions CR.Exceptions/ValidationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ namespace CR.Exceptions;
public abstract class ValidationException : CrException
{
protected ValidationException(ImmutableArray<CrError> 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<CrError> errors, string message, Exception? innerException = null)
protected ValidationException(ImmutableArray<CrError> errors, string? message, Exception? innerException = null)
: base(errors, message, innerException)
{
}
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ 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/)

---

### CR.Exceptions.AspNet

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)
[README](./CR.Exceptions.AspNet/README.md)
[NuGet](https://www.nuget.org/packages/CrCore.Exceptions.AspNet/)
Loading