Files
testdata-cli/envschema_repo/envschema/validators.py

154 lines
4.2 KiB
Python

"""Type validators for environment variable values."""
import re
from typing import Optional
from envschema.schema import EnvVarType
class ValidationError:
"""Represents a validation error."""
def __init__(self, message: str, value: Optional[str] = None):
self.message = message
self.value = value
def __str__(self) -> str:
if self.value is not None:
return f"{self.message} (got: {self.value!r})"
return self.message
class StringValidator:
"""Validator for string type - always passes."""
@staticmethod
def validate(value: Optional[str]) -> tuple[bool, Optional[ValidationError]]:
if value is None:
return True, None
return True, None
class IntegerValidator:
"""Validator for integer type."""
@staticmethod
def validate(value: Optional[str]) -> tuple[bool, Optional[ValidationError]]:
if value is None:
return True, None
try:
int(value)
return True, None
except ValueError:
return False, ValidationError(
f"Invalid integer value",
value=value
)
class BooleanValidator:
"""Validator for boolean type."""
TRUE_VALUES = {"true", "1", "yes", "on"}
FALSE_VALUES = {"false", "0", "no", "off"}
@staticmethod
def validate(value: Optional[str]) -> tuple[bool, Optional[ValidationError]]:
if value is None:
return True, None
value_lower = value.lower().strip()
if value_lower in BooleanValidator.TRUE_VALUES:
return True, None
if value_lower in BooleanValidator.FALSE_VALUES:
return True, None
return False, ValidationError(
f"Invalid boolean value (expected: true, false, 1, 0, yes, no, on, off)",
value=value
)
class ListValidator:
"""Validator for list type (comma-separated values)."""
@staticmethod
def validate(value: Optional[str]) -> tuple[bool, Optional[ValidationError]]:
if value is None:
return True, None
if "," in value:
return True, None
return False, ValidationError(
f"Invalid list value (expected comma-separated values)",
value=value
)
@staticmethod
def parse(value: str) -> list[str]:
"""Parse a comma-separated string into a list.
Args:
value: Comma-separated string.
Returns:
List of values.
"""
return [item.strip() for item in value.split(",") if item.strip()]
class PatternValidator:
"""Validator for pattern/regex validation."""
@staticmethod
def validate(value: Optional[str], pattern: str) -> tuple[bool, Optional[ValidationError]]:
if value is None:
return True, None
try:
if re.match(pattern, value):
return True, None
return False, ValidationError(
f"Value does not match pattern: {pattern}",
value=value
)
except re.error:
return False, ValidationError(
f"Invalid regex pattern: {pattern}",
value=value
)
def get_validator(var_type: EnvVarType):
"""Get the validator class for a given type.
Args:
var_type: The environment variable type.
Returns:
Validator class.
"""
validators = {
EnvVarType.STRING: StringValidator,
EnvVarType.INTEGER: IntegerValidator,
EnvVarType.BOOLEAN: BooleanValidator,
EnvVarType.LIST: ListValidator,
}
return validators.get(var_type, StringValidator)
def validate_value(value: Optional[str], var_type: EnvVarType, pattern: Optional[str] = None) -> tuple[bool, Optional[ValidationError]]:
"""Validate a value against a type and optional pattern.
Args:
value: The value to validate.
var_type: The expected type.
pattern: Optional regex pattern.
Returns:
Tuple of (is_valid, error).
"""
validator = get_validator(var_type)
is_valid, error = validator.validate(value)
if is_valid and pattern and value is not None:
is_valid, error = PatternValidator.validate(value, pattern)
return is_valid, error