errors.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. # -*- coding: utf-8 -*-
  2. """
  3. Fehlerklassen für das Validierungs-Framework.
  4. Bietet eine Hierarchie von Validierungsfehlern für
  5. detaillierte Fehlerberichterstattung.
  6. """
  7. from __future__ import annotations
  8. from dataclasses import dataclass, field
  9. from typing import Any
  10. @dataclass
  11. class FieldError:
  12. """
  13. Fehler für ein einzelnes Feld.
  14. Speichert Details über den Validierungsfehler eines
  15. bestimmten Feldes.
  16. """
  17. field: str
  18. """Name des fehlerhaften Feldes."""
  19. message: str
  20. """Fehlermeldung."""
  21. value: Any = None
  22. """Der fehlerhafte Wert."""
  23. code: str = "invalid"
  24. """Fehlercode für programmatische Behandlung."""
  25. nested_errors: list["FieldError"] = field(default_factory=list)
  26. """Verschachtelte Fehler bei komplexen Strukturen."""
  27. def to_dict(self) -> dict[str, Any]:
  28. """
  29. Konvertiert den Fehler in ein Dictionary.
  30. Returns:
  31. Dictionary-Repräsentation des Fehlers.
  32. """
  33. result: dict[str, Any] = {
  34. "field": self.field,
  35. "message": self.message,
  36. "code": self.code,
  37. }
  38. if self.value is not None:
  39. result["value"] = repr(self.value)
  40. if self.nested_errors:
  41. result["nested"] = [e.to_dict() for e in self.nested_errors]
  42. return result
  43. def __str__(self) -> str:
  44. """String-Repräsentation des Fehlers."""
  45. return f"{self.field}: {self.message}"
  46. class ValidationError(Exception):
  47. """
  48. Basis-Exception für Validierungsfehler.
  49. Kann einen einzelnen Validierungsfehler oder mehrere
  50. Fehler enthalten.
  51. """
  52. def __init__(
  53. self,
  54. message: str,
  55. field: str | None = None,
  56. value: Any = None,
  57. code: str = "invalid",
  58. errors: list[FieldError] | None = None,
  59. ) -> None:
  60. """
  61. Initialisiert den ValidationError.
  62. Args:
  63. message: Fehlermeldung.
  64. field: Optionaler Feldname.
  65. value: Optionaler fehlerhafter Wert.
  66. code: Fehlercode.
  67. errors: Optionale Liste von FieldErrors.
  68. """
  69. super().__init__(message)
  70. self.message = message
  71. self.field = field
  72. self.value = value
  73. self.code = code
  74. self.errors = errors or []
  75. # Einzelfehler auch in die Liste aufnehmen
  76. if field and not errors:
  77. self.errors = [
  78. FieldError(
  79. field=field,
  80. message=message,
  81. value=value,
  82. code=code,
  83. )
  84. ]
  85. def to_dict(self) -> dict[str, Any]:
  86. """
  87. Konvertiert den Fehler in ein Dictionary.
  88. Returns:
  89. Dictionary-Repräsentation.
  90. """
  91. result: dict[str, Any] = {
  92. "message": self.message,
  93. "code": self.code,
  94. }
  95. if self.field:
  96. result["field"] = self.field
  97. if self.errors:
  98. result["errors"] = [e.to_dict() for e in self.errors]
  99. return result
  100. def __str__(self) -> str:
  101. """String-Repräsentation des Fehlers."""
  102. if self.field:
  103. return f"Validierungsfehler in '{self.field}': {self.message}"
  104. return f"Validierungsfehler: {self.message}"
  105. class ValidationErrors(ValidationError):
  106. """
  107. Sammlung von mehreren Validierungsfehlern.
  108. Aggregiert mehrere Fehler zu einem einzelnen Exception-Objekt.
  109. """
  110. def __init__(
  111. self,
  112. errors: list[FieldError],
  113. message: str = "Validierung fehlgeschlagen",
  114. ) -> None:
  115. """
  116. Initialisiert die ValidationErrors.
  117. Args:
  118. errors: Liste von FieldErrors.
  119. message: Übergreifende Fehlermeldung.
  120. """
  121. super().__init__(
  122. message=message,
  123. errors=errors,
  124. code="validation_failed",
  125. )
  126. @classmethod
  127. def from_dict(cls, errors_dict: dict[str, str]) -> "ValidationErrors":
  128. """
  129. Erstellt ValidationErrors aus einem Dictionary.
  130. Args:
  131. errors_dict: Mapping von Feldnamen zu Fehlermeldungen.
  132. Returns:
  133. ValidationErrors-Instanz.
  134. """
  135. errors = [
  136. FieldError(field=field, message=message)
  137. for field, message in errors_dict.items()
  138. ]
  139. return cls(errors)
  140. def add_error(
  141. self,
  142. field: str,
  143. message: str,
  144. value: Any = None,
  145. code: str = "invalid",
  146. ) -> None:
  147. """
  148. Fügt einen Fehler hinzu.
  149. Args:
  150. field: Feldname.
  151. message: Fehlermeldung.
  152. value: Optionaler fehlerhafter Wert.
  153. code: Fehlercode.
  154. """
  155. self.errors.append(
  156. FieldError(
  157. field=field,
  158. message=message,
  159. value=value,
  160. code=code,
  161. )
  162. )
  163. def has_error(self, field: str) -> bool:
  164. """
  165. Prüft, ob ein Fehler für ein Feld existiert.
  166. Args:
  167. field: Feldname.
  168. Returns:
  169. True wenn Fehler existiert.
  170. """
  171. return any(e.field == field for e in self.errors)
  172. def get_error(self, field: str) -> FieldError | None:
  173. """
  174. Gibt den ersten Fehler für ein Feld zurück.
  175. Args:
  176. field: Feldname.
  177. Returns:
  178. FieldError oder None.
  179. """
  180. for error in self.errors:
  181. if error.field == field:
  182. return error
  183. return None
  184. def get_messages(self) -> dict[str, str]:
  185. """
  186. Gibt alle Fehlermeldungen als Dictionary zurück.
  187. Returns:
  188. Mapping von Feldnamen zu Fehlermeldungen.
  189. """
  190. return {e.field: e.message for e in self.errors}
  191. @property
  192. def is_empty(self) -> bool:
  193. """Prüft, ob keine Fehler vorhanden sind."""
  194. return len(self.errors) == 0
  195. def __len__(self) -> int:
  196. """Anzahl der Fehler."""
  197. return len(self.errors)
  198. def __iter__(self):
  199. """Iterator über die Fehler."""
  200. return iter(self.errors)
  201. def __bool__(self) -> bool:
  202. """True wenn Fehler vorhanden."""
  203. return len(self.errors) > 0
  204. def __str__(self) -> str:
  205. """String-Repräsentation."""
  206. if not self.errors:
  207. return "Keine Validierungsfehler"
  208. lines = [f"{self.message}:"]
  209. for error in self.errors:
  210. lines.append(f" - {error}")
  211. return "\n".join(lines)
  212. class SchemaError(ValidationError):
  213. """
  214. Fehler bei der Schema-Definition.
  215. Wird geworfen wenn ein Schema selbst ungültig ist,
  216. nicht wenn Daten das Schema verletzen.
  217. """
  218. def __init__(
  219. self,
  220. message: str,
  221. schema_name: str | None = None,
  222. field: str | None = None,
  223. ) -> None:
  224. """
  225. Initialisiert den SchemaError.
  226. Args:
  227. message: Fehlermeldung.
  228. schema_name: Name des fehlerhaften Schemas.
  229. field: Optionaler Feldname im Schema.
  230. """
  231. super().__init__(
  232. message=message,
  233. field=field,
  234. code="schema_error",
  235. )
  236. self.schema_name = schema_name
  237. def __str__(self) -> str:
  238. """String-Repräsentation."""
  239. if self.schema_name:
  240. if self.field:
  241. return f"Schema-Fehler in '{self.schema_name}.{self.field}': {self.message}"
  242. return f"Schema-Fehler in '{self.schema_name}': {self.message}"
  243. return f"Schema-Fehler: {self.message}"
  244. class TypeValidationError(ValidationError):
  245. """
  246. Fehler bei Typ-Validierung.
  247. Spezialisierter Fehler für Typ-Unstimmigkeiten.
  248. """
  249. def __init__(
  250. self,
  251. field: str,
  252. expected_type: type | str,
  253. actual_type: type | str,
  254. value: Any = None,
  255. ) -> None:
  256. """
  257. Initialisiert den TypeValidationError.
  258. Args:
  259. field: Feldname.
  260. expected_type: Erwarteter Typ.
  261. actual_type: Tatsächlicher Typ.
  262. value: Der fehlerhafte Wert.
  263. """
  264. expected_name = (
  265. expected_type.__name__
  266. if isinstance(expected_type, type)
  267. else str(expected_type)
  268. )
  269. actual_name = (
  270. actual_type.__name__
  271. if isinstance(actual_type, type)
  272. else str(actual_type)
  273. )
  274. super().__init__(
  275. message=f"Erwartet Typ '{expected_name}', erhalten '{actual_name}'",
  276. field=field,
  277. value=value,
  278. code="type_error",
  279. )
  280. self.expected_type = expected_type
  281. self.actual_type = actual_type
  282. class RangeValidationError(ValidationError):
  283. """
  284. Fehler bei Bereichs-Validierung.
  285. Spezialisierter Fehler für Werte außerhalb des Bereichs.
  286. """
  287. def __init__(
  288. self,
  289. field: str,
  290. value: Any,
  291. min_value: Any | None = None,
  292. max_value: Any | None = None,
  293. ) -> None:
  294. """
  295. Initialisiert den RangeValidationError.
  296. Args:
  297. field: Feldname.
  298. value: Der fehlerhafte Wert.
  299. min_value: Minimaler erlaubter Wert.
  300. max_value: Maximaler erlaubter Wert.
  301. """
  302. if min_value is not None and max_value is not None:
  303. msg = f"Wert muss zwischen {min_value} und {max_value} liegen"
  304. elif min_value is not None:
  305. msg = f"Wert muss mindestens {min_value} sein"
  306. elif max_value is not None:
  307. msg = f"Wert darf höchstens {max_value} sein"
  308. else:
  309. msg = "Wert außerhalb des Bereichs"
  310. super().__init__(
  311. message=msg,
  312. field=field,
  313. value=value,
  314. code="range_error",
  315. )
  316. self.min_value = min_value
  317. self.max_value = max_value
  318. class RequiredFieldError(ValidationError):
  319. """
  320. Fehler für fehlende Pflichtfelder.
  321. """
  322. def __init__(self, field: str) -> None:
  323. """
  324. Initialisiert den RequiredFieldError.
  325. Args:
  326. field: Name des fehlenden Feldes.
  327. """
  328. super().__init__(
  329. message="Dieses Feld ist erforderlich",
  330. field=field,
  331. code="required",
  332. )
  333. class PatternValidationError(ValidationError):
  334. """
  335. Fehler bei Pattern-Validierung.
  336. """
  337. def __init__(
  338. self,
  339. field: str,
  340. value: str,
  341. pattern: str,
  342. ) -> None:
  343. """
  344. Initialisiert den PatternValidationError.
  345. Args:
  346. field: Feldname.
  347. value: Der fehlerhafte Wert.
  348. pattern: Das nicht-gematchte Pattern.
  349. """
  350. super().__init__(
  351. message=f"Wert entspricht nicht dem Pattern '{pattern}'",
  352. field=field,
  353. value=value,
  354. code="pattern_error",
  355. )
  356. self.pattern = pattern