| """Input validation utilities.""" | |
| import re | |
| from typing import List, Optional, Tuple | |
| class ValidationError(Exception): | |
| """Custom exception for validation errors.""" | |
| pass | |
| class InputValidator: | |
| """Validates user inputs.""" | |
| def validate_username(username: str) -> str: | |
| """ | |
| Validate username for submission. | |
| Args: | |
| username: The username to validate | |
| Returns: | |
| Cleaned username | |
| Raises: | |
| ValidationError: If username is invalid | |
| """ | |
| if not username or not username.strip(): | |
| raise ValidationError("Username cannot be empty") | |
| cleaned = username.strip() | |
| if len(cleaned) < 3: | |
| raise ValidationError("Username must be at least 3 characters") | |
| if len(cleaned) > 50: | |
| raise ValidationError("Username must be less than 50 characters") | |
| # Allow alphanumeric, underscore, hyphen | |
| if not re.match(r'^[a-zA-Z0-9_-]+$', cleaned): | |
| raise ValidationError("Username can only contain letters, numbers, underscore, and hyphen") | |
| return cleaned | |
| def validate_filter_indices(filter_list: Optional[Tuple], max_index: int) -> Optional[List[int]]: | |
| """ | |
| Validate filter indices for test questions. | |
| Args: | |
| filter_list: Tuple/list of indices or None | |
| max_index: Maximum valid index (exclusive) | |
| Returns: | |
| Validated list of indices or None | |
| Raises: | |
| ValidationError: If indices are invalid | |
| """ | |
| if filter_list is None: | |
| return None | |
| if not isinstance(filter_list, (list, tuple)): | |
| raise ValidationError("Filter must be a list or tuple") | |
| if not filter_list: | |
| raise ValidationError("Filter cannot be empty (use None for all questions)") | |
| validated = [] | |
| for idx in filter_list: | |
| if not isinstance(idx, int): | |
| raise ValidationError(f"Filter index must be integer, got {type(idx)}") | |
| if idx < 0: | |
| raise ValidationError(f"Filter index cannot be negative: {idx}") | |
| if idx >= max_index: | |
| raise ValidationError(f"Filter index {idx} out of range (max: {max_index - 1})") | |
| validated.append(idx) | |
| return validated | |
| def validate_questions_data(questions_data: any) -> List[dict]: | |
| """ | |
| Validate questions data structure. | |
| Args: | |
| questions_data: Data to validate | |
| Returns: | |
| Validated questions list | |
| Raises: | |
| ValidationError: If data is invalid | |
| """ | |
| if not isinstance(questions_data, list): | |
| raise ValidationError(f"Questions data must be a list, got {type(questions_data)}") | |
| if not questions_data: | |
| raise ValidationError("Questions list is empty") | |
| for idx, item in enumerate(questions_data): | |
| if not isinstance(item, dict): | |
| raise ValidationError(f"Question {idx} must be a dict, got {type(item)}") | |
| if "task_id" not in item: | |
| raise ValidationError(f"Question {idx} missing 'task_id'") | |
| if "question" not in item: | |
| raise ValidationError(f"Question {idx} missing 'question'") | |
| return questions_data | |