""" Input Validators using Pydantic Ensures data integrity and security """ from pydantic import BaseModel, validator, Field from datetime import datetime import re class PhoneNumber(BaseModel): """Validate phone numbers""" number: str = Field(..., description="Phone number to validate") @validator('number') def validate_phone(cls, v): if not v: raise ValueError('Phone number cannot be empty') # Remove all non-digits digits = re.sub(r'\D', '', v) # Check length (7-15 digits is standard international range) if len(digits) < 7 or len(digits) > 15: raise ValueError(f'Invalid phone number length: {len(digits)} digits') return digits @property def formatted(self): """Return formatted phone number""" return self.number class AppointmentTime(BaseModel): """Validate appointment times""" time: str = Field(..., description="ISO 8601 datetime string") @validator('time') def validate_time(cls, v): try: # Parse ISO 8601 datetime dt = datetime.fromisoformat(v.replace('Z', '+00:00')) # Check if in the future if dt < datetime.now(): raise ValueError('Appointment time must be in the future') return v except ValueError as e: raise ValueError(f'Invalid datetime format: {e}') class AppointmentPurpose(BaseModel): """Validate appointment purpose""" purpose: str = Field(..., min_length=3, max_length=200) @validator('purpose') def validate_purpose(cls, v): # Remove potentially dangerous characters cleaned = re.sub(r'[<>{}]', '', v) if len(cleaned.strip()) < 3: raise ValueError('Purpose must be at least 3 characters') return cleaned.strip() class AppointmentId(BaseModel): """Validate appointment ID""" id: str = Field(..., description="Appointment ID") @validator('id') def validate_id(cls, v): # Allow alphanumeric, hyphens, and underscores only if not re.match(r'^[a-zA-Z0-9_-]+$', v): raise ValueError('Invalid appointment ID format') if len(v) > 100: raise ValueError('Appointment ID too long') return v # Helper functions for easy validation def validate_phone_number(number: str) -> str: """Validate and return cleaned phone number""" validated = PhoneNumber(number=number) return validated.formatted def validate_appointment_time(time: str) -> str: """Validate appointment time""" validated = AppointmentTime(time=time) return validated.time def validate_purpose(purpose: str) -> str: """Validate appointment purpose""" validated = AppointmentPurpose(purpose=purpose) return validated.purpose def validate_appointment_id(id: str) -> str: """Validate appointment ID""" validated = AppointmentId(id=id) return validated.id