Skip to content
This repository was archived by the owner on Aug 30, 2025. It is now read-only.

Commit e5a01e4

Browse files
committed
feat: Add new ExceptionMapper<T>(Status, Property, Code, Description) in options;
1 parent 089cee3 commit e5a01e4

File tree

10 files changed

+82
-20
lines changed

10 files changed

+82
-20
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public class Startup
110110
{
111111
options.ExceptionMapper<DuplicatedException>(exception => StatusCodes.Status409Conflict);
112112

113+
options.ExceptionMapper<PropertyException>(exception => (400, exception.Property, exception.Code, exception.Message));
114+
113115
options.ExceptionMapper<ModelStatesException>(exception => (
114116
exception.Status,
115117
exception.Errors.ToDictionary(

samples/PowerUtils.AspNetCore.ErrorHandler.Samples/Controllers/ExceptionsController.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,13 @@ public IActionResult CustomException()
4848
[HttpGet("timeout-exception")]
4949
public IActionResult TimeoutException()
5050
=> throw new TimeoutException();
51+
52+
[HttpGet("property-exception")]
53+
public IActionResult PropertyException()
54+
=> throw new PropertyException("Error validations")
55+
{
56+
Property = "Prop",
57+
Code = "Err",
58+
};
5159
}
5260
}

samples/PowerUtils.AspNetCore.ErrorHandler.Samples/Exceptions/ModelStatesException.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,4 @@ public DuplicatedException() : base("double")
4141
};
4242
}
4343
}
44-
45-
46-
public abstract class PropertyException : Exception
47-
{ // TODO: to finish
48-
public string Property { get; set; }
49-
public string Code { get; set; }
50-
}
5144
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace PowerUtils.AspNetCore.ErrorHandler.Samples.Exceptions
4+
{
5+
public class PropertyException : Exception
6+
{
7+
public PropertyException(string message)
8+
: base(message) { }
9+
10+
public string Property { get; set; }
11+
public string Code { get; set; }
12+
}
13+
}

samples/PowerUtils.AspNetCore.ErrorHandler.Samples/Startup.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Linq;
1+
using System.Linq;
32
using Microsoft.AspNetCore.Authentication;
43
using Microsoft.AspNetCore.Authentication.JwtBearer;
54
using Microsoft.AspNetCore.Builder;
@@ -45,7 +44,7 @@ public void ConfigureServices(IServiceCollection services)
4544
options.PropertyNamingPolicy = PropertyNamingPolicy.SnakeCase;
4645

4746
options.ExceptionMapper<PropertyException>(exception => 599); // Only to test the override a mapping
48-
//options.ExceptionMapper<PropertyException>(exception => (exception.Status, new(exception.Property, new()))); // TODO: to finish
47+
options.ExceptionMapper<PropertyException>(exception => (400, exception.Property, exception.Code, exception.Message));
4948

5049
options.ExceptionMapper<ModelStatesException>(exception => (
5150
exception.Status,
@@ -58,7 +57,6 @@ public void ConfigureServices(IServiceCollection services)
5857
options.ExceptionMapper<CustomException>(exception => 582);
5958

6059
options.ExceptionMapper<TestException>(exception => StatusCodes.Status503ServiceUnavailable);
61-
options.ExceptionMapper<TimeoutException>(exception => StatusCodes.Status504GatewayTimeout);
6260
});
6361

6462

src/ErrorHandlerOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using Microsoft.AspNetCore.Http;
45

56
namespace PowerUtils.AspNetCore.ErrorHandler
@@ -75,6 +76,12 @@ public static void ExceptionMapper<TException>(this ErrorHandlerOptions options,
7576

7677
public static void ExceptionMapper<TException>(this ErrorHandlerOptions options, Func<TException, int> configureMapper)
7778
where TException : Exception
78-
=> options.ExceptionMapper<TException>(e => (configureMapper(e), new Dictionary<string, ErrorDetails>()));
79+
=> options.ExceptionMapper<TException>(e => (configureMapper(e), ImmutableDictionary<string, ErrorDetails>.Empty));
80+
81+
public static void ExceptionMapper<TException>(this ErrorHandlerOptions options, Func<TException, (int Status, string Property, string Code, string Description)> configureMapper)
82+
where TException : Exception => options.ExceptionMapper<TException>(e => (configureMapper(e).Status, new Dictionary<string, ErrorDetails>()
83+
{
84+
[configureMapper(e).Property ?? ""] = new(configureMapper(e).Code, configureMapper(e).Description)
85+
}));
7986
}
8087
}

src/Handlers/ExceptionHandlerMiddleware.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using Microsoft.AspNetCore.Builder;
45
using Microsoft.AspNetCore.Diagnostics;
56
using Microsoft.AspNetCore.Http;
@@ -45,14 +46,20 @@ internal static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicat
4546

4647
var options = httpContext.RequestServices.GetRequiredService<IOptions<ErrorHandlerOptions>>();
4748

48-
IEnumerable<KeyValuePair<string, ErrorDetails>> errors;
49-
(httpContext.Response.StatusCode, errors) = exception.MappingToStatusCode(options.Value);
50-
51-
httpContext.ResetResponse(httpContext.Response.StatusCode);
52-
problemDetails = problemDetailsFactory.Create(httpContext, errors);
53-
49+
try
50+
{
51+
IEnumerable<KeyValuePair<string, ErrorDetails>> errors;
52+
(httpContext.Response.StatusCode, errors) = exception.MappingToStatusCode(options.Value);
53+
httpContext.ResetResponse(httpContext.Response.StatusCode);
54+
problemDetails = problemDetailsFactory.Create(httpContext, errors);
5455

55-
logger.Error(exception, problemDetails.Instance, problemDetails.Status);
56+
logger.Error(exception, problemDetails.Instance, problemDetails.Status);
57+
}
58+
catch(Exception mappingException)
59+
{
60+
problemDetails = problemDetailsFactory.Create(httpContext, ImmutableDictionary<string, ErrorDetails>.Empty);
61+
logger.Error(mappingException, "Error mapping exception");
62+
}
5663
}
5764

5865
// Write error details in body response

src/LoggerExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ namespace PowerUtils.AspNetCore.ErrorHandler
66
{
77
internal static class LoggerExtensions
88
{
9+
public static void Error(this ILogger logger, Exception exception, string message)
10+
=> logger.LogError(
11+
exception,
12+
$"[ERROR HANDLER] > {message}"
13+
);
14+
15+
916
public static void Error(this ILogger logger, Exception exception, string request, int? statusCode)
1017
=> logger.LogError(
1118
exception,

tests/PowerUtils.AspNetCore.ErrorHandler.Tests/Tests/Controllers/ExceptionsControllerTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,5 +252,33 @@ public async void EndpointWithTimeoutException_Request_504StatusCode()
252252
"An unexpected error has occurred."
253253
);
254254
}
255+
256+
[Fact]
257+
public async void PropertyException_Request_400WithErrors()
258+
{
259+
// Arrange
260+
var requestUri = "/exceptions/property-exception";
261+
var options = _testsFixture.GetService<IOptions<ApiBehaviorOptions>>();
262+
263+
264+
// Act
265+
(var response, var content) = await _testsFixture.Client.SendGetAsync(requestUri);
266+
options.Value.ClientErrorMapping.TryGetValue((int)response.StatusCode, out var clientErrorData);
267+
268+
269+
// Assert
270+
response.ValidateResponse(HttpStatusCode.BadRequest);
271+
272+
content.ValidateContent(
273+
HttpStatusCode.BadRequest,
274+
clientErrorData,
275+
"GET: " + requestUri,
276+
"Error validations",
277+
new Dictionary<string, ErrorDetails>()
278+
{
279+
{ "prop", new("Err", "Error validations") }
280+
}
281+
);
282+
}
255283
}
256284
}

tests/PowerUtils.AspNetCore.ErrorHandler.Tests/Utils/HttpClientUtils.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal static class HttpClientUtils
1818
endpoint += parameters.ToQueryString();
1919
}
2020

21-
2221
var response = await client.GetAsync(endpoint);
2322

2423
return (

0 commit comments

Comments
 (0)