belso.validator.validator — belso 0.0.1 documentation

# Source code for belso.validator.validator

    import json
    from typing import Any, Dict, Type, Union

    from belso.schemas import Schema
    from belso.utils.logging import get_logger

    # Get a module-specific logger
    logger = get_logger(__name__)

    class SchemaValidator:

        @staticmethod
        def validate(
                data: Union[Dict[str, Any], str],
                schema: Type[Schema]
            ) -> Dict[str, Any]:
            """
            Validate that the provided data conforms to the given schema.\n
            ---
            ### Args
            - `data` (`Any`): the data to validate (either a dict or JSON string).
            - `schema` (`Type[Schema]`): the schema to validate against.\n
            ---
            ### Returns:
            - `Dict[str, Any]`: the validated data.
            """
            try:
                schema_name = schema.name if hasattr(schema, "name") else "unnamed"
                logger.debug(f"Starting validation against schema '{schema_name}'...")

                # Convert string to dict if needed
                if isinstance(data, str):
                    logger.debug("Input data is a string, attempting to parse as JSON...")
                    try:
                        data = json.loads(data)
                        logger.debug("Successfully parsed JSON string.")
                    except json.JSONDecodeError as e:
                        logger.error(f"Failed to parse JSON string: {e}")
                        logger.debug("JSON parsing error details", exc_info=True)
                        raise ValueError("Invalid JSON string provided")

                # Get required fields
                required_fields = schema.get_required_fields()
                logger.debug(f"Schema has {len(required_fields)} required fields: {', '.join(required_fields)}")

                # Check required fields
                logger.debug("Checking for required fields...")
                for field_name in required_fields:
                    if field_name not in data:
                        logger.error(f"Missing required field: '{field_name}'.")
                        raise ValueError(f"Missing required field: {field_name}.")
                logger.debug("All required fields are present.")

                # Validate field types
                logger.debug("Validating field types...")
                for field in schema.fields:
                    if field.name in data:
                        value = data[field.name]
                        field_type = field.type.__name__ if hasattr(field.type, "__name__") else str(field.type)

                        # Skip None values for non-required fields
                        if value is None and not field.required:
                            logger.debug(f"Field '{field.name}' has None value, which is allowed for optional fields.")
                            continue

                        # Log the field being validated
                        logger.debug(f"Validating field '{field.name}' with value '{value}' against type '{field_type}'...")

                        # Type validation
                        if not isinstance(value, field.type):
                            # Special case for int/float compatibility
                            if field.type == float and isinstance(value, int):
                                logger.debug(f"Converting integer value {value} to float for field '{field.name}'...")
                                data[field.name] = float(value)
                            else:
                                value_type = type(value).__name__
                                logger.error(f"Type mismatch for field '{field.name}': expected '{field_type}', got '{value_type}'.")
                                raise TypeError(f"Field '{field.name}' expected type {field_type}, got {value_type}.")
                        else:
                            logger.debug(f"Field '{field.name}' passed type validation.")

                logger.debug("All fields passed validation.")
                return data

            except Exception as e:
                if not isinstance(e, (ValueError, TypeError)):
                    # Only log unexpected errors, as ValueError and TypeError are already logged
                    logger.error(f"Unexpected error during validation: {e}")
                    logger.debug("Validation error details", exc_info=True)
                raise