Coverage for src/csv_schema_validator/field_validators/exceptions.py: 88%
48 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-23 15:34 +0100
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-23 15:34 +0100
1"""
2Field validation exceptions for CSV field validation.
4This module defines specific exception classes for different types of field validation
5errors that can occur during CSV field validation, providing better error handling and
6more informative error messages.
7"""
8from __future__ import annotations
10from typing import Any
13class FieldValidationError(Exception):
14 """Base exception for field validation errors."""
16 def __init__(self, message: str, field_name: str, row: int,
17 value: Any, expected_type: str | None = None,
18 details: dict[str, Any] | None = None):
19 super().__init__(message)
20 self.message = message
21 self.field_name = field_name
22 self.row = row
23 self.value = value
24 self.expected_type = expected_type
25 self.details = details or {}
27 def to_dict(self) -> dict[str, Any]:
28 """Convert exception to dictionary format for JSON serialization."""
29 return {
30 "error_type": self.__class__.__name__,
31 "error_message": self.message,
32 "row": self.row,
33 "column": self.field_name,
34 "value": self.value,
35 "details": self.details
36 }
39class TypeValidationError(FieldValidationError):
40 """Raised when field type validation fails."""
42 def __init__(self, field_name: str, row: int, value: Any, expected_type: str):
43 message = f"Invalid type for field '{field_name}': expected {expected_type}, got {type(value).__name__}"
44 super().__init__(message, field_name, row, value, expected_type)
45 self.details = {
46 "expected_type": expected_type,
47 "actual_type": type(value).__name__
48 }
51class PatternValidationError(FieldValidationError):
52 """Raised when field pattern validation fails."""
54 def __init__(self, field_name: str, row: int, value: Any, pattern: str):
55 message = f"Field '{field_name}' does not match required pattern"
56 super().__init__(message, field_name, row, value, details={"expected_pattern": pattern})
59class EnumValidationError(FieldValidationError):
60 """Raised when field enum validation fails."""
62 def __init__(self, field_name: str, row: int, value: Any, allowed_values: list[str]):
63 message = f"Field '{field_name}' value '{value}' is not in allowed values"
64 super().__init__(message, field_name, row, value, details={"allowed_values": allowed_values})
67class RangeValidationError(FieldValidationError):
68 """Raised when field range validation fails."""
70 def __init__(self, field_name: str, row: int, value: Any, min_value: float | None = None,
71 max_value: float | None = None):
72 if min_value is not None and max_value is not None:
73 message = f"Field '{field_name}' value '{value}' is outside range [{min_value}, {max_value}]"
74 details = {"min_value": min_value, "max_value": max_value}
75 elif min_value is not None:
76 message = f"Field '{field_name}' value '{value}' is below minimum {min_value}"
77 details = {"min_value": min_value}
78 else:
79 message = f"Field '{field_name}' value '{value}' exceeds maximum {max_value}"
80 details = {"max_value": max_value}
82 super().__init__(message, field_name, row, value, details=details)
85class RequiredFieldError(FieldValidationError):
86 """Raised when a required field is missing."""
88 def __init__(self, field_name: str, row: int):
89 message = f"Required field '{field_name}' is missing"
90 super().__init__(message, field_name, row, None)
93class ValidationConfigurationError(Exception):
94 """Raised when there are configuration errors in validation setup."""
96 def __init__(self, message: str, details: dict[str, Any] | None = None):
97 super().__init__(message)
98 self.message = message
99 self.details = details or {}
101 def to_dict(self) -> dict[str, Any]:
102 """Convert exception to dictionary format for JSON serialization."""
103 return {
104 "error_type": self.__class__.__name__,
105 "error_message": self.message,
106 "row": None,
107 "column": None,
108 "value": None,
109 "details": self.details
110 }