"""Syntax validation for config formats.""" import json import re from typing import Optional, Tuple import yaml ENV_LINE_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*\s*=(.*)$") class ValidationError: """Represents a validation error with location info.""" def __init__(self, message: str, line: Optional[int] = None, column: Optional[int] = None): self.message = message self.line = line self.column = column def __str__(self) -> str: if self.line is not None: if self.column is not None: return f"Line {self.line}, Column {self.column}: {self.message}" return f"Line {self.line}: {self.message}" return self.message def validate_json(data: str) -> Tuple[bool, Optional[ValidationError]]: """Validate JSON syntax. Args: data: The JSON string to validate Returns: Tuple of (is_valid, error) """ try: json.loads(data) return True, None except json.JSONDecodeError as e: return False, ValidationError(str(e), e.lineno, e.colno) def validate_yaml(data: str) -> Tuple[bool, Optional[ValidationError]]: """Validate YAML syntax. Args: data: The YAML string to validate Returns: Tuple of (is_valid, error) """ try: yaml.safe_load(data) return True, None except yaml.YAMLError as e: lineno = getattr(e, "lineno", None) colno = getattr(e, "column", None) problem = getattr(e, "problem", str(e)) if lineno is not None: return False, ValidationError(problem, lineno, colno) return False, ValidationError(problem) def validate_toml(data: str) -> Tuple[bool, Optional[ValidationError]]: """Validate TOML syntax. Args: data: The TOML string to validate Returns: Tuple of (is_valid, error) """ import sys if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib try: tomllib.loads(data) return True, None except Exception as e: error_str = str(e) line_match = re.search(r"line (\d+)", error_str) line = int(line_match.group(1)) if line_match else None return False, ValidationError(error_str, line) def validate_env(data: str) -> Tuple[bool, Optional[ValidationError]]: """Validate ENV format syntax. Args: data: The ENV string to validate Returns: Tuple of (is_valid, error) """ lines = data.splitlines() for line_num, line in enumerate(lines, 1): stripped = line.strip() if not stripped or stripped.startswith("#"): continue if not line.strip() == line: return False, ValidationError("Invalid KEY=value format", line_num) if not ENV_LINE_PATTERN.match(line): return False, ValidationError("Invalid KEY=value format", line_num) return True, None def validate(data: str, format: str) -> Tuple[bool, Optional[ValidationError]]: """Validate syntax for any supported format. Args: data: The string to validate format: The format to validate (json, yaml, toml, env) Returns: Tuple of (is_valid, error) """ format_lower = format.lower() if format_lower == "json": return validate_json(data) elif format_lower == "yaml": return validate_yaml(data) elif format_lower == "toml": return validate_toml(data) elif format_lower == "env": return validate_env(data) else: return False, ValidationError(f"Unsupported format: {format}")