Error Handling¶
Throw JsonApi*Exception types in your code and the toolkit converts them into JSON:API error responses with the right HTTP status. Only unhandled exceptions get logged with stack traces; handled errors log just type and message.
Exception types¶
| Exception | Status | Use case |
|---|---|---|
JsonApiBadRequestException |
400 | Validation or malformed input |
JsonApiUnauthorizedException |
401 | Not authenticated |
JsonApiForbiddenException |
403 | Not authorized |
JsonApiNotFoundException |
404 | Resource not found |
JsonApiConflictException |
409 | Unique constraint or conflict |
JsonApiTooManyRequestsException |
429 | Rate limited |
Anything else becomes a 500 with full stack trace in logs.
Important
Don't wrap controller actions in a try/catch that swallows everything. The toolkit's exception filter needs the exception to bubble up.
Basic usage¶
if (string.IsNullOrWhiteSpace(request.Title))
throw new JsonApiBadRequestException("Title cannot be empty");
var todo = await _db.Todos.FirstOrDefaultAsync(t => t.Id == id)
?? throw new JsonApiNotFoundException($"Todo {id} not found");
The client gets:
Adding error codes, source pointers, and meta¶
Every JsonApi*Exception accepts optional code, errorSource, and meta arguments for richer responses:
throw new JsonApiBadRequestException(
message: "Invalid email format",
code: "INVALID_EMAIL",
errorSource: new ErrorSource { Pointer = "/data/attributes/email" },
meta: new Dictionary<string, object>
{
["expectedFormat"] = "user@domain.com",
["provided"] = request.Email
}
);
Produces:
{
"errors": [{
"status": "400",
"title": "Bad Request",
"detail": "Invalid email format",
"code": "INVALID_EMAIL",
"source": { "pointer": "/data/attributes/email" },
"meta": { "expectedFormat": "user@domain.com", "provided": "..." }
}]
}
For query-parameter errors, set errorSource: new ErrorSource { Parameter = "sort" } instead of a pointer.
Factory methods¶
For common errors, the JsonApiErrors factory produces consistent codes, sources, and meta without spelling them out each time:
var book = await _db.Books.FindAsync(id)
?? throw JsonApiErrors.NotFound(ResourceType, id);
if (await _db.Users.AnyAsync(u => u.Email == email))
throw JsonApiErrors.AlreadyExists("users", "email", email);
if (string.IsNullOrWhiteSpace(request.Title))
throw JsonApiErrors.RequiredFieldMissing("title");
The full list of factory methods (and the standard error codes they emit) is in the API reference for JsonApiErrors and JsonApiErrorCodes. Use those codes in client code to handle errors programmatically.