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__)

[docs] class SchemaValidator:
[docs] @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