|
|
"""
|
|
|
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')
|
|
|
|
|
|
|
|
|
digits = re.sub(r'\D', '', v)
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
dt = datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
|
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):
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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
|
|
|
|