Imagine a program which exposes a REST service/gRPC service/whatever service, which uses several 3rd party libraries. These libraries can of course throw exceptions if something goes wrong, for example if the user tried to access something which he isn't allowed to.
The problem here is that it isn't possible to check beforehand if the request is correct or if the user has enough rights. So we send the request to the 3rd party library and get an exception.
Is it good practice to catch these exceptions at the top level and map them to status codes like this?
var statusCode = HttpStatusCode.InternalServerError;
if (ex is ArgumentException || ex is ResourceDoesntExistException)
statusCode = HttpStatusCode.BadRequest;
else if (ex is UnauthorizedAccessException)
statusCode = HttpStatusCode.Forbidden;
else if (ex is CustomerNotFoundException)
statusCode = HttpStatusCode.NotFound;
and then return the status code with an error object:
return new ErrorResponse
{
Error = new ErrorDescription
{
Message = ex.Message,
Type = ex.GetType().Name
}
};
Advantages I see by using this approach:
- In the program, we don't have to care if we expose a REST service or a SOAP service or whatever. Simply throw an exception, it will be handled correctly later.
- The caller gets enough and correct information if something goes wrong (as long as the exceptions have meaningful names and information).
- Logging can also be centralised. All unhandled exceptions will be logged right where we convert them into error responses.
Disadvantages:
- It feels a little "hacky".
- ?
What is the correct way to do this?
Edit: Since it was requested, here is what I do for gRPC services, where the error mapping is quite similar:
private RpcException GenerateRpcException(Exception ex)
{
var statusCode = StatusCode.Unknown;
if (ex is ArgumentException || ex is ResourceDoesntExistException)
statusCode = StatusCode.InvalidArgument;
else if (ex is UnauthorizedAccessException)
statusCode = StatusCode.PermissionDenied;
else if (ex is CustomerNotFoundException)
statusCode = StatusCode.NotFound;
var status = new Status(statusCode, ex.Message);
var exceptionName = ex.GetType().Name;
var rpcMetadata = new Metadata
{
{ "exception_name", exceptionName }
};
return new RpcException(status, rpcMetadata);
}