| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- # -*- coding: utf-8 -*-
- """
- Validierungs-Middleware für Events.
- Validiert Event-Daten vor der Verarbeitung.
- """
- from __future__ import annotations
- from dataclasses import dataclass, field
- from typing import TYPE_CHECKING, Any, Callable, Type
- from trixy_core.events.middleware.base import (
- EventMiddleware,
- MiddlewareAction,
- MiddlewareContext,
- MiddlewareResult,
- )
- if TYPE_CHECKING:
- from trixy_core.events.event_data.base import EventData
- class EventValidationError(Exception):
- """
- Fehler bei der Event-Validierung.
- """
- def __init__(
- self,
- message: str,
- event_name: str,
- field: str | None = None,
- value: Any = None,
- ) -> None:
- """
- Initialisiert den Fehler.
- Args:
- message: Fehlermeldung.
- event_name: Name des Events.
- field: Betroffenes Feld.
- value: Ungültiger Wert.
- """
- super().__init__(message)
- self.event_name = event_name
- self.field = field
- self.value = value
- @dataclass
- class ValidationRule:
- """
- Regel für Event-Validierung.
- """
- field: str
- """Feldname (oder "*" für Event-Level)."""
- validator: Callable[[Any], bool]
- """Validierungsfunktion."""
- message: str = "Validierung fehlgeschlagen"
- """Fehlermeldung."""
- required: bool = False
- """Ob das Feld vorhanden sein muss."""
- allow_none: bool = True
- """Ob None erlaubt ist."""
- @dataclass
- class EventSchema:
- """
- Schema für Event-Validierung.
- """
- event_name: str
- """Name des Events (oder "*" für alle)."""
- rules: list[ValidationRule] = field(default_factory=list)
- """Validierungsregeln."""
- event_class: Type["EventData"] | None = None
- """Optionale Event-Klasse für Typ-Prüfung."""
- custom_validator: Callable[["EventData"], bool] | None = None
- """Optionale benutzerdefinierte Validierungsfunktion."""
- def add_rule(
- self,
- field: str,
- validator: Callable[[Any], bool],
- message: str = "Validierung fehlgeschlagen",
- required: bool = False,
- allow_none: bool = True,
- ) -> "EventSchema":
- """
- Fügt eine Regel hinzu (Fluent-API).
- Args:
- field: Feldname.
- validator: Validierungsfunktion.
- message: Fehlermeldung.
- required: Ob erforderlich.
- allow_none: Ob None erlaubt.
- Returns:
- Self für Verkettung.
- """
- self.rules.append(ValidationRule(
- field=field,
- validator=validator,
- message=message,
- required=required,
- allow_none=allow_none,
- ))
- return self
- class ValidationMiddleware(EventMiddleware):
- """
- Middleware für Event-Validierung.
- Validiert Event-Daten gegen definierte Schemas und Regeln.
- Example:
- middleware = ValidationMiddleware()
- # Schema definieren
- schema = EventSchema("user_login")
- schema.add_rule("username", lambda x: len(x) >= 3, "Username zu kurz")
- schema.add_rule("password", lambda x: len(x) >= 8, "Passwort zu kurz")
- middleware.register_schema(schema)
- # Oder mit Fluent-API
- middleware.for_event("user_register") \\
- .require("email") \\
- .rule("email", lambda x: "@" in x, "Ungültige E-Mail") \\
- .rule("age", lambda x: x >= 18, "Zu jung")
- """
- def __init__(
- self,
- name: str = "ValidationMiddleware",
- enabled: bool = True,
- strict: bool = True,
- on_validation_error: Callable[[EventValidationError], None] | None = None,
- ) -> None:
- """
- Initialisiert die Middleware.
- Args:
- name: Name der Middleware.
- enabled: Ob aktiv.
- strict: Ob bei Fehlern abgebrochen wird.
- on_validation_error: Callback bei Validierungsfehlern.
- """
- super().__init__(name, enabled)
- self._strict = strict
- self._on_validation_error = on_validation_error
- self._schemas: dict[str, EventSchema] = {}
- self._global_rules: list[ValidationRule] = []
- def register_schema(self, schema: EventSchema) -> None:
- """
- Registriert ein Event-Schema.
- Args:
- schema: Das Schema.
- """
- self._schemas[schema.event_name] = schema
- def for_event(self, event_name: str) -> "SchemaBuilder":
- """
- Startet einen Schema-Builder für ein Event.
- Args:
- event_name: Name des Events.
- Returns:
- SchemaBuilder für Fluent-API.
- """
- schema = EventSchema(event_name=event_name)
- self._schemas[event_name] = schema
- return SchemaBuilder(schema, self)
- def add_global_rule(
- self,
- field: str,
- validator: Callable[[Any], bool],
- message: str = "Validierung fehlgeschlagen",
- ) -> None:
- """
- Fügt eine globale Regel hinzu.
- Args:
- field: Feldname.
- validator: Validierungsfunktion.
- message: Fehlermeldung.
- """
- self._global_rules.append(ValidationRule(
- field=field,
- validator=validator,
- message=message,
- ))
- def _get_field_value(
- self,
- event_data: "EventData",
- field: str,
- ) -> tuple[bool, Any]:
- """
- Holt einen Feldwert aus den Event-Daten.
- Args:
- event_data: Die Event-Daten.
- field: Feldname.
- Returns:
- Tupel (gefunden, Wert).
- """
- # Direktes Attribut
- if hasattr(event_data, field):
- return True, getattr(event_data, field)
- # In Metadata
- if field in event_data.metadata:
- return True, event_data.metadata[field]
- return False, None
- def _validate_rule(
- self,
- event_data: "EventData",
- event_name: str,
- rule: ValidationRule,
- ) -> EventValidationError | None:
- """
- Validiert eine einzelne Regel.
- Args:
- event_data: Die Event-Daten.
- event_name: Name des Events.
- rule: Die Regel.
- Returns:
- Fehler oder None.
- """
- if rule.field == "*":
- # Event-Level-Validierung
- try:
- if not rule.validator(event_data):
- return EventValidationError(
- rule.message,
- event_name,
- )
- except Exception as e:
- return EventValidationError(
- f"Validierungsfehler: {e}",
- event_name,
- )
- return None
- found, value = self._get_field_value(event_data, rule.field)
- # Required-Prüfung
- if rule.required and not found:
- return EventValidationError(
- f"Pflichtfeld fehlt: {rule.field}",
- event_name,
- field=rule.field,
- )
- if not found:
- return None
- # None-Prüfung
- if value is None:
- if not rule.allow_none:
- return EventValidationError(
- f"Feld darf nicht None sein: {rule.field}",
- event_name,
- field=rule.field,
- value=value,
- )
- return None
- # Validator ausführen
- try:
- if not rule.validator(value):
- return EventValidationError(
- rule.message,
- event_name,
- field=rule.field,
- value=value,
- )
- except Exception as e:
- return EventValidationError(
- f"Validierungsfehler ({rule.field}): {e}",
- event_name,
- field=rule.field,
- value=value,
- )
- return None
- def _validate_event(
- self,
- event_name: str,
- event_data: "EventData",
- ) -> list[EventValidationError]:
- """
- Validiert ein Event.
- Args:
- event_name: Name des Events.
- event_data: Die Event-Daten.
- Returns:
- Liste von Fehlern.
- """
- errors: list[EventValidationError] = []
- # Globale Regeln
- for rule in self._global_rules:
- error = self._validate_rule(event_data, event_name, rule)
- if error:
- errors.append(error)
- # Event-spezifisches Schema
- schema = self._schemas.get(event_name) or self._schemas.get("*")
- if schema:
- # Event-Klassen-Prüfung
- if schema.event_class and not isinstance(event_data, schema.event_class):
- errors.append(EventValidationError(
- f"Falscher Event-Typ: erwartet {schema.event_class.__name__}",
- event_name,
- ))
- # Regeln prüfen
- for rule in schema.rules:
- error = self._validate_rule(event_data, event_name, rule)
- if error:
- errors.append(error)
- # Custom Validator
- if schema.custom_validator:
- try:
- if not schema.custom_validator(event_data):
- errors.append(EventValidationError(
- "Benutzerdefinierte Validierung fehlgeschlagen",
- event_name,
- ))
- except Exception as e:
- errors.append(EventValidationError(
- f"Validierungsfehler: {e}",
- event_name,
- ))
- return errors
- async def before(
- self,
- context: MiddlewareContext,
- ) -> MiddlewareResult:
- """
- Validiert Event-Daten vor der Verarbeitung.
- Args:
- context: Der Middleware-Kontext.
- Returns:
- Ergebnis mit CONTINUE oder ABORT.
- """
- errors = self._validate_event(context.event_name, context.event_data)
- if errors:
- # Speichere Fehler im Kontext
- context.set_property("validation_errors", errors)
- # Callbacks aufrufen
- if self._on_validation_error:
- for error in errors:
- self._on_validation_error(error)
- if self._strict:
- # In striktem Modus: Abbrechen
- context.errors.extend(errors)
- return MiddlewareResult(
- action=MiddlewareAction.ABORT,
- error=errors[0],
- )
- return MiddlewareResult()
- class SchemaBuilder:
- """
- Builder für Event-Schemas (Fluent-API).
- """
- def __init__(
- self,
- schema: EventSchema,
- middleware: ValidationMiddleware,
- ) -> None:
- """
- Initialisiert den Builder.
- Args:
- schema: Das Schema.
- middleware: Die Middleware.
- """
- self._schema = schema
- self._middleware = middleware
- def rule(
- self,
- field: str,
- validator: Callable[[Any], bool],
- message: str = "Validierung fehlgeschlagen",
- ) -> "SchemaBuilder":
- """
- Fügt eine Regel hinzu.
- Args:
- field: Feldname.
- validator: Validierungsfunktion.
- message: Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- self._schema.add_rule(field, validator, message)
- return self
- def require(
- self,
- field: str,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Markiert ein Feld als erforderlich.
- Args:
- field: Feldname.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- self._schema.add_rule(
- field=field,
- validator=lambda x: True, # Immer gültig wenn vorhanden
- message=message or f"Pflichtfeld fehlt: {field}",
- required=True,
- )
- return self
- def not_none(
- self,
- field: str,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Verbietet None für ein Feld.
- Args:
- field: Feldname.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- self._schema.add_rule(
- field=field,
- validator=lambda x: True,
- message=message or f"Feld darf nicht None sein: {field}",
- allow_none=False,
- )
- return self
- def type_check(
- self,
- field: str,
- expected_type: type,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Prüft den Typ eines Feldes.
- Args:
- field: Feldname.
- expected_type: Erwarteter Typ.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- self._schema.add_rule(
- field=field,
- validator=lambda x: isinstance(x, expected_type),
- message=message or f"Falscher Typ für {field}: erwartet {expected_type.__name__}",
- )
- return self
- def range(
- self,
- field: str,
- min_val: float | None = None,
- max_val: float | None = None,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Prüft einen Wertebereich.
- Args:
- field: Feldname.
- min_val: Minimalwert.
- max_val: Maximalwert.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- def validator(x: Any) -> bool:
- if min_val is not None and x < min_val:
- return False
- if max_val is not None and x > max_val:
- return False
- return True
- range_str = ""
- if min_val is not None and max_val is not None:
- range_str = f"[{min_val}, {max_val}]"
- elif min_val is not None:
- range_str = f">= {min_val}"
- elif max_val is not None:
- range_str = f"<= {max_val}"
- self._schema.add_rule(
- field=field,
- validator=validator,
- message=message or f"Wert für {field} außerhalb des Bereichs {range_str}",
- )
- return self
- def length(
- self,
- field: str,
- min_len: int | None = None,
- max_len: int | None = None,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Prüft die Länge eines Feldes.
- Args:
- field: Feldname.
- min_len: Minimale Länge.
- max_len: Maximale Länge.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- def validator(x: Any) -> bool:
- try:
- length = len(x)
- if min_len is not None and length < min_len:
- return False
- if max_len is not None and length > max_len:
- return False
- return True
- except TypeError:
- return False
- self._schema.add_rule(
- field=field,
- validator=validator,
- message=message or f"Ungültige Länge für {field}",
- )
- return self
- def pattern(
- self,
- field: str,
- regex: str,
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Prüft ein Regex-Pattern.
- Args:
- field: Feldname.
- regex: Regex-Pattern.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- import re
- pattern = re.compile(regex)
- self._schema.add_rule(
- field=field,
- validator=lambda x: bool(pattern.match(str(x))),
- message=message or f"Wert für {field} entspricht nicht dem Pattern",
- )
- return self
- def one_of(
- self,
- field: str,
- values: list[Any],
- message: str | None = None,
- ) -> "SchemaBuilder":
- """
- Prüft, ob der Wert in einer Liste ist.
- Args:
- field: Feldname.
- values: Erlaubte Werte.
- message: Optionale Fehlermeldung.
- Returns:
- Self für Verkettung.
- """
- self._schema.add_rule(
- field=field,
- validator=lambda x: x in values,
- message=message or f"Wert für {field} nicht erlaubt: {values}",
- )
- return self
- def custom(
- self,
- validator: Callable[["EventData"], bool],
- ) -> "SchemaBuilder":
- """
- Setzt einen benutzerdefinierten Validator.
- Args:
- validator: Validierungsfunktion.
- Returns:
- Self für Verkettung.
- """
- self._schema.custom_validator = validator
- return self
- def event_class(
- self,
- cls: Type["EventData"],
- ) -> "SchemaBuilder":
- """
- Setzt die erwartete Event-Klasse.
- Args:
- cls: Die Event-Klasse.
- Returns:
- Self für Verkettung.
- """
- self._schema.event_class = cls
- return self
- def build(self) -> EventSchema:
- """
- Gibt das fertige Schema zurück.
- Returns:
- Das Schema.
- """
- return self._schema
|