Shape Error Responses#
Restly’s request-time errors are ordinary FastAPI HTTPExceptions, so by
default every error renders as FastAPI’s standard {"detail": ...} body.
This page covers the typed exceptions your overrides should raise, and how to
change the error envelope app-wide.
One rule applies throughout: errors bypass
to_response. The
response boundary on a view shapes successful payloads (see
Response Envelopes and List Metadata); error
shaping happens at FastAPI’s exception-handler layer, app-wide, exactly as in
a plain FastAPI app.
The typed exceptions#
All request-time errors live in fr.exc and subclass fr.exc.RestlyHTTPError
(itself a fastapi.HTTPException):
Exception |
Status |
Raised when / raise it for |
|---|---|---|
|
A row does not exist, or is hidden by |
|
|
|
|
|
The request conflicts with current resource state. |
|
|
A list-endpoint parameter that is structurally valid but semantically wrong (e.g. |
Raise them from your own overrides (authorize, business methods, custom
routes), and they render through whatever handler is installed:
class ArticleView(fr.AsyncRestView):
...
async def authorize(self, action, obj=None, data=None):
if action == "delete" and not self.request.state.is_admin:
raise fr.exc.Forbidden("deletes need an admin token")
The remaining names in fr.exc are not HTTP errors:
fr.exc.RestlyError and
RestlyConfigurationError
are setup-time framework errors, and the warnings
RestlyUncommittedChangesWarning and
RestlyMisuseWarning also
live there.
422 vs 400 on list endpoints#
Two layers reject bad query strings on list endpoints, each with its own status code:
FastAPI’s request validation returns
422for requests that fail the schema-derived parameters: unknown keys (?nope=1), and type-invalid values for typed parameters (?page_size=oops).Restly’s query application raises
BadQueryParam(400) for parameters that passed validation but cannot be applied, such as an unresolvable sort field or an invalid filter path.
Change the error envelope app-wide#
To replace the default {"detail": ...} body, register a handler for
fr.exc.RestlyHTTPError; because
exception handlers match subclasses, one handler covers all four typed
errors. The handler below renders an RFC 9457 problem-details envelope:
from fastapi.responses import JSONResponse
@app.exception_handler(fr.exc.RestlyHTTPError)
async def problem_handler(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={
"type": "about:blank",
"title": exc.detail,
"status": exc.status_code,
},
media_type="application/problem+json",
)
With that installed, a hidden row renders as
{"type": "about:blank", "title": "Doc with id 999 was not found", "status": 404}
with the application/problem+json content type, including the 400
query-parameter errors. To also cover FastAPI’s
own 422 validation errors and plain HTTPExceptions raised elsewhere,
register handlers for RequestValidationError and HTTPException the
standard FastAPI way.
Database conflicts: IntegrityError to 409#
Not every conflict response comes from your own code: Restly installs a
default handler that translates SQLAlchemy IntegrityErrors into
409 Conflict responses. It respects a handler you registered yourself, and
it can be disabled with
fr.configure(app=app, install_default_exception_handlers=False);
the exact registration contract is in
Default Exception Handling.
See also#
Customize RestView: where
authorizeand the business methods sit; a raised exception skips the commit bracket.Filter, Sort, and Paginate Lists: the parameter grammar whose violations produce the 422/400 split.