"""Validation utilities for the application. [Task]: T008 [From]: specs/004-ai-chatbot/plan.md """ from pydantic import ValidationError, model_validator from pydantic_core import PydanticUndefined from typing import Any from sqlmodel import Field # Constants from spec MAX_MESSAGE_LENGTH = 10000 # FR-042: Maximum message content length class ValidationError(Exception): """Custom validation error.""" def __init__(self, message: str, field: str | None = None): self.message = message self.field = field super().__init__(self.message) def validate_message_length(content: str) -> str: """Validate message content length. [From]: specs/004-ai-chatbot/spec.md - FR-042 Args: content: Message content to validate Returns: str: The validated content Raises: ValidationError: If content exceeds maximum length """ if not content: raise ValidationError("Message content cannot be empty", "content") if len(content) > MAX_MESSAGE_LENGTH: raise ValidationError( f"Message content exceeds maximum length of {MAX_MESSAGE_LENGTH} characters " f"(got {len(content)} characters)", "content" ) return content def validate_conversation_id(conversation_id: Any) -> int | None: """Validate conversation ID. Args: conversation_id: Conversation ID to validate Returns: int | None: Validated conversation ID or None Raises: ValidationError: If conversation_id is invalid """ if conversation_id is None: return None if isinstance(conversation_id, int): if conversation_id <= 0: raise ValidationError("Conversation ID must be positive", "conversation_id") return conversation_id if isinstance(conversation_id, str): try: conv_id = int(conversation_id) if conv_id <= 0: raise ValidationError("Conversation ID must be positive", "conversation_id") return conv_id except ValueError: raise ValidationError("Conversation ID must be a valid integer", "conversation_id") raise ValidationError("Conversation ID must be an integer or null", "conversation_id") # Task validation constants MAX_TASK_TITLE_LENGTH = 255 # From Task model MAX_TASK_DESCRIPTION_LENGTH = 2000 # From Task model def validate_task_title(title: str) -> str: """Validate task title. [From]: models/task.py - Task.title Args: title: Task title to validate Returns: str: The validated title Raises: ValidationError: If title is empty or exceeds max length """ if not title or not title.strip(): raise ValidationError("Task title cannot be empty", "title") title = title.strip() if len(title) > MAX_TASK_TITLE_LENGTH: raise ValidationError( f"Task title exceeds maximum length of {MAX_TASK_TITLE_LENGTH} characters " f"(got {len(title)} characters)", "title" ) return title def validate_task_description(description: str | None) -> str: """Validate task description. [From]: models/task.py - Task.description Args: description: Task description to validate Returns: str: The validated description Raises: ValidationError: If description exceeds max length """ if description is None: return "" description = description.strip() if len(description) > MAX_TASK_DESCRIPTION_LENGTH: raise ValidationError( f"Task description exceeds maximum length of {MAX_TASK_DESCRIPTION_LENGTH} characters " f"(got {len(description)} characters)", "description" ) return description