| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- # -*- coding: utf-8 -*-
- """
- Validator-Klassen für verschiedene Validierungsszenarien.
- Bietet eine erweiterbare Hierarchie von Validatoren für
- Typen, Bereiche, Patterns und komplexe Strukturen.
- """
- from __future__ import annotations
- import re
- from abc import ABC, abstractmethod
- from dataclasses import dataclass
- from enum import Enum
- from typing import Any, Callable, Generic, Sequence, Type, TypeVar
- from trixy_core.utils.validation.errors import (
- FieldError,
- PatternValidationError,
- RangeValidationError,
- RequiredFieldError,
- TypeValidationError,
- ValidationError,
- )
- T = TypeVar("T")
- class Validator(ABC, Generic[T]):
- """
- Abstrakte Basisklasse für alle Validatoren.
- Definiert die Schnittstelle für Validierung und
- ermöglicht Verkettung von Validatoren.
- """
- def __init__(
- self,
- message: str | None = None,
- code: str = "invalid",
- ) -> None:
- """
- Initialisiert den Validator.
- Args:
- message: Optionale benutzerdefinierte Fehlermeldung.
- code: Fehlercode für programmatische Behandlung.
- """
- self._message = message
- self._code = code
- @property
- def message(self) -> str:
- """Gibt die Fehlermeldung zurück."""
- return self._message or self.default_message
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung (kann überschrieben werden)."""
- return "Validierung fehlgeschlagen"
- @abstractmethod
- def validate(self, value: T, field: str = "value") -> None:
- """
- Validiert einen Wert.
- Args:
- value: Der zu validierende Wert.
- field: Feldname für Fehlermeldungen.
- Raises:
- ValidationError: Wenn Validierung fehlschlägt.
- """
- pass
- def is_valid(self, value: T) -> bool:
- """
- Prüft, ob ein Wert gültig ist.
- Args:
- value: Der zu prüfende Wert.
- Returns:
- True wenn gültig.
- """
- try:
- self.validate(value)
- return True
- except ValidationError:
- return False
- def __call__(self, value: T, field: str = "value") -> None:
- """Ermöglicht Aufruf als Funktion."""
- self.validate(value, field)
- def __and__(self, other: "Validator") -> "AllValidator":
- """Kombiniert mit AND-Logik."""
- return AllValidator([self, other])
- def __or__(self, other: "Validator") -> "AnyValidator":
- """Kombiniert mit OR-Logik."""
- return AnyValidator([self, other])
- class TypeValidator(Validator[Any]):
- """
- Validator für Typ-Prüfung.
- Prüft, ob ein Wert einer bestimmten Typ-Klasse entspricht.
- """
- def __init__(
- self,
- expected_type: Type | tuple[Type, ...],
- allow_none: bool = False,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den TypeValidator.
- Args:
- expected_type: Erwarteter Typ oder Tuple von Typen.
- allow_none: Ob None erlaubt ist.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="type_error")
- self.expected_type = expected_type
- self.allow_none = allow_none
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- if isinstance(self.expected_type, tuple):
- type_names = " oder ".join(t.__name__ for t in self.expected_type)
- else:
- type_names = self.expected_type.__name__
- return f"Muss vom Typ {type_names} sein"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert den Typ des Wertes."""
- if value is None:
- if self.allow_none:
- return
- raise ValidationError(
- message="Wert darf nicht None sein",
- field=field,
- value=value,
- code="null_error",
- )
- if not isinstance(value, self.expected_type):
- raise TypeValidationError(
- field=field,
- expected_type=self.expected_type, # type: ignore
- actual_type=type(value),
- value=value,
- )
- class RangeValidator(Validator[Any]):
- """
- Validator für numerische Bereiche.
- Prüft, ob ein Wert innerhalb eines definierten Bereichs liegt.
- """
- def __init__(
- self,
- min_value: Any | None = None,
- max_value: Any | None = None,
- inclusive_min: bool = True,
- inclusive_max: bool = True,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den RangeValidator.
- Args:
- min_value: Minimaler Wert (None = kein Minimum).
- max_value: Maximaler Wert (None = kein Maximum).
- inclusive_min: Ob Minimum inklusive ist.
- inclusive_max: Ob Maximum inklusive ist.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="range_error")
- self.min_value = min_value
- self.max_value = max_value
- self.inclusive_min = inclusive_min
- self.inclusive_max = inclusive_max
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- parts = []
- if self.min_value is not None:
- op = ">=" if self.inclusive_min else ">"
- parts.append(f"{op} {self.min_value}")
- if self.max_value is not None:
- op = "<=" if self.inclusive_max else "<"
- parts.append(f"{op} {self.max_value}")
- return f"Wert muss {' und '.join(parts)} sein"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert, ob der Wert im Bereich liegt."""
- if self.min_value is not None:
- if self.inclusive_min:
- if value < self.min_value:
- raise RangeValidationError(
- field=field,
- value=value,
- min_value=self.min_value,
- max_value=self.max_value,
- )
- else:
- if value <= self.min_value:
- raise RangeValidationError(
- field=field,
- value=value,
- min_value=self.min_value,
- max_value=self.max_value,
- )
- if self.max_value is not None:
- if self.inclusive_max:
- if value > self.max_value:
- raise RangeValidationError(
- field=field,
- value=value,
- min_value=self.min_value,
- max_value=self.max_value,
- )
- else:
- if value >= self.max_value:
- raise RangeValidationError(
- field=field,
- value=value,
- min_value=self.min_value,
- max_value=self.max_value,
- )
- class PatternValidator(Validator[str]):
- """
- Validator für Regex-Pattern.
- Prüft, ob ein String einem regulären Ausdruck entspricht.
- """
- def __init__(
- self,
- pattern: str,
- flags: int = 0,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den PatternValidator.
- Args:
- pattern: Regulärer Ausdruck.
- flags: Regex-Flags (z.B. re.IGNORECASE).
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="pattern_error")
- self.pattern = pattern
- self._regex = re.compile(pattern, flags)
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- return f"Muss dem Pattern '{self.pattern}' entsprechen"
- def validate(self, value: str, field: str = "value") -> None:
- """Validiert, ob der String dem Pattern entspricht."""
- if not self._regex.match(value):
- raise PatternValidationError(
- field=field,
- value=value,
- pattern=self.pattern,
- )
- class LengthValidator(Validator[Any]):
- """
- Validator für Längen-Prüfung.
- Prüft die Länge von Strings, Listen, etc.
- """
- def __init__(
- self,
- min_length: int | None = None,
- max_length: int | None = None,
- exact_length: int | None = None,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den LengthValidator.
- Args:
- min_length: Minimale Länge.
- max_length: Maximale Länge.
- exact_length: Exakte Länge (überschreibt min/max).
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="length_error")
- self.min_length = min_length
- self.max_length = max_length
- self.exact_length = exact_length
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- if self.exact_length is not None:
- return f"Länge muss genau {self.exact_length} sein"
- parts = []
- if self.min_length is not None:
- parts.append(f"mindestens {self.min_length}")
- if self.max_length is not None:
- parts.append(f"höchstens {self.max_length}")
- return f"Länge muss {' und '.join(parts)} sein"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert die Länge des Wertes."""
- try:
- length = len(value)
- except TypeError:
- raise ValidationError(
- message="Wert hat keine Länge",
- field=field,
- value=value,
- code="no_length",
- )
- if self.exact_length is not None:
- if length != self.exact_length:
- raise ValidationError(
- message=f"Länge muss genau {self.exact_length} sein, ist aber {length}",
- field=field,
- value=value,
- code="length_error",
- )
- else:
- if self.min_length is not None and length < self.min_length:
- raise ValidationError(
- message=f"Länge muss mindestens {self.min_length} sein, ist aber {length}",
- field=field,
- value=value,
- code="min_length_error",
- )
- if self.max_length is not None and length > self.max_length:
- raise ValidationError(
- message=f"Länge darf höchstens {self.max_length} sein, ist aber {length}",
- field=field,
- value=value,
- code="max_length_error",
- )
- class RequiredValidator(Validator[Any]):
- """
- Validator für Pflichtfelder.
- Prüft, ob ein Wert vorhanden und nicht leer ist.
- """
- def __init__(
- self,
- allow_empty: bool = False,
- allow_whitespace: bool = False,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den RequiredValidator.
- Args:
- allow_empty: Ob leere Werte ([], {}, "") erlaubt sind.
- allow_whitespace: Ob Whitespace-only Strings erlaubt sind.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="required")
- self.allow_empty = allow_empty
- self.allow_whitespace = allow_whitespace
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- return "Dieses Feld ist erforderlich"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert, ob der Wert vorhanden ist."""
- if value is None:
- raise RequiredFieldError(field)
- if not self.allow_empty:
- try:
- if len(value) == 0:
- raise ValidationError(
- message="Darf nicht leer sein",
- field=field,
- value=value,
- code="empty",
- )
- except TypeError:
- pass # Hat keine Länge, ist OK
- if not self.allow_whitespace and isinstance(value, str):
- if not value.strip():
- raise ValidationError(
- message="Darf nicht nur Leerzeichen enthalten",
- field=field,
- value=value,
- code="whitespace_only",
- )
- class OptionalValidator(Validator[Any]):
- """
- Validator für optionale Felder mit Fallback.
- Erlaubt None und wendet sonst den inneren Validator an.
- """
- def __init__(
- self,
- inner_validator: Validator,
- default: Any = None,
- ) -> None:
- """
- Initialisiert den OptionalValidator.
- Args:
- inner_validator: Validator für nicht-None Werte.
- default: Standardwert falls None.
- """
- super().__init__()
- self.inner_validator = inner_validator
- self.default = default
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert den Wert falls nicht None."""
- if value is None:
- return
- self.inner_validator.validate(value, field)
- class EnumValidator(Validator[Any]):
- """
- Validator für Enum-Werte.
- Prüft, ob ein Wert ein gültiger Enum-Wert ist.
- """
- def __init__(
- self,
- enum_class: Type[Enum] | None = None,
- allowed_values: Sequence[Any] | None = None,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den EnumValidator.
- Args:
- enum_class: Enum-Klasse für gültige Werte.
- allowed_values: Alternative: Liste erlaubter Werte.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="enum_error")
- self.enum_class = enum_class
- self.allowed_values = allowed_values
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- if self.enum_class:
- values = [e.value for e in self.enum_class]
- else:
- values = list(self.allowed_values or [])
- return f"Muss einer von {values} sein"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert, ob der Wert erlaubt ist."""
- if self.enum_class:
- valid_values = [e.value for e in self.enum_class]
- valid_names = [e.name for e in self.enum_class]
- if value not in valid_values and value not in valid_names:
- if not isinstance(value, self.enum_class):
- raise ValidationError(
- message=self.message,
- field=field,
- value=value,
- code="enum_error",
- )
- elif self.allowed_values:
- if value not in self.allowed_values:
- raise ValidationError(
- message=self.message,
- field=field,
- value=value,
- code="enum_error",
- )
- class ListValidator(Validator[list]):
- """
- Validator für Listen.
- Validiert Listen und deren Elemente.
- """
- def __init__(
- self,
- item_validator: Validator | None = None,
- min_items: int | None = None,
- max_items: int | None = None,
- unique: bool = False,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den ListValidator.
- Args:
- item_validator: Validator für jedes Element.
- min_items: Minimale Anzahl Elemente.
- max_items: Maximale Anzahl Elemente.
- unique: Ob alle Elemente eindeutig sein müssen.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="list_error")
- self.item_validator = item_validator
- self.min_items = min_items
- self.max_items = max_items
- self.unique = unique
- def validate(self, value: list, field: str = "value") -> None:
- """Validiert die Liste und deren Elemente."""
- if not isinstance(value, (list, tuple)):
- raise TypeValidationError(
- field=field,
- expected_type=list,
- actual_type=type(value),
- value=value,
- )
- # Längen-Prüfung
- if self.min_items is not None and len(value) < self.min_items:
- raise ValidationError(
- message=f"Liste muss mindestens {self.min_items} Elemente haben",
- field=field,
- value=value,
- code="min_items",
- )
- if self.max_items is not None and len(value) > self.max_items:
- raise ValidationError(
- message=f"Liste darf höchstens {self.max_items} Elemente haben",
- field=field,
- value=value,
- code="max_items",
- )
- # Eindeutigkeits-Prüfung
- if self.unique:
- try:
- if len(set(value)) != len(value):
- raise ValidationError(
- message="Liste enthält doppelte Elemente",
- field=field,
- value=value,
- code="not_unique",
- )
- except TypeError:
- # Elemente nicht hashbar
- pass
- # Element-Validierung
- if self.item_validator:
- for i, item in enumerate(value):
- try:
- self.item_validator.validate(item, f"{field}[{i}]")
- except ValidationError as e:
- raise ValidationError(
- message=f"Element {i}: {e.message}",
- field=f"{field}[{i}]",
- value=item,
- code="item_error",
- )
- class DictValidator(Validator[dict]):
- """
- Validator für Dictionaries.
- Validiert Dictionaries auf Schlüssel und Werte.
- """
- def __init__(
- self,
- key_validator: Validator | None = None,
- value_validator: Validator | None = None,
- required_keys: Sequence[str] | None = None,
- allowed_keys: Sequence[str] | None = None,
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den DictValidator.
- Args:
- key_validator: Validator für Schlüssel.
- value_validator: Validator für Werte.
- required_keys: Liste erforderlicher Schlüssel.
- allowed_keys: Liste erlaubter Schlüssel (andere werden abgelehnt).
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message, code="dict_error")
- self.key_validator = key_validator
- self.value_validator = value_validator
- self.required_keys = set(required_keys) if required_keys else set()
- self.allowed_keys = set(allowed_keys) if allowed_keys else None
- def validate(self, value: dict, field: str = "value") -> None:
- """Validiert das Dictionary."""
- if not isinstance(value, dict):
- raise TypeValidationError(
- field=field,
- expected_type=dict,
- actual_type=type(value),
- value=value,
- )
- # Erforderliche Schlüssel prüfen
- missing = self.required_keys - set(value.keys())
- if missing:
- raise ValidationError(
- message=f"Fehlende Schlüssel: {missing}",
- field=field,
- value=value,
- code="missing_keys",
- )
- # Erlaubte Schlüssel prüfen
- if self.allowed_keys is not None:
- extra = set(value.keys()) - self.allowed_keys
- if extra:
- raise ValidationError(
- message=f"Unerlaubte Schlüssel: {extra}",
- field=field,
- value=value,
- code="extra_keys",
- )
- # Schlüssel und Werte validieren
- for key, val in value.items():
- if self.key_validator:
- self.key_validator.validate(key, f"{field}[key]")
- if self.value_validator:
- self.value_validator.validate(val, f"{field}[{key!r}]")
- class CompositeValidator(Validator[Any]):
- """
- Basis für zusammengesetzte Validatoren.
- """
- def __init__(
- self,
- validators: Sequence[Validator],
- message: str | None = None,
- ) -> None:
- """
- Initialisiert den CompositeValidator.
- Args:
- validators: Liste von Validatoren.
- message: Optionale Fehlermeldung.
- """
- super().__init__(message=message)
- self.validators = list(validators)
- def add(self, validator: Validator) -> "CompositeValidator":
- """Fügt einen Validator hinzu."""
- self.validators.append(validator)
- return self
- class AllValidator(CompositeValidator):
- """
- Validator der alle Unter-Validatoren prüft (AND-Logik).
- """
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert mit allen Validatoren."""
- for validator in self.validators:
- validator.validate(value, field)
- class AnyValidator(CompositeValidator):
- """
- Validator der mindestens einen Unter-Validator prüft (OR-Logik).
- """
- @property
- def default_message(self) -> str:
- """Standard-Fehlermeldung."""
- return "Keiner der Validatoren war erfolgreich"
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert mit mindestens einem Validator."""
- errors = []
- for validator in self.validators:
- try:
- validator.validate(value, field)
- return # Mindestens einer erfolgreich
- except ValidationError as e:
- errors.append(e)
- # Alle fehlgeschlagen
- raise ValidationError(
- message=self.message,
- field=field,
- value=value,
- code="all_failed",
- )
- class ConditionalValidator(Validator[Any]):
- """
- Validator mit bedingter Logik.
- Wendet einen Validator nur an, wenn eine Bedingung erfüllt ist.
- """
- def __init__(
- self,
- condition: Callable[[Any], bool],
- then_validator: Validator,
- else_validator: Validator | None = None,
- ) -> None:
- """
- Initialisiert den ConditionalValidator.
- Args:
- condition: Funktion, die bestimmt welcher Validator verwendet wird.
- then_validator: Validator wenn Bedingung True.
- else_validator: Optionaler Validator wenn Bedingung False.
- """
- super().__init__()
- self.condition = condition
- self.then_validator = then_validator
- self.else_validator = else_validator
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert basierend auf der Bedingung."""
- if self.condition(value):
- self.then_validator.validate(value, field)
- elif self.else_validator:
- self.else_validator.validate(value, field)
- class CallableValidator(Validator[Any]):
- """
- Validator basierend auf einer benutzerdefinierten Funktion.
- """
- def __init__(
- self,
- check: Callable[[Any], bool],
- message: str = "Validierung fehlgeschlagen",
- code: str = "custom",
- ) -> None:
- """
- Initialisiert den CallableValidator.
- Args:
- check: Funktion, die True zurückgibt wenn gültig.
- message: Fehlermeldung bei Ungültigkeit.
- code: Fehlercode.
- """
- super().__init__(message=message, code=code)
- self.check = check
- def validate(self, value: Any, field: str = "value") -> None:
- """Validiert mit der benutzerdefinierten Funktion."""
- if not self.check(value):
- raise ValidationError(
- message=self.message,
- field=field,
- value=value,
- code=self._code,
- )
|