# -*- coding: utf-8 -*- """ Fehlerklassen für das Validierungs-Framework. Bietet eine Hierarchie von Validierungsfehlern für detaillierte Fehlerberichterstattung. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any @dataclass class FieldError: """ Fehler für ein einzelnes Feld. Speichert Details über den Validierungsfehler eines bestimmten Feldes. """ field: str """Name des fehlerhaften Feldes.""" message: str """Fehlermeldung.""" value: Any = None """Der fehlerhafte Wert.""" code: str = "invalid" """Fehlercode für programmatische Behandlung.""" nested_errors: list["FieldError"] = field(default_factory=list) """Verschachtelte Fehler bei komplexen Strukturen.""" def to_dict(self) -> dict[str, Any]: """ Konvertiert den Fehler in ein Dictionary. Returns: Dictionary-Repräsentation des Fehlers. """ result: dict[str, Any] = { "field": self.field, "message": self.message, "code": self.code, } if self.value is not None: result["value"] = repr(self.value) if self.nested_errors: result["nested"] = [e.to_dict() for e in self.nested_errors] return result def __str__(self) -> str: """String-Repräsentation des Fehlers.""" return f"{self.field}: {self.message}" class ValidationError(Exception): """ Basis-Exception für Validierungsfehler. Kann einen einzelnen Validierungsfehler oder mehrere Fehler enthalten. """ def __init__( self, message: str, field: str | None = None, value: Any = None, code: str = "invalid", errors: list[FieldError] | None = None, ) -> None: """ Initialisiert den ValidationError. Args: message: Fehlermeldung. field: Optionaler Feldname. value: Optionaler fehlerhafter Wert. code: Fehlercode. errors: Optionale Liste von FieldErrors. """ super().__init__(message) self.message = message self.field = field self.value = value self.code = code self.errors = errors or [] # Einzelfehler auch in die Liste aufnehmen if field and not errors: self.errors = [ FieldError( field=field, message=message, value=value, code=code, ) ] def to_dict(self) -> dict[str, Any]: """ Konvertiert den Fehler in ein Dictionary. Returns: Dictionary-Repräsentation. """ result: dict[str, Any] = { "message": self.message, "code": self.code, } if self.field: result["field"] = self.field if self.errors: result["errors"] = [e.to_dict() for e in self.errors] return result def __str__(self) -> str: """String-Repräsentation des Fehlers.""" if self.field: return f"Validierungsfehler in '{self.field}': {self.message}" return f"Validierungsfehler: {self.message}" class ValidationErrors(ValidationError): """ Sammlung von mehreren Validierungsfehlern. Aggregiert mehrere Fehler zu einem einzelnen Exception-Objekt. """ def __init__( self, errors: list[FieldError], message: str = "Validierung fehlgeschlagen", ) -> None: """ Initialisiert die ValidationErrors. Args: errors: Liste von FieldErrors. message: Übergreifende Fehlermeldung. """ super().__init__( message=message, errors=errors, code="validation_failed", ) @classmethod def from_dict(cls, errors_dict: dict[str, str]) -> "ValidationErrors": """ Erstellt ValidationErrors aus einem Dictionary. Args: errors_dict: Mapping von Feldnamen zu Fehlermeldungen. Returns: ValidationErrors-Instanz. """ errors = [ FieldError(field=field, message=message) for field, message in errors_dict.items() ] return cls(errors) def add_error( self, field: str, message: str, value: Any = None, code: str = "invalid", ) -> None: """ Fügt einen Fehler hinzu. Args: field: Feldname. message: Fehlermeldung. value: Optionaler fehlerhafter Wert. code: Fehlercode. """ self.errors.append( FieldError( field=field, message=message, value=value, code=code, ) ) def has_error(self, field: str) -> bool: """ Prüft, ob ein Fehler für ein Feld existiert. Args: field: Feldname. Returns: True wenn Fehler existiert. """ return any(e.field == field for e in self.errors) def get_error(self, field: str) -> FieldError | None: """ Gibt den ersten Fehler für ein Feld zurück. Args: field: Feldname. Returns: FieldError oder None. """ for error in self.errors: if error.field == field: return error return None def get_messages(self) -> dict[str, str]: """ Gibt alle Fehlermeldungen als Dictionary zurück. Returns: Mapping von Feldnamen zu Fehlermeldungen. """ return {e.field: e.message for e in self.errors} @property def is_empty(self) -> bool: """Prüft, ob keine Fehler vorhanden sind.""" return len(self.errors) == 0 def __len__(self) -> int: """Anzahl der Fehler.""" return len(self.errors) def __iter__(self): """Iterator über die Fehler.""" return iter(self.errors) def __bool__(self) -> bool: """True wenn Fehler vorhanden.""" return len(self.errors) > 0 def __str__(self) -> str: """String-Repräsentation.""" if not self.errors: return "Keine Validierungsfehler" lines = [f"{self.message}:"] for error in self.errors: lines.append(f" - {error}") return "\n".join(lines) class SchemaError(ValidationError): """ Fehler bei der Schema-Definition. Wird geworfen wenn ein Schema selbst ungültig ist, nicht wenn Daten das Schema verletzen. """ def __init__( self, message: str, schema_name: str | None = None, field: str | None = None, ) -> None: """ Initialisiert den SchemaError. Args: message: Fehlermeldung. schema_name: Name des fehlerhaften Schemas. field: Optionaler Feldname im Schema. """ super().__init__( message=message, field=field, code="schema_error", ) self.schema_name = schema_name def __str__(self) -> str: """String-Repräsentation.""" if self.schema_name: if self.field: return f"Schema-Fehler in '{self.schema_name}.{self.field}': {self.message}" return f"Schema-Fehler in '{self.schema_name}': {self.message}" return f"Schema-Fehler: {self.message}" class TypeValidationError(ValidationError): """ Fehler bei Typ-Validierung. Spezialisierter Fehler für Typ-Unstimmigkeiten. """ def __init__( self, field: str, expected_type: type | str, actual_type: type | str, value: Any = None, ) -> None: """ Initialisiert den TypeValidationError. Args: field: Feldname. expected_type: Erwarteter Typ. actual_type: Tatsächlicher Typ. value: Der fehlerhafte Wert. """ expected_name = ( expected_type.__name__ if isinstance(expected_type, type) else str(expected_type) ) actual_name = ( actual_type.__name__ if isinstance(actual_type, type) else str(actual_type) ) super().__init__( message=f"Erwartet Typ '{expected_name}', erhalten '{actual_name}'", field=field, value=value, code="type_error", ) self.expected_type = expected_type self.actual_type = actual_type class RangeValidationError(ValidationError): """ Fehler bei Bereichs-Validierung. Spezialisierter Fehler für Werte außerhalb des Bereichs. """ def __init__( self, field: str, value: Any, min_value: Any | None = None, max_value: Any | None = None, ) -> None: """ Initialisiert den RangeValidationError. Args: field: Feldname. value: Der fehlerhafte Wert. min_value: Minimaler erlaubter Wert. max_value: Maximaler erlaubter Wert. """ if min_value is not None and max_value is not None: msg = f"Wert muss zwischen {min_value} und {max_value} liegen" elif min_value is not None: msg = f"Wert muss mindestens {min_value} sein" elif max_value is not None: msg = f"Wert darf höchstens {max_value} sein" else: msg = "Wert außerhalb des Bereichs" super().__init__( message=msg, field=field, value=value, code="range_error", ) self.min_value = min_value self.max_value = max_value class RequiredFieldError(ValidationError): """ Fehler für fehlende Pflichtfelder. """ def __init__(self, field: str) -> None: """ Initialisiert den RequiredFieldError. Args: field: Name des fehlenden Feldes. """ super().__init__( message="Dieses Feld ist erforderlich", field=field, code="required", ) class PatternValidationError(ValidationError): """ Fehler bei Pattern-Validierung. """ def __init__( self, field: str, value: str, pattern: str, ) -> None: """ Initialisiert den PatternValidationError. Args: field: Feldname. value: Der fehlerhafte Wert. pattern: Das nicht-gematchte Pattern. """ super().__init__( message=f"Wert entspricht nicht dem Pattern '{pattern}'", field=field, value=value, code="pattern_error", ) self.pattern = pattern