Errors and Error handlers for REST API
Errors are an essential part of a REST API system. Error instances must follow a particular structure so the client developer can correctly handle them at the client side. We had set a proper error structure at the beginning of creating REST APIs. It's as follows: { "error": { "message": "Error description", "code": 400 } } Any error occurring during client server communication would follow the above format. Code is the returned status code and message is a brief description of the error. To raise an error we used an _error_abort() function which was an abstraction over Flask-RESTplus abort(). We defined an error structure inside _error_abort() and passed it to abort(). def _error_abort(code, message): error = { 'code': code, 'message': message, } abort(code, error=error) This method had its limitations. Since the error handlers were not being overidden, only errors raised through _error_abort() had the defined structure. So if an Internal Server error occurred, the returned error response wouldn't follow the format. To overcome this, we wrote our own exceptions for errors and created error handlers to handle them. We first made the response structure more detailed, so the client developer can understand what kind of error is being returned. { "error": { "code": 400, "message": "'name' is a required parameter", "status": "INVALID_FIELD", "field": "name" } } The above is an example of a validation error. The "status" key helps making the error more unique. For example we use 400 status code for both validation errors and invalid service errors ("Service not belonging to said event"). But both have different statuses: "INVALID_FIELD" and "INVALID_SERVICE". The "field" key is only useful in the case validation errors, where it names the field that did not pass the validation. For other errors it remains null. I first documented what kind of errors we would need. Code Status Field Description 401 NOT_AUTHORIZED null Invalid token or user not authenticated 400 INVALID_FIELD “field_name” Missing or invalid field 400 INVALID_SERVICE null Service ID mentioned in path does not belong to said Event 404 NOT_FOUND null Event or Service not found 403 PERMISSION_DENIED null User role not allowed to perform such action 500 SERVER_ERROR null Internal server error Next part was creating exception classes for each one these. I created a base error class that extended the python Exception class. class BaseError(Exception): """Base Error Class""" def __init__(self, code=400, message='', status='', field=None): Exception.__init__(self) self.code = code self.message = message self.status = status self.field = field def to_dict(self): return {'code': self.code, 'message': self.message, 'status': self.status, 'field': self.field, } The to_dict() method would help when returning the response in error handlers. I then extended this base class to other error classes. Here are three of them: class NotFoundError(BaseError): def __init__(self, message='Not found'): BaseError.__init__(self) self.code = 404 self.message = message self.status = 'NOT_FOUND' class NotAuthorizedError(BaseError): def __init__(self, message='Unauthorized'): BaseError.__init__(self) self.code = 401 self.message = message self.status = 'NOT_AUTHORIZED' class ValidationError(BaseError): def __init__(self, field, message='Invalid field'): BaseError.__init__(self) self.code = 400 self.message = message self.status = 'INVALID_FIELD' self.field = field I then defined the…
