""" Field Validation Defaults - Smart defaults for activation requirement types Provides sensible validation rules, placeholders, and help text based on field type. Managers only specify the type, system fills in the rest automatically. """ from typing import Dict, Any, Optional # Smart defaults by field type FIELD_TYPE_DEFAULTS = { "phone": { "min_length": 10, "max_length": 15, "pattern": r"^\+?[0-9]{10,15}$", "placeholder": "+254XXXXXXXXX", "help_text": "Enter phone number with country code" }, "email": { "min_length": 5, "max_length": 100, "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", "placeholder": "user@example.com", "help_text": "Enter valid email address" }, "mac_address": { "min_length": 17, "max_length": 17, "pattern": r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", "placeholder": "AA:BB:CC:DD:EE:FF", "help_text": "Enter MAC address (format: AA:BB:CC:DD:EE:FF)" }, "serial_number": { "min_length": 6, "max_length": 30, "pattern": r"^[A-Z0-9\-]+$", "placeholder": "ABC123XYZ", "help_text": "Enter equipment serial number" }, "ip_address": { "min_length": 7, "max_length": 15, "pattern": r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", "placeholder": "192.168.1.1", "help_text": "Enter IPv4 address" }, "url": { "min_length": 10, "max_length": 500, "pattern": r"^https?://[^\s/$.?#].[^\s]*$", "placeholder": "https://example.com", "help_text": "Enter valid URL starting with http:// or https://" }, "text": { "min_length": 1, "max_length": 255, "pattern": None, "placeholder": None, # Will use field label "help_text": None }, "number": { "min_length": None, "max_length": None, "pattern": r"^-?[0-9]+(\.[0-9]+)?$", "placeholder": "0", "help_text": "Enter numeric value" }, "date": { "min_length": None, "max_length": None, "pattern": r"^\d{4}-\d{2}-\d{2}$", "placeholder": "YYYY-MM-DD", "help_text": "Select date" }, "boolean": { "min_length": None, "max_length": None, "pattern": None, "placeholder": None, "help_text": None }, "select": { "min_length": None, "max_length": None, "pattern": None, "placeholder": "Select an option", "help_text": None } } def apply_field_defaults(field_config: Dict[str, Any]) -> Dict[str, Any]: """ Apply smart defaults to field configuration based on type Args: field_config: Field configuration dict (from ActivationRequirement) Returns: Enhanced field config with defaults applied (non-destructive) """ field_type = field_config.get("type", "text") defaults = FIELD_TYPE_DEFAULTS.get(field_type, FIELD_TYPE_DEFAULTS["text"]) # Create enhanced config (don't modify original) enhanced = field_config.copy() # Apply defaults only if not already specified if enhanced.get("min_length") is None and defaults["min_length"] is not None: enhanced["min_length"] = defaults["min_length"] if enhanced.get("max_length") is None and defaults["max_length"] is not None: enhanced["max_length"] = defaults["max_length"] # Handle pattern (check both 'pattern' and deprecated 'validation_regex') if not enhanced.get("pattern") and not enhanced.get("validation_regex"): if defaults["pattern"]: enhanced["pattern"] = defaults["pattern"] elif enhanced.get("validation_regex") and not enhanced.get("pattern"): # Migrate deprecated field enhanced["pattern"] = enhanced["validation_regex"] if enhanced.get("placeholder") is None: if defaults["placeholder"]: enhanced["placeholder"] = defaults["placeholder"] elif field_type == "text": # For text fields, use label as placeholder enhanced["placeholder"] = f"Enter {enhanced.get('label', 'value').lower()}" if enhanced.get("help_text") is None and defaults["help_text"]: enhanced["help_text"] = defaults["help_text"] return enhanced def get_validation_rules(field_config: Dict[str, Any]) -> Dict[str, Any]: """ Get complete validation rules for frontend Args: field_config: Field configuration with defaults applied Returns: Validation rules dict ready for frontend consumption """ return { "min_length": field_config.get("min_length"), "max_length": field_config.get("max_length"), "pattern": field_config.get("pattern"), "placeholder": field_config.get("placeholder"), "help_text": field_config.get("help_text"), "required": field_config.get("required", True) } def validate_field_value(value: Any, field_config: Dict[str, Any]) -> tuple[bool, Optional[str]]: """ Validate a field value against its configuration Args: value: Value to validate field_config: Field configuration with validation rules Returns: Tuple of (is_valid, error_message) """ import re field_type = field_config.get("type", "text") required = field_config.get("required", True) # Check required if required and (value is None or value == ""): return False, f"{field_config.get('label', 'Field')} is required" # If not required and empty, it's valid if not required and (value is None or value == ""): return True, None # Convert value to string for validation str_value = str(value) # Check min_length min_length = field_config.get("min_length") if min_length is not None and len(str_value) < min_length: return False, f"{field_config.get('label', 'Field')} must be at least {min_length} characters" # Check max_length max_length = field_config.get("max_length") if max_length is not None and len(str_value) > max_length: return False, f"{field_config.get('label', 'Field')} must not exceed {max_length} characters" # Check pattern pattern = field_config.get("pattern") if pattern: try: if not re.match(pattern, str_value): help_text = field_config.get("help_text", "Invalid format") return False, f"{field_config.get('label', 'Field')}: {help_text}" except re.error: # Invalid regex pattern - log but don't fail validation return True, None # Type-specific validation if field_type == "number": try: float(str_value) except ValueError: return False, f"{field_config.get('label', 'Field')} must be a valid number" elif field_type == "boolean": if str_value.lower() not in ["true", "false", "1", "0", "yes", "no"]: return False, f"{field_config.get('label', 'Field')} must be true or false" elif field_type == "select": options = field_config.get("options", []) if options and str_value not in options: return False, f"{field_config.get('label', 'Field')} must be one of: {', '.join(options)}" return True, None