Error Handling & Communication
If the web application detects an internal state that prevents it from
proceeding with the processing of a requested task, this is considered
as an exceptional error that is being communicated by programmatically
throwing an exception. (NOTE: Detection of a state considered as error
could also occur in any library or .NET code being called directly or
indirectly. However in any case the throwing of an according exception
type is assumed.)
Because internally with an application, exceptions are the central means
of error communication, the ascension of exceptions thrown from any deep
code must not be blocked or interrupted by catching a exception while
not proceeding with a correct task result processing until the exception
is reaching the final stage of the API execution where a result cover is
being returned.
It should be clear that the more information about an error condition an exception carries, the more error details can be returned to a caller of an API. Ordinary exceptions typically carry just a few pieces of information that could be exploited by an external API caller (e.g. exception type and message - while stuff like a call stack is typically far to technical to make sense to external API consumers). This makes it very desirable to augment an exception with more detail. Preferable at the point of error detection and throwing or at least at a upstream level of the call stack.
Exception Augmentation
With this it is meant to supplement any type of exception with generic information that help to create a end user comprehensible error description where this additional error detail would consist of:
Unique Error Identifier (a.k.a
MsgTemplate
)
This should allow to identify a description of an error. (Technically this could mean to use this error identifier as key into a message lookup table. In order to make this key even more unique it could be generated with a combination of this error identifier and the exception’s source property.)
The error identifier gets represented with text that builds a preliminary error description given in technician speech that is not meant to be used as the final description! It is rather meant as a hint for a translator to find a more appropriate phrasing in the context of an end user.
Any variable parts in the identifier text citing actual values or names must be denoted with placeholders resulting into a message template style like:"No valid entry {entity} with key: '{key}'"
- so that placeholders are given with a name in curly braces.Named Values (a.k.a
TemplateData
)
These arbitrary values are meant to be optionally used to resolve placeholders in aMsgTemplate
.
By adding a using Tlabs;
line to the application code we get access to
helper methods to retrieve supplemental exception details or to attach
additional information to an exception (either to a new or an existing
one).
using Tlabs;
Exception ex;
//obtain ex...
var msgTemplate= ex.MsgTemplate()
var tmplData= ex.TemplateData();
An exception can be augmented either
when throwing
using Tlabs; throw EX.New<KeyNotFoundException>(“No account for '{accountKey}' transaction {transId} defined.", key, id); throw new KeyNotFoundException("Cached account not found").SetTemplateData(“No account for '{accountKey}' transaction {transId} defined.", key, id);
or rethrowing
using Tlabs; try { ... } catch (KeyNotFoundException e) { e.SetMissingTemplateData(“No account for '{accountKey}' transaction {transId} defined.", key, id); throw; }
Notes On Specific Error Conditions
Parameter/Argument Validation
Errors should be reported withArgumentException
(and all derived likeArgumentNullException
,ArgumentOutOfRangeException
, …) by passing at least the faulting parameter name like with:ArgumentNullException(nameof(badParamName))
,ArgumentException(”Developer error message”, nameof(badParamName))
orArgumentOutOfRangeException(nameof(badParamName), badValue, ”Developer error message”)
Entry Not Found
Dictionary item access where the key is not present result into an unspecificKeyNotFoundException
and should be replaced withif (! dict.TryGetValue(key, out var entry) throw EX.New<KeyNotFoundException>("...
LINQ queries for persistent entities like:var entity= querry.Single();
should be changed into:var entity= querry.SingleOrDefault() ?? throw DataEntityNotFoundException<EntityType>(keyValue);
Persistence Errors
Normally ifDataStore.CommitChanges()
throws this is totally unexpected and should result into some 500 server error.
But if any business logic relies on the presence of some unique (key) constraints, we need to catch a resultingDataPersistenceException
to rethrow e.g. aArgumentException
to return a 400 client error.
Surfacing of Detected Errors
Any exception resulting from the detection of internal state that prevents the successful completion of a process, finally needs to be communicated to the initiator.
With a Web API this requires to finally handle the error with a
try { } catch { }
clause in order to communicate the error with the
response cover.
The Web API design proposed with the Tlabs library implements this with
the resolveError()
method of a Controller derived from
Tlabs.Server.Controller.ApiCtrl
.
If your web application introduces any custom exception type to be
specifically handled, the resolveError()
method should be overridden
with a derived Controller like:
protected override string resolveError(Exception e, string msg0= null) {
string msg= base.resolveError(e, msg0);
switch (e) {
case VoucherInvalidException vie:
ctx.Response.StatusCode= StatusCodes.Status400BadRequest;
msg= vie.Message;
break;
}
return msg;
}
API Error Communication
Providing more detailed reasons about error conditions while keeping the overall API response structure is the main reason for returning the API’s data wrapped in a cover.
This is because the error communication capabilities of the HTTP protocol itself are rather limited to the HTTP Status Code of the response. Possible codes are falling roughly into three categories:
Success
Success codes should always give a covered result containing a{ "success": true }
property.200 OK
- Response of a general successful API201 Created
- Response to a POST that results in a creation.
(Should not be used. A POST API to create a resource that actually did not create MUST not return 200 to indicate a failure!)204 No Content
- Response to a successful request that are not returning a body
(should not be used for consistency, a cover with no data should be returned instead)
Client Request Errors
Any errors caused by the client in passing invalid request data that can not be processed by the server. The response should be a JSON cover with anerror
property giving any details about the failure.{ "success": false, "error": "reason" }
400 Bad Request
- The request is malformed, such if the body could not be parsed401 Unauthorized
- No authentication details were provided.403 Forbidden
- When authentication succeeded but the authenticated user doesn't have access to the resource404 Not Found
- When a non-existent resource is requested405 Method Not Allowed
- When the HTTP method is not allowed for the requested resource410 Gone
- Indicates that the resource at this end point is no longer available. Useful as a blanket response for old API versions415 Unsupported Media Type
- If incorrect content type was provided as part of the request422 Unprocessable Entity
- Used for validation errors429 Too Many Requests
- When a request is rejected due to rate limiting
Server Errors
These errors indicate a persistent server problem that a client typically can not fix with a corrected request. To solve such problems the application (deployment) configuration or software needs to be fixed. The error details provided with a response cover can typically only be evaluated by experts of the application.
In the case of client request errors any UI that issued the web API request should present a comprehensible (and potentially localized) problem description to the end user that would allow to correct any user input causing the problem. This typically requires specific error details returned from the API to be translated by the UI into a user comprehensible form. This is exactly where the web API response cover with error details steps in. The response cover and exception creation utilities provided by the Tlabs library supporting a web application to generate an error response like this:
{
"success": false,
"error": "No valid membership Record with key: '000'", //raw error info. for developers
"errDetails": {
"code": "MEMB-ACC", //error (category) code
"msgTemplate": "No valid membership {entity} with key: '{key}'",
"msgData": {
"entity": "Record",
"key": "000"
}
}
}
The properties of an error details object are:
success (boolean)
(false on error) used to discriminate any JSON response data from non error response regardless of the HTTP status code.error (string)
Raw and potentially more detailed error information to be interpreted by developers only.errDetails (object)
Error details to be utilized programmatically (typically from an UI application).code (string)
Technical error code to categorize or narrow the functional area affected by the error.msgTemplate (string)
A (combined withcode
) unique error key to be used to lookup a localized or application specific description (template) of the problem. (Specific placeholders for values provided with themsgData
are given in the form{placeholder-name}
.)msgData (object)
Optional detail data to be used to resolve placeholder values in a message template.
Application Notes:
With an end-user UI themsgTemplate
string (optionally combined with thecode
value) can be used as a key into a locale specific translation.
e.g. “Sorry, but we could not find any account details for the member contract: ‘{key}’. If you are still having problems please call us on 555-123456.”