Skip to content

Commit 0cdf9a5

Browse files
Merge pull request #37 from intility/generic-exception
feat: ✨ general-purpose exception class
2 parents d22466d + e222bb4 commit 0cdf9a5

3 files changed

Lines changed: 62 additions & 10 deletions

File tree

JsonApiToolkit.Tests/Filters/JsonApiExceptionFilterTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,29 @@ public void OnException_WithUnhandledException_LogsWithStackTrace()
213213
Times.Once
214214
);
215215
}
216+
217+
[Theory]
218+
[InlineData(402, "Payment Required")]
219+
[InlineData(410, "Gone")]
220+
[InlineData(422, "Unprocessable Entity")]
221+
[InlineData(451, "Unavailable For Legal Reasons")]
222+
public void OnException_WithJsonApiHttpException_ReturnsCorrectStatusAndTitle(
223+
int statusCode,
224+
string expectedTitle
225+
)
226+
{
227+
var exception = new JsonApiHttpException(statusCode, "Test message");
228+
var context = CreateExceptionContext(exception);
229+
230+
_filter.OnException(context);
231+
232+
Assert.True(context.ExceptionHandled);
233+
var result = Assert.IsType<ObjectResult>(context.Result);
234+
Assert.Equal(statusCode, result.StatusCode);
235+
var errorResponse = Assert.IsType<JsonApiErrorResponse>(result.Value);
236+
237+
Assert.Single(errorResponse.Errors);
238+
Assert.Equal(statusCode.ToString(), errorResponse.Errors[0].Status);
239+
Assert.Equal(expectedTitle, errorResponse.Errors[0].Title);
240+
}
216241
}

JsonApiToolkit/Filters/JsonApiExceptionFilter.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Net;
2+
using System.Text.RegularExpressions;
13
using JsonApiToolkit.Models.Errors;
24
using Microsoft.AspNetCore.Mvc;
35
using Microsoft.AspNetCore.Mvc.Filters;
@@ -68,15 +70,14 @@ public void OnException(ExceptionContext context)
6870
context.ExceptionHandled = true;
6971
}
7072

71-
private static string GetTitleForStatusCode(int statusCode) =>
72-
statusCode switch
73+
private static string GetTitleForStatusCode(int statusCode)
74+
{
75+
if (Enum.IsDefined(typeof(HttpStatusCode), statusCode))
7376
{
74-
400 => "Bad Request",
75-
401 => "Unauthorized",
76-
403 => "Forbidden",
77-
404 => "Not Found",
78-
409 => "Conflict",
79-
429 => "Too Many Requests",
80-
_ => "Error",
81-
};
77+
// Convert PascalCase enum name to Title Case with spaces
78+
var name = ((HttpStatusCode)statusCode).ToString();
79+
return Regex.Replace(name, "(\\B[A-Z])", " $1");
80+
}
81+
return "Error";
82+
}
8283
}

JsonApiToolkit/Models/Errors/JsonApiErrorTypes.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,29 @@ public JsonApiTooManyRequestsException(
188188
)
189189
: base(429, message, code, errorSource, meta, innerException) { }
190190
}
191+
192+
/// <summary>
193+
/// General-purpose exception for any HTTP status code.
194+
/// Use this when the specific exception types don't cover your use case.
195+
/// </summary>
196+
public class JsonApiHttpException : JsonApiException
197+
{
198+
/// <summary>
199+
/// Initializes a new JSON:API HTTP exception with any status code.
200+
/// </summary>
201+
public JsonApiHttpException(int statusCode, string message)
202+
: base(statusCode, message) { }
203+
204+
/// <summary>
205+
/// Initializes a new JSON:API HTTP exception with additional details.
206+
/// </summary>
207+
public JsonApiHttpException(
208+
int statusCode,
209+
string message,
210+
string? code = null,
211+
ErrorSource? errorSource = null,
212+
Dictionary<string, object>? meta = null,
213+
Exception? innerException = null
214+
)
215+
: base(statusCode, message, code, errorSource, meta, innerException) { }
216+
}

0 commit comments

Comments
 (0)