diff --git a/.flake8 b/.flake8 index 3d0894c..8b8914f 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] exclude = __init__.py, venv, *.md, .* +max-line-length = 90 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 6dd6bff..3c0b1a6 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,7 +2,7 @@ ### Build docker image ```bash -docker build -t jtrfaker . +docker build -t flask-inputfilter . ``` ### Run docker container in interactive mode @@ -11,15 +11,15 @@ docker compose up -d ``` ```bash -docker exec -it jtrfaker bash +docker exec -it flask-inputfilter /bin/bash ``` ### Run tests ```bash -docker exec -it jtrfaker pytest +docker exec -it flask-inputfilter pytest ``` ### Run linting ```bash -docker exec -it jtrfaker flake8 +docker exec -it flask-inputfilter black . ``` diff --git a/README.md b/README.md index bb7e7c9..106f5e2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It provides a modular way to clean and ensure that incoming data meets expected pip install flask-inputfilter ``` -## Usage +## Quickstart To use the `InputFilter` class, you need to create a new class that inherits from it and define the fields you want to validate and filter. There are lots of different filters and validators available to use, and you can also create your own custom filters and validators. @@ -19,8 +19,8 @@ There are lots of different filters and validators available to use, and you can ```python from flask_inputfilter import InputFilter from flask_inputfilter.Enum import RegexEnum -from flask_inputfilter.Filter import ToIntegerFilter, ToNullFilter, StringTrimFilter -from flask_inputfilter.Validator import RegexValidator +from flask_inputfilter.Filter import StringTrimFilter, ToIntegerFilter, ToNullFilter +from flask_inputfilter.Validator import IsIntegerValidator, RegexValidator class UpdateZipcodeInputFilter(InputFilter): @@ -31,7 +31,10 @@ class UpdateZipcodeInputFilter(InputFilter): self.add( 'id', required=True, - filters=[ToIntegerFilter(), ToNullFilter()] + filters=[ToIntegerFilter(), ToNullFilter()], + validators=[ + IsIntegerValidator() + ] ) self.add( @@ -41,7 +44,7 @@ class UpdateZipcodeInputFilter(InputFilter): validators=[ RegexValidator( RegexEnum.POSTAL_CODE.value, - 'The email is not in the format of an email.' + 'The zipcode is not in the correct format.' ) ] ) @@ -69,3 +72,25 @@ def updateZipcode(): zipcode = data.get('zipcode') ``` +## Options + +The `add` method takes the following options: + +- [`Required`](#required) +- [`Filter`](src/flask_inputfilter/Filter/README.md) +- [`Validator`](src/flask_inputfilter/Validator/README.md) +- [`Default`](#default) +- [`Fallback`](#fallback) + +### Required + +The `required` option is used to specify if the field is required or not. +If the field is required and not present in the input data, the `validate` method will return a 400 response with the error message. + +### Default + +The `default` option is used to specify a default value to use if the field is not present in the input data. + +### Fallback + +The `fallback` option is used to specify a fallback value to use if the field is not present in the input data, although it is required or the validation fails. diff --git a/pyproject.toml b/pyproject.toml index 9787c3b..d3148be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,6 @@ [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 90 diff --git a/requirements.txt b/requirements.txt index 77163da..7d79c96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ pillow==2.0.0 pytest setuptools twine +black diff --git a/setup.py b/setup.py index a1b7d0b..85e8a23 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,7 @@ version="0.0.3", author="Leander Cain Slotosch", author_email="slotosch.leander@outlook.de", - description="A library to filter and validate input data in" - "Flask applications", + description="A library to filter and validate input data in" "Flask applications", long_description=open("README.md").read(), long_description_content_type="text/markdown", url="https://github.com/LeanderCS/flask-inputfilter", diff --git a/src/flask_inputfilter/Enum/RegexEnum.py b/src/flask_inputfilter/Enum/RegexEnum.py index a48cc6b..c3d4274 100644 --- a/src/flask_inputfilter/Enum/RegexEnum.py +++ b/src/flask_inputfilter/Enum/RegexEnum.py @@ -6,17 +6,18 @@ class RegexEnum(Enum): Enum for regex patterns. """ - EMAIL = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' + EMAIL = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" - IPV4_ADDRESS = r'^(?:\d{1,3}\.){3}\d{1,3}$' - IPV6_ADDRESS = r'^\[?([a-fA-F0-9:]+:+)+[a-fA-F0-9]+\]?$' + IPV4_ADDRESS = r"^(?:\d{1,3}\.){3}\d{1,3}$" + IPV6_ADDRESS = r"^\[?([a-fA-F0-9:]+:+)+[a-fA-F0-9]+\]?$" - ISO_DATE = r'^\d{4}-\d{2}-\d{2}$' - ISO_DATETIME = (r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}' - r'(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$') + ISO_DATE = r"^\d{4}-\d{2}-\d{2}$" + ISO_DATETIME = ( + r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}" r"(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$" + ) - PHONE_NUMBER = r'^\+?[\d\s\-()]{7,}$' + PHONE_NUMBER = r"^\+?[\d\s\-()]{7,}$" - POSTAL_CODE = r'^\d{4,10}$' + POSTAL_CODE = r"^\d{4,10}$" - URL = r'^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$' + URL = r"^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$" diff --git a/src/flask_inputfilter/Exception/ValidationError.py b/src/flask_inputfilter/Exception/ValidationError.py index 0dd44e8..c383c95 100644 --- a/src/flask_inputfilter/Exception/ValidationError.py +++ b/src/flask_inputfilter/Exception/ValidationError.py @@ -1,4 +1,3 @@ - class ValidationError(Exception): """ This class is used to raise an exception when a validation error occurs. diff --git a/src/flask_inputfilter/Filter/ArrayExplodeFilter.py b/src/flask_inputfilter/Filter/ArrayExplodeFilter.py new file mode 100644 index 0000000..b398739 --- /dev/null +++ b/src/flask_inputfilter/Filter/ArrayExplodeFilter.py @@ -0,0 +1,20 @@ +from typing import Any, List, Optional + +from ..Filter.BaseFilter import BaseFilter + + +class ArrayExplodeFilter(BaseFilter): + """ + Filter that splits a string into an array based on a specified delimiter. + """ + + def __init__(self, delimiter: str = ",") -> None: + + self.delimiter = delimiter + + def apply(self, value: Any) -> Optional[List[str]]: + + if not isinstance(value, str): + return None + + return value.split(self.delimiter) diff --git a/src/flask_inputfilter/Filter/README.md b/src/flask_inputfilter/Filter/README.md new file mode 100644 index 0000000..a943fcb --- /dev/null +++ b/src/flask_inputfilter/Filter/README.md @@ -0,0 +1,21 @@ +# Filter + +The `Filter` module contains the filters that can be used to filter the input data. + +## Available filters + +The following filters are available in the `Filter` module: + +1. [`ArrayExplodeFilter`](src/flask_inputfilter/Filter/ArrayExplodeFilter.py) - Explodes the input string into an array. +2. [`StringTrimFilter`](src/flask_inputfilter/Filter/StringTrimFilter.py) - Trims the whitespace from the beginning and end of the string. +3. [`ToBooleanFilter`](src/flask_inputfilter/Filter/ToBooleanFilter.py) - Converts the string to a boolean value. +4. [`ToCamelCaseFilter`](src/flask_inputfilter/Filter/ToCamelCaseFilter.py) - Converts the string to camel case. +5. [`ToFloatFilter`](src/flask_inputfilter/Filter/ToFloatFilter.py) - Converts the string to a float value. +5. [`ToIntegerFilter`](src/flask_inputfilter/Filter/ToIntegerFilter.py) - Converts the string to an integer value. +6. [`ToLowerFilter`](src/flask_inputfilter/Filter/ToLowerFilter.py) - Converts the string to lowercase. +7. [`ToNullFilter`](src/flask_inputfilter/Filter/ToNullFilter.py) - Converts the string to `None` if it is already `None` or `''` (empty string). +8. [`ToPascaleCaseFilter`](src/flask_inputfilter/Filter/ToPascaleCaseFilter.py) - Converts the string to pascal case. +9. [`ToSnakeCaseFilter`](src/flask_inputfilter/Filter/ToSnakeCaseFilter.py) - Converts the string to snake case. +9. [`ToStringFilter`](src/flask_inputfilter/Filter/ToStringFilter.py) - Converts the input to a string value. +9. [`ToUpperFilter`](src/flask_inputfilter/Filter/ToUpperFilter.py) - Converts the string to uppercase. +10. [`WhitespaceCollapseFilter`](src/flask_inputfilter/Filter/WhitespaceCollapseFilter.py) - Collapses the whitespace in the string. diff --git a/src/flask_inputfilter/Filter/ToBoolFilter.py b/src/flask_inputfilter/Filter/ToBooleanFilter.py similarity index 89% rename from src/flask_inputfilter/Filter/ToBoolFilter.py rename to src/flask_inputfilter/Filter/ToBooleanFilter.py index 34dd29a..a2c348c 100644 --- a/src/flask_inputfilter/Filter/ToBoolFilter.py +++ b/src/flask_inputfilter/Filter/ToBooleanFilter.py @@ -3,7 +3,7 @@ from ..Filter import BaseFilter -class ToBoolFilter(BaseFilter): +class ToBooleanFilter(BaseFilter): """ Filter, that transforms the value to a boolean. """ diff --git a/src/flask_inputfilter/Filter/ToCamelCaseFilter.py b/src/flask_inputfilter/Filter/ToCamelCaseFilter.py new file mode 100644 index 0000000..433da22 --- /dev/null +++ b/src/flask_inputfilter/Filter/ToCamelCaseFilter.py @@ -0,0 +1,21 @@ +import re +from typing import Any, Optional + +from ..Filter import BaseFilter + + +class ToCamelCaseFilter(BaseFilter): + """ + Filter that converts a string to camelCase. + """ + + def apply(self, value: Any) -> Optional[str]: + + if not isinstance(value, str): + return None + + value = re.sub(r"[\s-_]+", " ", value).strip() + + value = "".join(word.capitalize() for word in value.split()) + + return value[0].lower() + value[1:] if value else value diff --git a/src/flask_inputfilter/Filter/ToNullFilter.py b/src/flask_inputfilter/Filter/ToNullFilter.py index 98e0aa9..f2d7c60 100644 --- a/src/flask_inputfilter/Filter/ToNullFilter.py +++ b/src/flask_inputfilter/Filter/ToNullFilter.py @@ -10,4 +10,4 @@ class ToNullFilter(BaseFilter): def apply(self, value: Any) -> Optional[Any]: - return None if value in ('', None) else value + return None if value in ("", None) else value diff --git a/src/flask_inputfilter/Filter/ToPascaleCaseFilter.py b/src/flask_inputfilter/Filter/ToPascaleCaseFilter.py new file mode 100644 index 0000000..283c900 --- /dev/null +++ b/src/flask_inputfilter/Filter/ToPascaleCaseFilter.py @@ -0,0 +1,21 @@ +import re +from typing import Any, Optional + +from ..Filter.BaseFilter import BaseFilter + + +class ToPascaleCaseFilter(BaseFilter): + """ + Filter that converts a string to PascalCase. + """ + + def apply(self, value: Any) -> Optional[str]: + + if not isinstance(value, str): + return None + + value = re.sub(r"[\s\-_]+", " ", value).strip() + + value = "".join(word.capitalize() for word in value.split()) + + return value diff --git a/src/flask_inputfilter/Filter/ToSnakeCaseFilter.py b/src/flask_inputfilter/Filter/ToSnakeCaseFilter.py new file mode 100644 index 0000000..22606a7 --- /dev/null +++ b/src/flask_inputfilter/Filter/ToSnakeCaseFilter.py @@ -0,0 +1,21 @@ +import re +from typing import Any, Optional + +from ..Filter.BaseFilter import BaseFilter + + +class ToSnakeCaseFilter(BaseFilter): + """ + Filter that converts a string to snake_case. + """ + + def apply(self, value: Any) -> Optional[str]: + + if not isinstance(value, str): + return None + + value = re.sub(r"(? Optional[str]: + + if not isinstance(value, str): + return None + + value = re.sub(r"\s+", " ", value).strip() + + return value diff --git a/src/flask_inputfilter/Filter/__init__.py b/src/flask_inputfilter/Filter/__init__.py index 60df56a..25f30e3 100644 --- a/src/flask_inputfilter/Filter/__init__.py +++ b/src/flask_inputfilter/Filter/__init__.py @@ -1,9 +1,14 @@ +from .ArrayExplodeFilter import ArrayExplodeFilter from .BaseFilter import BaseFilter from .StringTrimFilter import StringTrimFilter -from .ToBoolFilter import ToBoolFilter +from .ToBooleanFilter import ToBooleanFilter +from .ToCamelCaseFilter import ToCamelCaseFilter from .ToFloatFilter import ToFloatFilter from .ToIntegerFilter import ToIntegerFilter from .ToLowerFilter import ToLowerFilter from .ToNullFilter import ToNullFilter +from .ToPascaleCaseFilter import ToPascaleCaseFilter +from .ToSnakeCaseFilter import ToSnakeCaseFilter from .ToStringFilter import ToStringFilter from .ToUpperFilter import ToUpperFilter +from .WhitespaceCollapseFilter import WhitespaceCollapseFilter diff --git a/src/flask_inputfilter/InputFilter.py b/src/flask_inputfilter/InputFilter.py index 7b98a6a..729c9be 100644 --- a/src/flask_inputfilter/InputFilter.py +++ b/src/flask_inputfilter/InputFilter.py @@ -16,19 +16,25 @@ def __init__(self) -> None: self.fields = {} - def add(self, - name: str, - required: bool = True, - filters: Optional[List[BaseFilter]] = None, - validators: Optional[List[BaseValidator]] = None): + def add( + self, + name: str, + required: bool = True, + default: Any = None, + fallback: Any = None, + filters: Optional[List[BaseFilter]] = None, + validators: Optional[List[BaseValidator]] = None, + ): """ Add the field to the input filter. """ self.fields[name] = { - 'required': required, - 'filters': filters or [], - 'validators': validators or [] + "required": required, + "default": default, + "fallback": fallback, + "filters": filters or [], + "validators": validators or [], } def applyFilters(self, field_name: str, value: Any) -> Any: @@ -41,7 +47,7 @@ def applyFilters(self, field_name: str, value: Any) -> Any: if not field: return value - for filter_ in field['filters']: + for filter_ in field["filters"]: value = filter_.apply(value) return value @@ -56,11 +62,12 @@ def validateField(self, fieldName: str, value: Any) -> None: if not field: return - for validator in field['validators']: + for validator in field["validators"]: validator.validate(value) - def validateData(self, data: Dict[str, Any], - kwargs: Dict[str, Any] = None) -> Dict[str, Any]: + def validateData( + self, data: Dict[str, Any], kwargs: Dict[str, Any] = None + ) -> Dict[str, Any]: """ Validate the input data, considering both request data and URL parameters (kwargs). @@ -70,18 +77,30 @@ def validateData(self, data: Dict[str, Any], kwargs = {} validatedData = {} - combinedData = {**data, **kwargs} for fieldName, fieldInfo in self.fields.items(): value = combinedData.get(fieldName) + value = self.applyFilters(fieldName, value) - if fieldInfo['required'] and value is None: - raise ValidationError(f"Field '{fieldName}' is required.") + if value is None and fieldInfo["required"]: + if fieldInfo["fallback"] is None: + raise ValidationError(f"Field '{fieldName}' is required.") + + value = fieldInfo["fallback"] + + if value is None and fieldInfo["default"] is not None: + value = fieldInfo["default"] if value is not None: - self.validateField(fieldName, value) + try: + self.validateField(fieldName, value) + except ValidationError: + if fieldInfo["fallback"] is not None: + value = fieldInfo["fallback"] + else: + raise validatedData[fieldName] = value @@ -95,10 +114,10 @@ def validate(cls): def decorator(f): def wrapper(*args, **kwargs): - if request.method == 'GET': + if request.method == "GET": data = request.args - elif request.method in ['POST', 'PUT', 'DELETE']: + elif request.method in ["POST", "PUT", "DELETE"]: if not request.is_json: data = request.args @@ -106,8 +125,7 @@ def wrapper(*args, **kwargs): data = request.json else: - return Response( - status=415, response="Unsupported method Type") + return Response(status=415, response="Unsupported method Type") inputFilter = cls() @@ -120,4 +138,5 @@ def wrapper(*args, **kwargs): return f(*args, **kwargs) return wrapper + return decorator diff --git a/src/flask_inputfilter/Validator/ArrayElementValidator.py b/src/flask_inputfilter/Validator/ArrayElementValidator.py index 6ef65cc..b56e802 100644 --- a/src/flask_inputfilter/Validator/ArrayElementValidator.py +++ b/src/flask_inputfilter/Validator/ArrayElementValidator.py @@ -13,9 +13,14 @@ class ArrayElementValidator(BaseValidator): Validator to validate each element in an array. """ - def __init__(self, elementFilter: 'InputFilter') -> None: + def __init__( + self, + elementFilter: "InputFilter", + error_message: str = "Value '{}' is not in '{}'", + ) -> None: self.elementFilter = elementFilter + self.error_message = error_message def validate(self, value: Any) -> None: @@ -28,4 +33,9 @@ def validate(self, value: Any) -> None: value[i] = validated_element except ValidationError: - raise ValidationError(f"Invalid element '{element}' in array") + if "{}" in self.error_message: + raise ValidationError( + self.error_message.format(element, self.elementFilter) + ) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/ArrayLengthValidator.py b/src/flask_inputfilter/Validator/ArrayLengthValidator.py new file mode 100644 index 0000000..dff3f0e --- /dev/null +++ b/src/flask_inputfilter/Validator/ArrayLengthValidator.py @@ -0,0 +1,36 @@ +from typing import Any + +from ..Exception import ValidationError +from ..Validator.BaseValidator import BaseValidator + + +class ArrayLengthValidator(BaseValidator): + """ + Validator that checks if the length of an array is within the specified range. + """ + + def __init__( + self, + min_length: int = 0, + max_length: int = float("inf"), + error_message: str = "Array length must be between {} and {}.", + ) -> None: + + self.min_length = min_length + self.max_length = max_length + self.error_message = error_message + + def validate(self, value: Any) -> None: + + if not isinstance(value, list): + raise ValidationError("Value must be a list.") + + array_length = len(value) + + if not (self.min_length <= array_length <= self.max_length): + if "{}" in self.error_message: + raise ValidationError( + self.error_message.format(self.min_length, self.max_length) + ) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/BaseValidator.py b/src/flask_inputfilter/Validator/BaseValidator.py index 6b810f1..63a2954 100644 --- a/src/flask_inputfilter/Validator/BaseValidator.py +++ b/src/flask_inputfilter/Validator/BaseValidator.py @@ -8,5 +8,4 @@ class BaseValidator: def validate(self, value: Any) -> None: - raise NotImplementedError( - "Validator validate method must be implemented") + raise NotImplementedError("Validator validate method must be implemented") diff --git a/src/flask_inputfilter/Validator/InArrayValidator.py b/src/flask_inputfilter/Validator/InArrayValidator.py index 713d4fd..43ed526 100644 --- a/src/flask_inputfilter/Validator/InArrayValidator.py +++ b/src/flask_inputfilter/Validator/InArrayValidator.py @@ -9,18 +9,32 @@ class InArrayValidator(BaseValidator): Validator that checks if a value is in a given list of allowed values. """ - def __init__(self, haystack: List[Any], strict: bool = False) -> None: + def __init__( + self, + haystack: List[Any], + strict: bool = False, + error_message: str = "Value '{}' is not in the allowed values '{}'.", + ) -> None: self.haystack = haystack self.strict = strict + self.error_message = error_message def validate(self, value: Any) -> None: - if self.strict and value not in self.haystack: - raise ValidationError( - f"Value '{value}' is not in the allowed values.") + try: + if self.strict: + if value not in self.haystack or not any( + isinstance(value, type(item)) for item in self.haystack + ): + raise ValidationError - elif value not in self.haystack: - raise ValidationError( - f"Value '{value}' is not in the allowed values" - f"(non-strict check).") + else: + if value not in self.haystack: + raise ValidationError + + except Exception: + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value, self.haystack)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/InEnumValidator.py b/src/flask_inputfilter/Validator/InEnumValidator.py index 20b3a03..502f36a 100644 --- a/src/flask_inputfilter/Validator/InEnumValidator.py +++ b/src/flask_inputfilter/Validator/InEnumValidator.py @@ -10,13 +10,19 @@ class InEnumValidator(BaseValidator): Validator that checks if a value is in a given Enum. """ - def __init__(self, enumClass: Type[Enum]) -> None: + def __init__( + self, + enumClass: Type[Enum], + error_message: str = "Value '{}' is not an value of '{}'", + ) -> None: self.enumClass = enumClass + self.error_message = error_message def validate(self, value: Any) -> None: - if not any(value.lower() == item.name.lower() - for item in self.enumClass): - raise ValidationError( - f"Value '{value}' is not an value of {self.enumClass}.") + if not any(value.lower() == item.name.lower() for item in self.enumClass): + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value, self.enumClass)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsArrayValidator.py b/src/flask_inputfilter/Validator/IsArrayValidator.py index b9ff7aa..faa09e7 100644 --- a/src/flask_inputfilter/Validator/IsArrayValidator.py +++ b/src/flask_inputfilter/Validator/IsArrayValidator.py @@ -9,7 +9,14 @@ class IsArrayValidator(BaseValidator): Validator that checks if a value is an array. """ + def __init__(self, error_message: str = "Value '{}' is not an array.") -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: if not isinstance(value, list): - raise ValidationError(f"Value '{value}' is not an array.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsBase64ImageCorrectSizeValidator.py b/src/flask_inputfilter/Validator/IsBase64ImageCorrectSizeValidator.py index 08d8429..b473805 100644 --- a/src/flask_inputfilter/Validator/IsBase64ImageCorrectSizeValidator.py +++ b/src/flask_inputfilter/Validator/IsBase64ImageCorrectSizeValidator.py @@ -11,11 +11,16 @@ class IsBase64ImageCorrectSizeValidator(BaseValidator): By default, the image size must be between 1 and 4MB. """ - def __init__(self, minSize: int = 1, - maxSize: int = 4 * 1024 * 1024) -> None: + def __init__( + self, + minSize: int = 1, + maxSize: int = 4 * 1024 * 1024, + error_message: str = "The image is invalid or does not have an allowed size.", + ) -> None: - self.minSize = minSize - self.maxSize = maxSize + self.min_size = minSize + self.max_size = maxSize + self.error_message = error_message def validate(self, value: Any) -> None: @@ -23,10 +28,8 @@ def validate(self, value: Any) -> None: decoded_image = base64.b64decode(value, validate=True) image_size = len(decoded_image) - if not (self.minSize <= image_size <= self.maxSize): - raise ValidationError(f"Image size {image_size} is not " - f"within the range {self.minSize}-" - f"{self.maxSize}.") + if not (self.min_size <= image_size <= self.max_size): + raise ValidationError except Exception: - raise ValidationError("Das Bild ist ungültig oder beschädigt.") + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsBase64ImageValidator.py b/src/flask_inputfilter/Validator/IsBase64ImageValidator.py index 7171e35..0b5442e 100644 --- a/src/flask_inputfilter/Validator/IsBase64ImageValidator.py +++ b/src/flask_inputfilter/Validator/IsBase64ImageValidator.py @@ -12,6 +12,13 @@ class IsBase64ImageValidator(BaseValidator): Validator that checks if a Base64 string is a valid image. """ + def __init__( + self, + error_message: str = "The image is invalid or does not have an allowed size.", + ) -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: try: @@ -20,4 +27,4 @@ def validate(self, value: Any) -> None: image.verify() except Exception: - raise ValidationError("Das Bild ist ungültig oder beschädigt.") + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsBoolValidator.py b/src/flask_inputfilter/Validator/IsBoolValidator.py index e64dd90..0e3b22f 100644 --- a/src/flask_inputfilter/Validator/IsBoolValidator.py +++ b/src/flask_inputfilter/Validator/IsBoolValidator.py @@ -9,7 +9,14 @@ class IsBoolValidator(BaseValidator): Validator that checks if a value is a bool. """ + def __init__(self, error_message: str = "Value '{}' is not a bool.") -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: if not isinstance(value, bool): - raise ValidationError(f"Value '{value}' is not a bool.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsFloatValidator.py b/src/flask_inputfilter/Validator/IsFloatValidator.py index 926e637..9366c3c 100644 --- a/src/flask_inputfilter/Validator/IsFloatValidator.py +++ b/src/flask_inputfilter/Validator/IsFloatValidator.py @@ -8,7 +8,14 @@ class IsFloatValidator(BaseValidator): Validator that checks if a value is a float. """ + def __init__(self, error_message: str = "Value '{}' is not a float.") -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: if not isinstance(value, float): - raise ValidationError(f"Value '{value}' is not a float.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsHexadecimalValidator.py b/src/flask_inputfilter/Validator/IsHexadecimalValidator.py new file mode 100644 index 0000000..3299df0 --- /dev/null +++ b/src/flask_inputfilter/Validator/IsHexadecimalValidator.py @@ -0,0 +1,27 @@ +from typing import Any + +from ..Exception import ValidationError +from ..Validator.BaseValidator import BaseValidator + + +class IsHexadecimalValidator(BaseValidator): + """ + Validator that checks if a value is a valid hexadecimal string. + """ + + def __init__( + self, error_message: str = "Value '{}' is not a valid hexadecimal string." + ) -> None: + + self.error_message = error_message + + def validate(self, value: Any) -> None: + + if not isinstance(value, str): + raise ValidationError("Value must be a string.") + + try: + int(value, 16) + + except ValueError: + raise ValidationError(self.error_message.format(value)) diff --git a/src/flask_inputfilter/Validator/IsInstanceValidator.py b/src/flask_inputfilter/Validator/IsInstanceValidator.py index d83f411..6b6eb94 100644 --- a/src/flask_inputfilter/Validator/IsInstanceValidator.py +++ b/src/flask_inputfilter/Validator/IsInstanceValidator.py @@ -9,12 +9,19 @@ class IsInstanceValidator(BaseValidator): Validator that checks if a value is an instance of a given class. """ - def __init__(self, classType: Type[Any]) -> None: + def __init__( + self, + classType: Type[Any], + error_message: str = "Value '{}' is not an instance of '{}'.", + ) -> None: self.classType = classType + self.error_message = error_message def validate(self, value: Any) -> None: if not isinstance(value, self.classType): - raise ValidationError( - f"Value '{value}' is not an instance of {self.classType}.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value, self.classType)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsIntegerValidator.py b/src/flask_inputfilter/Validator/IsIntegerValidator.py index 8e7b589..490884f 100644 --- a/src/flask_inputfilter/Validator/IsIntegerValidator.py +++ b/src/flask_inputfilter/Validator/IsIntegerValidator.py @@ -9,7 +9,14 @@ class IsIntegerValidator(BaseValidator): Validator that checks if a value is an integer. """ + def __init__(self, error_message: str = "Value '{}' is not an integer.") -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: if not isinstance(value, int): - raise ValidationError(f"Value '{value}' is not an integer.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsJsonValidator.py b/src/flask_inputfilter/Validator/IsJsonValidator.py new file mode 100644 index 0000000..a693f1e --- /dev/null +++ b/src/flask_inputfilter/Validator/IsJsonValidator.py @@ -0,0 +1,28 @@ +import json +from typing import Any + +from ..Exception import ValidationError +from ..Validator.BaseValidator import BaseValidator + + +class IsJsonValidator(BaseValidator): + """ + Validator that checks if a value is a valid JSON string. + """ + + def __init__( + self, error_message: str = "Value '{}' is not a valid JSON string." + ) -> None: + + self.error_message = error_message + + def validate(self, value: Any) -> None: + + try: + json.loads(value) + + except (TypeError, ValueError): + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsStringValidator.py b/src/flask_inputfilter/Validator/IsStringValidator.py index 0d36001..4ca2225 100644 --- a/src/flask_inputfilter/Validator/IsStringValidator.py +++ b/src/flask_inputfilter/Validator/IsStringValidator.py @@ -9,7 +9,14 @@ class IsStringValidator(BaseValidator): Validator that checks if a value is a string. """ + def __init__(self, error_message: str = "Value '{}' is not a string.") -> None: + + self.error_message = error_message + def validate(self, value: Any) -> None: if not isinstance(value, str): - raise ValidationError(f"Value '{value}' is not a string.") + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value)) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/IsUUIDValidator.py b/src/flask_inputfilter/Validator/IsUUIDValidator.py new file mode 100644 index 0000000..f74b05c --- /dev/null +++ b/src/flask_inputfilter/Validator/IsUUIDValidator.py @@ -0,0 +1,26 @@ +import uuid +from typing import Any + +from ..Exception import ValidationError +from ..Validator.BaseValidator import BaseValidator + + +class IsUUIDValidator(BaseValidator): + """ + Validator that checks if a value is a valid UUID string. + """ + + def __init__(self, error_message: str = "Value '{}' is not a valid UUID.") -> None: + + self.error_message = error_message + + def validate(self, value: Any) -> None: + + if not isinstance(value, str): + raise ValidationError("Value must be a string.") + + try: + uuid.UUID(value) + + except ValueError: + raise ValidationError(self.error_message.format(value)) diff --git a/src/flask_inputfilter/Validator/LengthValidator.py b/src/flask_inputfilter/Validator/LengthValidator.py index 16676b3..1e95956 100644 --- a/src/flask_inputfilter/Validator/LengthValidator.py +++ b/src/flask_inputfilter/Validator/LengthValidator.py @@ -1,25 +1,54 @@ +from enum import Enum from typing import Any from ..Exception import ValidationError from ..Validator import BaseValidator +class LengthEnum(Enum): + """ + Enum that defines the possible length types. + """ + + LEAST = "least" + + MOST = "most" + + class LengthValidator(BaseValidator): """ Validator that checks the length of a string value. """ - def __init__(self, minLength: int = 0, maxLength: int = None) -> None: + def __init__( + self, + min_length: int = 0, + max_length: int = None, + error_message: str = "Value '{}' must be at {} '{}' characters long.", + ) -> None: - self.minLength = minLength - self.maxLength = maxLength + self.min_length = min_length + self.max_length = max_length + self.error_message = error_message def validate(self, value: Any) -> None: - if len(value) < self.minLength: - raise ValidationError( - f"Value must be at least {self.minLength} characters long.") - - if self.maxLength is not None and len(value) > self.maxLength: - raise ValidationError( - f"Value must be at most {self.maxLength} characters long.") + if len(value) < self.min_length: + if "{}" in self.error_message: + raise ValidationError( + self.error_message.format( + value, LengthEnum.LEAST.value, self.min_length + ) + ) + + raise ValidationError(self.error_message) + + if self.max_length is not None and len(value) > self.max_length: + if "{}" in self.error_message: + raise ValidationError( + self.error_message.format( + value, LengthEnum.MOST.value, self.max_length + ) + ) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/README.md b/src/flask_inputfilter/Validator/README.md new file mode 100644 index 0000000..8fa70e1 --- /dev/null +++ b/src/flask_inputfilter/Validator/README.md @@ -0,0 +1,26 @@ +# Validator + +The `Validator` class is used to validate the data after the filters have been applied. + +## Available validators + +The following validators are available in the `Validator` module: + +1. [`ArrayElementValidator`](src/flask_inputfilter/Validator/ArrayElementValidator.py) - Validates each element of an array with its own defined InputFilter. +2. [`ArrayLengthValidator`](src/flask_inputfilter/Validator/ArrayLengthValidator.py) - Validates the length of an array. +3. [`InArrayValidator`](src/flask_inputfilter/Validator/InArrayValidator.py) - Validates that the value is in the given array. +4. [`InEnumValidator`](src/flask_inputfilter/Validator/InEnumValidator.py) - Validates that the value is in the given enum. +5. [`IsArrayValidator`](src/flask_inputfilter/Validator/IsArrayValidator.py) - Validates that the value is an array. +6. [`IsBase64ImageCorrectSizeValidator`](src/flask_inputfilter/Validator/IsBase64ImageCorrectSizeValidator.py) - Validates that the value is a base64 encoded string. +7. [`IsBase64ImageValidator`](src/flask_inputfilter/Validator/IsBase64ImageValidator.py) - Validates that the value is a base64 encoded string. +8. [`IsBooleanValidator`](src/flask_inputfilter/Validator/IsBooleanValidator.py) - Validates that the value is a boolean. +9. [`IsFloatValidator`](src/flask_inputfilter/Validator/IsFloatValidator.py) - Validates that the value is a float. +10. [`IsHexadecimalValidator`](src/flask_inputfilter/Validator/IsHexadecimalValidator.py) - Validates that the value is a hexadecimal string. +11. [`IsInstanceValidator`](src/flask_inputfilter/Validator/IsInstanceValidator.py) - Validates that the value is an instance of a class. +12. [`IsIntegerValidator`](src/flask_inputfilter/Validator/IsIntegerValidator.py) - Validates that the value is an integer. +13. [`IsJsonValidator`](src/flask_inputfilter/Validator/IsJsonValidator.py) - Validates that the value is a json string. +14. [`IsStringValidator`](src/flask_inputfilter/Validator/IsStringValidator.py) - Validates that the value is a string. +15. [`IsUUIDValidator`](src/flask_inputfilter/Validator/IsUUIDValidator.py) - Validates that the value is a UUID. +16. [`LengthValidator`](src/flask_inputfilter/Validator/LengthValidator.py) - Validates the length of the value. +17. [`RangeValidator`](src/flask_inputfilter/Validator/RangeValidator.py) - Validates that the value is within a specified range. +18. [`RegexValidator`](src/flask_inputfilter/Validator/RegexValidator.py) - Validates that the value matches a regex pattern. diff --git a/src/flask_inputfilter/Validator/RangeValidator.py b/src/flask_inputfilter/Validator/RangeValidator.py new file mode 100644 index 0000000..6a2be76 --- /dev/null +++ b/src/flask_inputfilter/Validator/RangeValidator.py @@ -0,0 +1,33 @@ +from typing import Any + +from ..Exception import ValidationError +from ..Validator.BaseValidator import BaseValidator + + +class RangeValidator(BaseValidator): + """ + Validator that checks if a numeric value is within a specified range. + """ + + def __init__( + self, + min_value: float = None, + max_value: float = None, + error_message: str = "Value '{}' is not within the range {} to {}.", + ) -> None: + + self.min_value = min_value + self.max_value = max_value + self.error_message = error_message + + def validate(self, value: Any) -> None: + + if (self.min_value is not None and value < self.min_value) or ( + self.max_value is not None and value > self.max_value + ): + if "{}" in self.error_message: + raise ValidationError( + self.error_message.format(value, self.min_value, self.max_value) + ) + + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/RegexValidator.py b/src/flask_inputfilter/Validator/RegexValidator.py index e6e4c93..f318f5e 100644 --- a/src/flask_inputfilter/Validator/RegexValidator.py +++ b/src/flask_inputfilter/Validator/RegexValidator.py @@ -10,17 +10,19 @@ class RegexValidator(BaseValidator): expression pattern. """ - def __init__(self, pattern: str, errorMessage: str = None) -> None: + def __init__( + self, + pattern: str, + error_message: str = "Value '{}' does not match the required pattern '{}'.", + ) -> None: self.pattern = pattern - self.errorMessage = errorMessage + self.error_message = error_message def validate(self, value: str) -> None: if not re.match(self.pattern, value): - if self.errorMessage: - raise ValidationError(self.errorMessage) + if "{}" in self.error_message: + raise ValidationError(self.error_message.format(value, self.pattern)) - raise ValidationError( - f"Value '{value}' does not match the required pattern " - f"'{self.pattern}'.") + raise ValidationError(self.error_message) diff --git a/src/flask_inputfilter/Validator/__init__.py b/src/flask_inputfilter/Validator/__init__.py index 104cc27..849e76a 100644 --- a/src/flask_inputfilter/Validator/__init__.py +++ b/src/flask_inputfilter/Validator/__init__.py @@ -1,5 +1,7 @@ from .ArrayElementValidator import ArrayElementValidator +from .ArrayLengthValidator import ArrayLengthValidator from .BaseValidator import BaseValidator +from .IsHexadecimalValidator import IsHexadecimalValidator from .InArrayValidator import InArrayValidator from .InEnumValidator import InEnumValidator from .IsArrayValidator import IsArrayValidator @@ -9,6 +11,9 @@ from .IsFloatValidator import IsFloatValidator from .IsInstanceValidator import IsInstanceValidator from .IsIntegerValidator import IsIntegerValidator +from .IsJsonValidator import IsJsonValidator from .IsStringValidator import IsStringValidator +from .IsUUIDValidator import IsUUIDValidator from .LengthValidator import LengthValidator +from .RangeValidator import RangeValidator from .RegexValidator import RegexValidator diff --git a/test/data/base64_image.txt b/test/data/base64_image.txt new file mode 100644 index 0000000..f016555 --- /dev/null +++ b/test/data/base64_image.txt @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/test/test_filter.py b/test/test_filter.py new file mode 100644 index 0000000..5cf40f7 --- /dev/null +++ b/test/test_filter.py @@ -0,0 +1,176 @@ +import unittest + +from src.flask_inputfilter.Filter import ( + ToIntegerFilter, + ToNullFilter, + StringTrimFilter, + ToFloatFilter, + ToLowerFilter, + ToUpperFilter, + ToStringFilter, + ToBooleanFilter, + ArrayExplodeFilter, + ToSnakeCaseFilter, + ToPascaleCaseFilter, + WhitespaceCollapseFilter, +) +from src.flask_inputfilter.InputFilter import InputFilter + + +class TestInputFilter(unittest.TestCase): + def setUp(self) -> None: + """ + Set up a InputFilter instance for testing. + """ + + self.inputFilter = InputFilter() + + def test_array_explode_filter(self) -> None: + """ + Test that ArrayExplodeFilter explodes a string to a list. + """ + + self.inputFilter.add( + "tags", + required=False, + filters=[ArrayExplodeFilter()], + ) + + validated_data = self.inputFilter.validateData({"tags": "tag1,tag2,tag3"}) + + self.assertEqual(validated_data["tags"], ["tag1", "tag2", "tag3"]) + + self.inputFilter.add("items", required=False, filters=[ArrayExplodeFilter(";")]) + + validated_data = self.inputFilter.validateData({"items": "item1;item2;item3"}) + + self.assertEqual(validated_data["items"], ["item1", "item2", "item3"]) + + def test_string_trim_filter(self) -> None: + """ + Test that StringTrimFilter trims whitespace. + """ + + self.inputFilter.add( + "trimmed_field", required=False, filters=[StringTrimFilter()] + ) + + validated_data = self.inputFilter.validateData( + {"trimmed_field": " Hello World "} + ) + + self.assertEqual(validated_data["trimmed_field"], "Hello World") + + def test_to_bool_filter(self) -> None: + """ + Test that ToBooleanFilter converts string to boolean. + """ + + self.inputFilter.add("is_active", required=True, filters=[ToBooleanFilter()]) + + validated_data = self.inputFilter.validateData({"is_active": "true"}) + + self.assertTrue(validated_data["is_active"]) + + def test_to_float_filter(self) -> None: + """ + Test that ToFloatFilter converts string to float. + """ + + self.inputFilter.add("price", required=True, filters=[ToFloatFilter()]) + + validated_data = self.inputFilter.validateData({"price": "19.99"}) + + self.assertEqual(validated_data["price"], 19.99) + + def test_to_integer_filter(self) -> None: + """ + Test that ToIntegerFilter converts string to integer. + """ + + self.inputFilter.add("age", required=True, filters=[ToIntegerFilter()]) + + validated_data = self.inputFilter.validateData({"age": "25"}) + + self.assertEqual(validated_data["age"], 25) + + def test_to_lower_filter(self) -> None: + """ + Test that ToLowerFilter converts string to lowercase. + """ + + self.inputFilter.add("username", required=True, filters=[ToLowerFilter()]) + + validated_data = self.inputFilter.validateData({"username": "TESTUSER"}) + + self.assertEqual(validated_data["username"], "testuser") + + def test_to_null_filter(self) -> None: + """ + Test that ToNullFilter transforms empty string to None. + """ + + self.inputFilter.add("optional_field", required=False, filters=[ToNullFilter()]) + + validated_data = self.inputFilter.validateData({"optional_field": ""}) + + self.assertIsNone(validated_data["optional_field"]) + + def test_to_pascal_case_filter(self) -> None: + """ + Test that PascalCaseFilter converts string to pascal case. + """ + + self.inputFilter.add("username", required=True, filters=[ToPascaleCaseFilter()]) + + validated_data = self.inputFilter.validateData({"username": "test user"}) + + self.assertEqual(validated_data["username"], "TestUser") + + def test_snake_case_filter(self) -> None: + """ + Test that SnakeCaseFilter converts string to snake case. + """ + + self.inputFilter.add("username", required=True, filters=[ToSnakeCaseFilter()]) + + validated_data = self.inputFilter.validateData({"username": "TestUser"}) + + self.assertEqual(validated_data["username"], "test_user") + + def test_to_string_filter(self) -> None: + """ + Test that ToStringFilter converts any type to string. + """ + + self.inputFilter.add("age", required=True, filters=[ToStringFilter()]) + + validated_data = self.inputFilter.validateData({"age": 25}) + + self.assertEqual(validated_data["age"], "25") + + def test_to_upper_filter(self) -> None: + """ + Test that ToUpperFilter converts string to uppercase. + """ + + self.inputFilter.add("username", required=True, filters=[ToUpperFilter()]) + + validated_data = self.inputFilter.validateData({"username": "testuser"}) + + self.assertEqual(validated_data["username"], "TESTUSER") + + def test_whitespace_collapse_filter(self) -> None: + """ + Test that WhitespaceCollapseFilter collapses whitespace. + """ + + self.inputFilter.add( + "collapsed_field", required=False, filters=[WhitespaceCollapseFilter()] + ) + + validated_data = self.inputFilter.validateData( + {"collapsed_field": "Hello World"} + ) + + self.assertEqual(validated_data["collapsed_field"], "Hello World") diff --git a/test/test_input_filter.py b/test/test_input_filter.py index 86427ca..98f58bb 100644 --- a/test/test_input_filter.py +++ b/test/test_input_filter.py @@ -1,14 +1,8 @@ import unittest -from enum import Enum from src.flask_inputfilter.Exception import ValidationError -from src.flask_inputfilter.Filter import ToIntegerFilter, ToNullFilter, \ - StringTrimFilter, ToFloatFilter, ToLowerFilter, ToUpperFilter from src.flask_inputfilter.InputFilter import InputFilter -from src.flask_inputfilter.Validator import IsIntegerValidator, \ - LengthValidator, InArrayValidator, RegexValidator, IsArrayValidator, \ - IsFloatValidator, ArrayElementValidator, InEnumValidator, \ - IsBase64ImageCorrectSizeValidator, IsBoolValidator, IsInstanceValidator +from src.flask_inputfilter.Validator import InArrayValidator class TestInputFilter(unittest.TestCase): @@ -19,321 +13,58 @@ def setUp(self) -> None: self.inputFilter = InputFilter() - self.inputFilter.add( - 'age', - required=True, - filters=[ToIntegerFilter()], - validators=[IsIntegerValidator()] - ) - - self.inputFilter.add( - 'name', - required=False, - validators=[ - LengthValidator(minLength=3) - ] - ) - - self.inputFilter.add( - 'gender', - required=False, - validators=[ - InArrayValidator( - haystack=['male', 'female', 'other'] - ) - ] - ) - - self.inputFilter.add( - 'email', - required=False, - validators=[ - RegexValidator( - pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$' - ) - ] - ) - - self.inputFilter.add( - 'tags', - required=False, - validators=[ - IsArrayValidator() - ] - ) - - def test_required_validation(self) -> None: - """ - Test validation of required fields. - """ - - with self.assertRaises(ValidationError): - self.inputFilter.validateData({'age': None}) - - with self.assertRaises(ValidationError): - self.inputFilter.validateData({}) - - def test_length_validation(self) -> None: - """ - Test length validation. - """ - - with self.assertRaises(ValidationError): - self.inputFilter.validateData({'age': 25, 'name': 'Jo'}) - - def test_successful_validation(self) -> None: - """ - Test successful validation. - """ - - data = { - 'age': '40', - 'name': 'Alice', - 'gender': 'female', - 'email': 'alice@example.com'} - validatedData = self.inputFilter.validateData(data) - - self.assertEqual(validatedData['age'], 40) - self.assertEqual(validatedData['name'], 'Alice') - self.assertEqual(validatedData['gender'], 'female') - self.assertEqual(validatedData['email'], 'alice@example.com') - - def test_null_filter(self) -> None: - """ - Test that ToNullFilter transforms empty string to None. - """ - - self.inputFilter.add( - 'optional_field', - required=False, - filters=[ - ToNullFilter()]) - validatedData = self.inputFilter.validateData( - {'age': 25, 'name': 'test', 'optional_field': ''}) - - self.assertIsNone(validatedData['optional_field']) - - def test_invalid_gender(self) -> None: - """ - Test validation for invalid gender. - """ - - with self.assertRaises(ValidationError): - self.inputFilter.validateData( - {'age': 25, 'name': 'Alice', 'gender': 'unknown'}) - - def test_invalid_email_format(self) -> None: - """ - Test validation for invalid email format. - """ - - with self.assertRaises(ValidationError): - self.inputFilter.validateData( - {'age': 25, 'name': 'Alice', 'email': 'invalid_email'}) - - def test_valid_email(self) -> None: - """ - Test successful validation of a valid email format. - """ - - data = {'age': '30', 'name': 'Alice', 'email': 'alice@example.com'} - - validatedData = self.inputFilter.validateData(data) - self.assertEqual(validatedData['email'], 'alice@example.com') - - def test_successful_optional(self) -> None: + def test_optional(self) -> None: """ Test that optional field validation works. """ - data = {'age': '30', 'name': 'Alice'} - validatedData = self.inputFilter.validateData(data) - self.assertIsNone(validatedData.get('gender')) + self.inputFilter.add("name", required=True) - def test_string_trim_filter(self) -> None: - """ - Test that StringTrimFilter trims whitespace. - """ - - self.inputFilter.add( - 'trimmed_field', - required=False, - filters=[ - StringTrimFilter()]) - validatedData = self.inputFilter.validateData( - {'age': 25, 'name': 'test', 'trimmed_field': ' Hello World '}) - self.assertEqual(validatedData['trimmed_field'], 'Hello World') - - def test_is_array_validator(self) -> None: - """ - Test that IsArrayValidator validates array type. - """ - - with self.assertRaises(ValidationError): - self.inputFilter.validateData( - {'age': 25, 'name': 'Alice', 'tags': 'not_an_array'}) - - data = {'age': 25, 'name': 'Alice', 'tags': ['tag1', 'tag2']} - validatedData = self.inputFilter.validateData(data) - self.assertEqual(validatedData['tags'], ['tag1', 'tag2']) - - def test_to_float_filter(self) -> None: - """ - Test that ToFloatFilter converts string to float. - """ - - self.inputFilter.add( - 'price', - required=True, - filters=[ - ToFloatFilter()]) - validatedData = self.inputFilter.validateData( - {'age': 25, 'name': 'test', 'price': '19.99'}) - self.assertEqual(validatedData['price'], 19.99) + self.inputFilter.validateData({"name": "Alice"}) - def test_is_float_validator(self) -> None: - """ - Test that IsFloatValidator validates float type. - """ - - self.inputFilter.add( - 'price', - required=True, - validators=[ - IsFloatValidator()]) with self.assertRaises(ValidationError): - self.inputFilter.validateData( - {'age': 25, 'name': 'Alice', 'price': 'not_a_float'}) - - data = {'age': 25, 'name': 'Alice', 'price': 19.99} - validatedData = self.inputFilter.validateData(data) - self.assertEqual(validatedData['price'], 19.99) - - def test_to_lower_filter(self) -> None: - """ - Test that ToLowerFilter converts string to lowercase. - """ - - self.inputFilter.add( - 'username', - required=True, - filters=[ - ToLowerFilter()]) - validatedData = self.inputFilter.validateData( - {'age': 25, 'name': 'test', 'username': 'TESTUSER'}) - self.assertEqual(validatedData['username'], 'testuser') - - def test_to_upper_filter(self) -> None: - """ - Test that ToUpperFilter converts string to uppercase. - """ - - self.inputFilter.add( - 'username', - required=True, - filters=[ - ToUpperFilter()]) - validatedData = self.inputFilter.validateData( - {'age': 25, 'name': 'test', 'username': 'testuser'}) - self.assertEqual(validatedData['username'], 'TESTUSER') + self.inputFilter.validateData({}) - def test_array_element_validator(self) -> None: + def test_default(self) -> None: """ - Test ArrayElementValidator. + Test that default field works. """ - elementFilter = InputFilter() - elementFilter.add( - 'id', required=True, filters=[ - ToIntegerFilter()], validators=[ - IsIntegerValidator()]) - - self.inputFilter.add( - 'items', required=True, validators=[ - ArrayElementValidator(elementFilter)]) - - valid_data = {'age': 30, 'items': [{'id': '1'}, {'id': '2'}]} - validated_data = self.inputFilter.validateData(valid_data) - self.assertEqual(validated_data['items'], [{'id': 1}, {'id': 2}]) + self.inputFilter.add("available", required=False, default=True) - invalid_data = {'age': 30, 'items': [{'id': '1'}, {'id': 'invalid'}]} - with self.assertRaises(ValidationError): - self.inputFilter.validateData(invalid_data) + # Default case triggert + validated_data = self.inputFilter.validateData({}) - def test_in_enum_validator(self) -> None: - """ - Test InEnumValidator. - """ + self.assertEqual(validated_data["available"], True) - class Color(Enum): - RED = 'red' - GREEN = 'green' - BLUE = 'blue' + # Override default case + validated_data = self.inputFilter.validateData({"available": False}) - self.inputFilter.add( - 'color', required=True, validators=[ - InEnumValidator(Color)]) + self.assertEqual(validated_data["available"], False) - valid_data = {'age': 30, 'color': 'red'} - validated_data = self.inputFilter.validateData(valid_data) - self.assertEqual(validated_data['color'], 'red') - - invalid_data = {'age': 30, 'color': 'yellow'} - with self.assertRaises(ValidationError): - self.inputFilter.validateData(invalid_data) - - def test_is_base64_image_correct_size_validator(self) -> None: + def test_fallback(self) -> None: """ - Test IsBase64ImageCorrectSizeValidator. + Test that fallback field works. """ + self.inputFilter.add("available", required=True, fallback=True) self.inputFilter.add( - 'image', required=True, validators=[ - IsBase64ImageCorrectSizeValidator( - minSize=10, maxSize=50)]) - - valid_data = {'age': 30, 'image': 'iVBORw0KGgoAAAANSUhEUgAAAAUA'} - validated_data = self.inputFilter.validateData(valid_data) - self.assertEqual( - validated_data['image'], - 'iVBORw0KGgoAAAANSUhEUgAAAAUA') - - invalid_data = {'age': 30, 'image': 'iVBORw0KGgoAAAANSUhEUgAAAAU'} - with self.assertRaises(ValidationError): - self.inputFilter.validateData(invalid_data) - - def test_is_bool_validator(self) -> None: - """ - Test IsBoolValidator. - """ - - self.inputFilter.add( - 'is_active', - required=True, - validators=[ - IsBoolValidator()]) - - valid_data = {'age': 30, 'is_active': True} - validated_data = self.inputFilter.validateData(valid_data) - self.assertEqual(validated_data['is_active'], True) - - invalid_data = {'age': 30, 'is_active': 'yes'} - with self.assertRaises(ValidationError): - self.inputFilter.validateData(invalid_data) + "color", + required=False, + fallback="red", + validators=[InArrayValidator(["red", "green", "blue"])], + ) - def test_is_instance_validator(self) -> None: - """ - Test IsInstanceValidator. - """ + # Fallback case triggert + validated_data = self.inputFilter.validateData({"color": "yellow"}) - self.inputFilter.add( - 'user', required=True, validators=[ - IsInstanceValidator(dict)]) + self.assertEqual(validated_data["available"], True) + self.assertEqual(validated_data["color"], "red") - valid_data = {'age': 30, 'user': {'name': 'Alice'}} - validated_data = self.inputFilter.validateData(valid_data) - self.assertEqual(validated_data['user'], {'name': 'Alice'}) + # Override fallback case + validated_data = self.inputFilter.validateData( + {"available": False, "color": "green"} + ) - invalid_data = {'age': 30, 'user': 'Alice'} - with self.assertRaises(ValidationError): - self.inputFilter.validateData(invalid_data) + self.assertEqual(validated_data["available"], False) + self.assertEqual(validated_data["color"], "green") diff --git a/test/test_validator.py b/test/test_validator.py new file mode 100644 index 0000000..202f684 --- /dev/null +++ b/test/test_validator.py @@ -0,0 +1,304 @@ +import unittest +from enum import Enum + +from src.flask_inputfilter.Exception import ValidationError +from src.flask_inputfilter.InputFilter import InputFilter +from src.flask_inputfilter.Validator import ( + IsIntegerValidator, + LengthValidator, + InArrayValidator, + RegexValidator, + IsArrayValidator, + IsFloatValidator, + ArrayElementValidator, + InEnumValidator, + IsBase64ImageCorrectSizeValidator, + IsBoolValidator, + IsInstanceValidator, + RangeValidator, + IsStringValidator, + IsBase64ImageValidator, + ArrayLengthValidator, + IsJsonValidator, + IsHexadecimalValidator, + IsUUIDValidator, +) + + +class TestInputFilter(unittest.TestCase): + def setUp(self) -> None: + """ + Set up a InputFilter instance for testing. + """ + + self.inputFilter = InputFilter() + + def test_array_element_validator(self) -> None: + """ + Test ArrayElementValidator. + """ + + elementFilter = InputFilter() + elementFilter.add( + "id", + required=True, + validators=[IsIntegerValidator()], + ) + + self.inputFilter.add( + "items", required=True, validators=[ArrayElementValidator(elementFilter)] + ) + + validated_data = self.inputFilter.validateData({"items": [{"id": 1}, {"id": 2}]}) + + self.assertEqual(validated_data["items"], [{"id": 1}, {"id": 2}]) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"items": [{"id": 1}, {"id": "invalid"}]}) + + def test_array_length_validator(self) -> None: + """ + Test ArrayLengthValidator. + """ + + self.inputFilter.add( + "items", + required=True, + validators=[ArrayLengthValidator(min_length=2, max_length=5)], + ) + + self.inputFilter.validateData({"items": [1, 2, 3, 4]}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"items": [1]}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"items": [1, 2, 3, 4, 5, 6]}) + + def test_in_array_validator(self) -> None: + """ + Test InArrayValidator. + """ + + self.inputFilter.add( + "color", + required=True, + validators=[InArrayValidator(["red", "green", "blue"])], + ) + + self.inputFilter.validateData({"color": "red"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"color": "yellow"}) + + def test_in_enum_validator(self) -> None: + """ + Test InEnumValidator. + """ + + class Color(Enum): + RED = "red" + GREEN = "green" + BLUE = "blue" + + self.inputFilter.add("color", required=True, validators=[InEnumValidator(Color)]) + + self.inputFilter.validateData({"color": "red"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"color": "yellow"}) + + def test_is_array_validator(self) -> None: + """ + Test that IsArrayValidator validates array type. + """ + + self.inputFilter.add("tags", required=False, validators=[IsArrayValidator()]) + + self.inputFilter.validateData({"tags": ["tag1", "tag2"]}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"tags": "not_an_array"}) + + def test_is_base64_image_correct_size_validator(self) -> None: + """ + Test IsBase64ImageCorrectSizeValidator. + """ + + self.inputFilter.add( + "image", + required=True, + validators=[IsBase64ImageCorrectSizeValidator(minSize=10, maxSize=50)], + ) + + self.inputFilter.validateData({"image": "iVBORw0KGgoAAAANSUhEUgAAAAUA"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"image": "iVBORw0KGgoAAAANSUhEUgAAAAU"}) + + def test_is_base64_image_validator(self) -> None: + """ + Test IsBase64ImageValidator. + """ + + self.inputFilter.add( + "image", required=True, validators=[IsBase64ImageValidator()] + ) + + with open("test/data/base64_image.txt", "r") as file: + self.inputFilter.validateData({"image": file.read()}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"image": "not_a_base64_image"}) + + def test_is_bool_validator(self) -> None: + """ + Test IsBoolValidator. + """ + + self.inputFilter.add("is_active", required=True, validators=[IsBoolValidator()]) + + self.inputFilter.validateData({"is_active": True}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"is_active": "yes"}) + + def test_is_float_validator(self) -> None: + """ + Test that IsFloatValidator validates float type. + """ + + self.inputFilter.add("price", required=True, validators=[IsFloatValidator()]) + + self.inputFilter.validateData({"price": 19.99}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"price": "not_a_float"}) + + def test_is_hexadecimal_validator(self) -> None: + """ + Test that HexadecimalValidator validates hexadecimal format. + """ + + self.inputFilter.add("hex", required=True, validators=[IsHexadecimalValidator()]) + + self.inputFilter.validateData({"hex": "0x1234"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"hex": "not_a_hex"}) + + def test_is_instance_validator(self) -> None: + """ + Test IsInstanceValidator. + """ + + self.inputFilter.add( + "user", required=True, validators=[IsInstanceValidator(dict)] + ) + + self.inputFilter.validateData({"user": {"name": "Alice"}}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"user": "Alice"}) + + def test_is_integer_validator(self) -> None: + """ + Test that IsIntegerValidator validates integer type. + """ + + self.inputFilter.add("age", required=True, validators=[IsIntegerValidator()]) + + self.inputFilter.validateData({"age": 25}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"age": "obviously not an integer"}) + + def test_is_json_validator(self) -> None: + """ + Test that IsJsonValidator validates JSON format. + """ + + self.inputFilter.add("data", required=True, validators=[IsJsonValidator()]) + + self.inputFilter.validateData({"data": '{"name": "Alice"}'}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"data": "not_a_json"}) + + def test_is_string_validator(self) -> None: + """ + Test that IsStringValidator validates string type. + """ + + self.inputFilter.add("name", required=True, validators=[IsStringValidator()]) + + self.inputFilter.validateData({"name": "obviously an string"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"name": 123}) + + def test_is_uuid_validator(self) -> None: + """ + Test that IsUuidValidator validates UUID format. + """ + + self.inputFilter.add("uuid", required=True, validators=[IsUUIDValidator()]) + + self.inputFilter.validateData({"uuid": "550e8400-e29b-41d4-a716-446655440000"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"uuid": "not_a_uuid"}) + + def test_length_validator(self) -> None: + """ + Test that LengthValidator validates the length of a string. + """ + + self.inputFilter.add( + "name", + required=False, + validators=[LengthValidator(min_length=2, max_length=5)], + ) + + self.inputFilter.validateData({"name": "test"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"name": "a"}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"name": "this_is_too_long"}) + + def test_range_validator(self) -> None: + """ + Test that RangeValidator validates numeric values within a specified range. + """ + + self.inputFilter.add( + "range_field", required=False, validators=[RangeValidator(2, 5)] + ) + + self.inputFilter.validateData({"name": "test", "range_field": 3.76}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"name": "test", "range_field": 1.22}) + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"name": "test", "range_field": 7.89}) + + def test_regex_validator(self) -> None: + """ + Test successful validation of a valid regex format. + """ + + self.inputFilter.add( + "email", + required=False, + validators=[RegexValidator(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")], + ) + + validated_data = self.inputFilter.validateData({"email": "alice@example.com"}) + + self.assertEqual(validated_data["email"], "alice@example.com") + + with self.assertRaises(ValidationError): + self.inputFilter.validateData({"email": "invalid_email"})