""" Validators for API data validation. Provides reusable validation functions for domain models data. """ import re import uuid from datetime import datetime, date from typing import Any, Dict, List, Optional, Union def is_valid_uuid(value: Any) -> bool: """ Check if a value is a valid UUID. Args: value: The value to check. Returns: bool: True if the value is a valid UUID, False otherwise. """ if isinstance(value, uuid.UUID): return True if not isinstance(value, str): return False try: uuid.UUID(value) return True except (ValueError, AttributeError, TypeError): return False def is_valid_email(email: str) -> bool: """ Validate an email address format. Args: email: The email address to validate. Returns: bool: True if the email format is valid, False otherwise. """ if not email or not isinstance(email, str): return False # Simple regex for email validation email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(email_pattern, email)) def is_valid_phone(phone: str) -> bool: """ Validate a phone number format. Args: phone: The phone number to validate. Returns: bool: True if the phone format is valid, False otherwise. """ if not phone or not isinstance(phone, str): return False # Remove common formatting characters cleaned = re.sub(r'[\s\-\(\)\.]+', '', phone) # Check if it's a valid format (allows international format) phone_pattern = r'^(\+\d{1,3})?(\d{10,15})$' return bool(re.match(phone_pattern, cleaned)) def is_valid_date(date_val: Any) -> bool: """ Validate a date string in ISO format (YYYY-MM-DD). Args: date_val: The date to validate. Returns: bool: True if the date is valid, False otherwise. """ return isinstance(date_val, date) def is_valid_datetime(datetime_str: str) -> bool: """ Validate a datetime string in ISO format. Args: datetime_str: The datetime string to validate. Returns: bool: True if the datetime format is valid, False otherwise. """ if not datetime_str or not isinstance(datetime_str, str): return False try: datetime.fromisoformat(datetime_str) return True except ValueError: return False def validate_required_fields(data: Dict[str, Any], required_fields: List[str]) -> List[str]: """ Validate that all required fields are present and not empty in the data. Args: data: Dictionary containing the data to validate. required_fields: List of field names that are required. Returns: List[str]: List of missing field names. Empty if all required fields are present. """ missing_fields = [] for field in required_fields: if field not in data or data[field] is None or (isinstance(data[field], str) and not data[field].strip()): missing_fields.append(field) return missing_fields def validate_model_exists(model_id: str, model_type: str, repository_method: callable, error_message: Optional[str] = None) -> Dict[str, Any]: """ Validate that a model with the given ID exists. Args: model_id: The ID of the model to check. model_type: The type of model (e.g., 'customer', 'account'). repository_method: Repository method to fetch the model. error_message: Custom error message. If None, a default message is used. Returns: Dict[str, Any]: Dictionary with 'valid' and 'error' keys. """ if not is_valid_uuid(model_id): return { 'valid': False, 'error': f"Invalid {model_type} ID format." } model = repository_method(model_id) if not model: return { 'valid': False, 'error': error_message or f"{model_type.capitalize()} with ID {model_id} not found." } return { 'valid': True, 'model': model, 'error': None } def validate_decimal_amount(amount: Union[float, str, int], field_name: str = 'amount') -> Dict[str, Any]: """ Validate a decimal amount (e.g., for money). Args: amount: The amount to validate. field_name: The name of the field being validated. Returns: Dict[str, Any]: Dictionary with 'valid' and 'error' keys. """ try: # Convert to float if it's a string if isinstance(amount, str): amount = float(amount) # Check if it's a number if not isinstance(amount, (int, float)): return { 'valid': False, 'error': f"{field_name} must be a number." } # Check if it's non-negative if amount < 0: return { 'valid': False, 'error': f"{field_name} cannot be negative." } return { 'valid': True, 'amount': float(amount), 'error': None } except ValueError: return { 'valid': False, 'error': f"Invalid {field_name} format." }