mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Hotfix JSON serialization
This commit is contained in:
@@ -430,7 +430,7 @@ prune:
|
|||||||
# Clean Cache Files
|
# Clean Cache Files
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache|./data/)" | xargs rm -rf
|
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache|data/|site/)" | xargs rm -rf
|
||||||
@cd panel && rm -rf node_modules/ && rm -rf .next/ && rm -rf logs/ && rm -rf coverage/
|
@cd panel && rm -rf node_modules/ && rm -rf .next/ && rm -rf logs/ && rm -rf coverage/
|
||||||
@cd ..
|
@cd ..
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from typing import Any, cast
|
|||||||
import structlog
|
import structlog
|
||||||
from fastapi import FastAPI, HTTPException, Request, Response
|
from fastapi import FastAPI, HTTPException, Request, Response
|
||||||
from fastapi import status as http_status
|
from fastapi import status as http_status
|
||||||
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
@@ -431,7 +432,15 @@ async def request_validation_handler(request: Request, exc: Exception) -> JSONRe
|
|||||||
body=body_for_log,
|
body=body_for_log,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
content: dict[str, Any] = {"detail": errors, "body": body}
|
# jsonable_encoder is mandatory: Pydantic v2 stashes the raw exception
|
||||||
|
# object under error['ctx']['error'] for any validator that raises
|
||||||
|
# ValueError (e.g. blocker_type), and a raw Exception is not JSON
|
||||||
|
# serializable — without this json.dumps crashes the 422 render into a
|
||||||
|
# 500. FastAPI's own default validation handler encodes for the same reason.
|
||||||
|
content: dict[str, Any] = {
|
||||||
|
"detail": jsonable_encoder(errors),
|
||||||
|
"body": jsonable_encoder(body),
|
||||||
|
}
|
||||||
remediate = _uuid_field_remediation(errors)
|
remediate = _uuid_field_remediation(errors)
|
||||||
if remediate is not None:
|
if remediate is not None:
|
||||||
content["remediate"] = remediate
|
content["remediate"] = remediate
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from uuid import UUID # noqa: TC003
|
|||||||
|
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, field_validator
|
||||||
from roboco.api.middleware import (
|
from roboco.api.middleware import (
|
||||||
_uuid_field_remediation,
|
_uuid_field_remediation,
|
||||||
get_status_code,
|
get_status_code,
|
||||||
@@ -278,6 +278,43 @@ def test_request_validation_handler_returns_422_with_details() -> None:
|
|||||||
assert "body" in body
|
assert "body" in body
|
||||||
|
|
||||||
|
|
||||||
|
class _EnumFieldBody(BaseModel):
|
||||||
|
"""Mirrors IAmBlockedRequest: a field_validator that raises ValueError.
|
||||||
|
|
||||||
|
Pydantic v2 stashes the raw ValueError object in error['ctx']['error'],
|
||||||
|
which is not JSON-serializable — the handler must encode it (jsonable_encoder)
|
||||||
|
or json.dumps crashes the 422 render into a 500."""
|
||||||
|
|
||||||
|
kind: str
|
||||||
|
|
||||||
|
@field_validator("kind")
|
||||||
|
@classmethod
|
||||||
|
def _one_of(cls, v: str) -> str:
|
||||||
|
if v not in {"a", "b"}:
|
||||||
|
raise ValueError(f"kind must be one of: a | b. Got {v!r}.")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def test_validator_valueerror_returns_422_not_500() -> None:
|
||||||
|
"""A field_validator ValueError (raw exc in ctx) must render a clean 422,
|
||||||
|
not crash the handler into a 500. Reproduces the live i_am_blocked
|
||||||
|
blocker_type='task_complete' crash."""
|
||||||
|
app = FastAPI()
|
||||||
|
setup_middleware(app)
|
||||||
|
|
||||||
|
@app.post("/enum")
|
||||||
|
async def _e(_data: _EnumFieldBody) -> Any:
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
client = TestClient(app, raise_server_exceptions=False)
|
||||||
|
response = client.post("/enum", json={"kind": "task_complete"})
|
||||||
|
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
|
||||||
|
body = response.json()
|
||||||
|
assert "detail" in body
|
||||||
|
# The human-readable validator message must survive serialization.
|
||||||
|
assert "kind must be one of" in str(body["detail"])
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# secret scrubbing in the 422 log line
|
# secret scrubbing in the 422 log line
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user