From b6457e70ce8f73e291ad98d12c112a8689b95ec1 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Tue, 13 Jun 2023 15:40:32 +0545 Subject: [PATCH 1/2] Refactor request body validation using decorator -------------------------------------------------- - Refactored the validation of the request body against a DTO class by implementing a decorator, validate_request_body. - This decorator will be applied to the route handler that requires request body validation. - This decorator validates the request body using the provided DTO class and stores the validated DTO instance in request.validated_dto. - This approach improves code organization, promotes code reuse, and enhances readability. - Added documentation to the decorator to clarify its purpose and behavior. This commit improves the validation process for request bodies and enhances the maintainability and readability of the codebase. --- backend/models/dtos/__init__.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/models/dtos/__init__.py b/backend/models/dtos/__init__.py index e69de29bb2..00eed9183a 100644 --- a/backend/models/dtos/__init__.py +++ b/backend/models/dtos/__init__.py @@ -0,0 +1,33 @@ +from functools import wraps +from flask import request +from schematics.exceptions import DataError + + +from backend.exceptions import BadRequest + + +def get_validation_errors(e): + """Returns a list of validation errors from a schematics DataError""" + return [ + {"field": field, "message": str(error[0])} for field, error in e.errors.items() + ] + + +def validate_request_body(dto_class): + """Decorator to validate request body against a DTO class""" + + def decorator(f): + @wraps(f) + def wrapper(*args, **kwargs): + try: + dto = dto_class(request.get_json()) + dto.validate() + request.validated_dto = dto + except DataError as e: + field_errors = get_validation_errors(e) + raise BadRequest(field_errors=field_errors) + return f(*args, **kwargs) + + return wrapper + + return decorator From 9492dd97a8f64f14084230804b4f3cc146a0c861 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Wed, 21 Jun 2023 11:18:41 +0545 Subject: [PATCH 2/2] Refactor request body validation decorator and improve request handling in views. --------------------------------------------------------- The previous implementation of the request body validation decorator has been refactored to provide better flexibility and handling of requests. The new implementation introduces the following changes: - Renamed the decorator from validate_request_body to validate_request to reflect its broader functionality of validating the entire request, including request body, query parameters, and path parameters. - Added a note in the decorator's docstring regarding the recommended order of applying decorators, emphasizing that it should come after the token_auth decorator if used. This ensures that the authenticated user ID can be set on the DTO if it has a user_id field. - Modified the decorator to dynamically set attribute values on the DTO based on the request's body, query parameters, and path parameters. This enables more flexible assignment of values to DTO attributes, accommodating different request sources. - Introduced the use of request.is_json to check if the request body is in JSON format before accessing its attributes. This helps avoid errors when handling non-JSON request bodies. - Added support for setting the authenticated user ID on the DTO if it contains a user_id field. The user ID is obtained from the token_auth service's current_user() method. This decorator should only be applied after the token_auth decorator (if token_auth is used) so that this decorator can access token_auth.current_user. --- backend/models/dtos/__init__.py | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/backend/models/dtos/__init__.py b/backend/models/dtos/__init__.py index 00eed9183a..457fd2410a 100644 --- a/backend/models/dtos/__init__.py +++ b/backend/models/dtos/__init__.py @@ -13,16 +13,43 @@ def get_validation_errors(e): ] -def validate_request_body(dto_class): - """Decorator to validate request body against a DTO class""" +def validate_request(dto_class): + """ + Decorator to validate request against a DTO class. + -------------------------------- + NOTE: This decorator should be applied after the token_auth decorator (if used) so that + the authenticated user id can be set on the DTO if the DTO has a user_id field. + -------------------------------- + Parameters: + dto_class: DTO + The DTO class to validate against + """ def decorator(f): @wraps(f) def wrapper(*args, **kwargs): + from backend.services.users.authentication_service import token_auth + try: - dto = dto_class(request.get_json()) + dto = dto_class() + + # Set attribute values from request body, query parameters, and path parameters + for attr in dto.__class__._fields: + if request.is_json and attr in request.json: + setattr(dto, attr, request.json[attr]) + elif attr in request.args: + setattr(dto, attr, request.args.get(attr)) + elif attr in kwargs: + setattr(dto, attr, kwargs[attr]) + + # Set authenticated user id if user_id is a field in the DTO + if "user_id" in dto.__class__._fields: + dto.user_id = token_auth.current_user() + dto.validate() - request.validated_dto = dto + request.validated_dto = ( + dto # Set validated DTO on request object for use in view function + ) except DataError as e: field_errors = get_validation_errors(e) raise BadRequest(field_errors=field_errors)