feat(workbench): persist operational workflows and events
This commit is contained in:
@@ -22,6 +22,10 @@ class ProblemDetails(BaseModel):
|
||||
code: str
|
||||
trace_id: str
|
||||
errors: list[dict[str, Any]] = Field(default_factory=list)
|
||||
retryable: bool = False
|
||||
related_refs: list[Any] = Field(default_factory=list)
|
||||
recovery_action: str = "none"
|
||||
retry_after_seconds: int | None = None
|
||||
|
||||
|
||||
def _trace_id(request: Request) -> str:
|
||||
@@ -36,6 +40,10 @@ def _problem_response(
|
||||
detail: str,
|
||||
code: str,
|
||||
errors: list[dict[str, Any]] | None = None,
|
||||
retryable: bool = False,
|
||||
related_refs: list[Any] | None = None,
|
||||
recovery_action: str = "none",
|
||||
retry_after_seconds: int | None = None,
|
||||
) -> JSONResponse:
|
||||
problem = ProblemDetails(
|
||||
type=f"https://tjwater.example/problems/{code.replace('_', '-')}",
|
||||
@@ -46,6 +54,10 @@ def _problem_response(
|
||||
code=code,
|
||||
trace_id=_trace_id(request),
|
||||
errors=errors or [],
|
||||
retryable=retryable,
|
||||
related_refs=related_refs or [],
|
||||
recovery_action=recovery_action,
|
||||
retry_after_seconds=retry_after_seconds,
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=status_code,
|
||||
@@ -89,7 +101,6 @@ def install_problem_details_handlers(app: FastAPI) -> None:
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_error_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
||||
detail = exc.detail if isinstance(exc.detail, str) else str(exc.detail)
|
||||
code_by_status = {
|
||||
401: "unauthenticated",
|
||||
403: "forbidden",
|
||||
@@ -98,12 +109,43 @@ def install_problem_details_handlers(app: FastAPI) -> None:
|
||||
422: "validation_error",
|
||||
503: "dependency_unavailable",
|
||||
}
|
||||
fallback_code = code_by_status.get(exc.status_code, "request_error")
|
||||
detail_payload = exc.detail if isinstance(exc.detail, dict) else None
|
||||
code = (
|
||||
detail_payload.get("code")
|
||||
if detail_payload and isinstance(detail_payload.get("code"), str)
|
||||
else fallback_code
|
||||
)
|
||||
detail = exc.detail if isinstance(exc.detail, str) else None
|
||||
if detail_payload:
|
||||
candidate = detail_payload.get("message") or detail_payload.get("detail")
|
||||
if isinstance(candidate, str):
|
||||
detail = candidate
|
||||
if detail is None:
|
||||
detail = code.replace("_", " ").title()
|
||||
related_refs = detail_payload.get("related_refs", []) if detail_payload else []
|
||||
if not isinstance(related_refs, list):
|
||||
related_refs = []
|
||||
recovery_action = (
|
||||
detail_payload.get("recovery_action", "none") if detail_payload else "none"
|
||||
)
|
||||
if not isinstance(recovery_action, str):
|
||||
recovery_action = "none"
|
||||
retry_after_seconds = (
|
||||
detail_payload.get("retry_after_seconds") if detail_payload else None
|
||||
)
|
||||
if not isinstance(retry_after_seconds, int):
|
||||
retry_after_seconds = None
|
||||
return _problem_response(
|
||||
request,
|
||||
status_code=exc.status_code,
|
||||
title=code_by_status.get(exc.status_code, "request_error")
|
||||
.replace("_", " ")
|
||||
.title(),
|
||||
title=fallback_code.replace("_", " ").title(),
|
||||
detail=detail,
|
||||
code=code_by_status.get(exc.status_code, "request_error"),
|
||||
code=code,
|
||||
retryable=(
|
||||
bool(detail_payload.get("retryable", False)) if detail_payload else False
|
||||
),
|
||||
related_refs=related_refs,
|
||||
recovery_action=recovery_action,
|
||||
retry_after_seconds=retry_after_seconds,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user