diff --git a/docs/source/index.md b/docs/source/index.md index 701a07b..fedf4c7 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -165,7 +165,7 @@ class PostRequest(JsonRequest): ### Error Messages Format -In case validation fails to pass, the following is the format of the generated response: +In case validation fails to pass, the following is the default format of the generated response: ```js { success: False, @@ -181,7 +181,19 @@ In case validation fails to pass, the following is the format of the generated r } } ``` -All validation error messages will have a HTTP error status code 400. + +This format can be configured using the following flask configurations: +- `SIEVE_RESPONSE_MESSAGE` - Set this to modify your default error message e.g "Invalid Input". +- `SIEVE_INCLUDE_SUCCESS_KEY` - Set this to False to remove the success key from the response. +- `SIEVE_RESPONSE_WRAPPER` - Set this to wrap your response e.g. `data`. + + +### Response Status Code + +By default, all validation error messages will have a HTTP error status code 400. This can be configured by setting the flask config `SIEVE_INVALID_STATUS_CODE`. +```python +app.config['SIEVE_INVALID_STATUS_CODE'] = 422 +``` ### Stopping on First Validation Failure diff --git a/flask_sieve/__init__.py b/flask_sieve/__init__.py index 15c2d06..4836e1f 100644 --- a/flask_sieve/__init__.py +++ b/flask_sieve/__init__.py @@ -2,6 +2,12 @@ from .validator import validate, Validator from .exceptions import ValidationException, register_error_handler + class Sieve: - def __init__(self, app): + def __init__(self, app=None): + if app is not None: + self.init_app(app) + + @staticmethod + def init_app(app): register_error_handler(app) diff --git a/flask_sieve/exceptions.py b/flask_sieve/exceptions.py index 99f4f28..d5c6dc7 100644 --- a/flask_sieve/exceptions.py +++ b/flask_sieve/exceptions.py @@ -1,5 +1,6 @@ from flask import jsonify + class ValidationException(Exception): def __init__(self, errors): self.errors = errors @@ -7,13 +8,20 @@ def __init__(self, errors): def register_error_handler(app): def validations_error_handler(ex): - return jsonify({ - 'success': False, - 'message': 'Validation error', + response = { + 'message': app.config.get('SIEVE_RESPONSE_MESSAGE', 'Validation error'), 'errors': ex.errors - }), 400 + } + + if app.config.get('SIEVE_INCLUDE_SUCCESS_KEY'): + response['success'] = False + + if app.config.get('SIEVE_RESPONSE_WRAPPER'): + response = {app.config.get('SIEVE_RESPONSE_WRAPPER'): response} + + return jsonify(response), app.config.get('SIEVE_INVALID_STATUS_CODE', 400) + app.register_error_handler( ValidationException, validations_error_handler ) - diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 1929ecf..a5abf83 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -18,3 +18,93 @@ def test_error_handler(self): ) self.assertEqual(400, status) self.assertIn('Test error', str(response.get_json())) + + def test_configurable_status_code(self): + app = Flask(__name__) + app.config['SIEVE_INVALID_STATUS_CODE'] = 422 + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(422, status) + self.assertIn('Test error', str(response.get_json())) + + def test_configuring_response_message(self): + app = Flask(__name__) + msg = "Only Chuck Norris can submit invalid data!" + app.config['SIEVE_RESPONSE_MESSAGE'] = msg + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn(msg, str(response.get_json())) + + def test_keeping_success_message(self): + app = Flask(__name__) + app.config['SIEVE_INCLUDE_SUCCESS_KEY'] = True + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertTrue('success' in response.get_json()) + + def test_keeping_removing_message(self): + app = Flask(__name__) + app.config['SIEVE_INCLUDE_SUCCESS_KEY'] = False + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertFalse('success' in response.get_json()) + + def test_wrapping_response_with_data(self): + app = Flask(__name__) + app.config['SIEVE_RESPONSE_WRAPPER'] = 'data' + + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json())) + self.assertTrue('data' in response.get_json()) + + def test_wrapping_response_without_data(self): + app = Flask(__name__) + register_error_handler(app) + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json())) + self.assertFalse('data' in response.get_json()) + self.assertTrue('errors' in response.get_json()) diff --git a/tests/test_sieve.py b/tests/test_sieve.py index 11779b1..1b7978c 100644 --- a/tests/test_sieve.py +++ b/tests/test_sieve.py @@ -19,3 +19,19 @@ def test_registers_error_handler(self): ) self.assertEqual(400, status) self.assertIn('Test error', str(response.get_json())) + + def test_deferring_registration_of_error_handler(self): + app = Flask(__name__) + s = Sieve() + + s.init_app(app) + + self.assertIn(ValidationException, app.error_handler_spec[None][None]) + errors = {'field': 'Test error'} + + with app.app_context(): + response, status = app.error_handler_spec[None][None][ValidationException]( + ValidationException(errors) + ) + self.assertEqual(400, status) + self.assertIn('Test error', str(response.get_json()))