Coverage for src/lilbee/server/validation_format.py: 100%
13 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
1"""Shared rendering of Litestar body-validation failures for wire envelopes."""
3from __future__ import annotations
5import re
7from litestar.exceptions import ValidationException
9_BODY_KIND_PATTERN = re.compile(r"^Input should be a valid dictionary or instance of \w+$")
10BODY_KIND_MESSAGE = "The request body must be a JSON object."
13def plain_validation(exc: ValidationException) -> ValidationException:
14 """Replace the message that names an internal model class with a plain one.
16 A body that is not an object makes pydantic say "a valid dictionary or instance of
17 <ModelName>", which leaks the name of a class a client never sees.
18 """
19 if isinstance(exc.extra, list):
20 exc.extra = [
21 {**item, "message": BODY_KIND_MESSAGE}
22 if isinstance(item, dict) and _BODY_KIND_PATTERN.match(str(item.get("message", "")))
23 else item
24 for item in exc.extra
25 ]
26 return exc
29def format_validation(exc: ValidationException) -> str:
30 """Render a litestar/pydantic ValidationException as one user-facing string.
32 Litestar wraps pydantic errors as ``{"key": "field_name", "message": "..."}``
33 entries on ``exc.extra``; they flatten into a semicolon-joined string so the
34 error envelope carries the same field names a client expects to see.
35 """
36 items: list[dict[str, str]] = exc.extra if isinstance(exc.extra, list) else []
37 parts = [f"{err.get('key') or ''}: {err.get('message', '')}".lstrip(": ") for err in items]
38 return "; ".join(parts) if parts else str(exc.detail)