Problem Details#
New in version 2.9.0.
Problem details are a standardized way of providing machine-readable details of errors in HTTP responses as specified in RFC 9457, the latest RFC at the time of writing.
Usage#
To send a problem details response, the ProblemDetailsPlugin
should be registered and then
a ProblemDetailsException
can be raised anywhere which will automatically be converted
into a problem details response.
from dataclasses import dataclass
from litestar import Litestar, post
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsException, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
# Logic to check if the user has enough credit to buy the item.
# We assume the user does not have enough credit.
raise ProblemDetailsException(
type_="https://example.com/probs/out-of-credit",
title="You do not have enough credit.",
detail="Your current balance is 30, but that costs 50.",
instance="/account/12345/msgs/abc",
extra={"balance": 30},
)
problem_details_plugin = ProblemDetailsPlugin(ProblemDetailsConfig())
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":500,"type":"https://example.com/probs/out-of-credit","title":"You do not have enough credit.","instance":"/account/12345/msgs/abc","detail":"Your current balance is 30, but that costs 50.","balance":30}
You can convert all HTTPExceptions
into problem details response by enabling the flag in the ProblemDetailsConfig.
from dataclasses import dataclass
from litestar import Litestar, post
from litestar.exceptions.http_exceptions import NotFoundException
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
# Logic to check if the user has enough credit to buy the item.
# We assume the user does not have enough credit.
raise NotFoundException(detail="No item with the given ID was found", extra={"item_id": data.item_id})
problem_details_plugin = ProblemDetailsPlugin(ProblemDetailsConfig(enable_for_all_http_exceptions=True))
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":404,"title":"No item with the given ID was found","detail":"Not Found","item_id":1234}
You can also convert any exception that is not a HTTPException
into a problem details response
by providing a mapping of the exception type to a callable that converts the exception into a
ProblemDetailsException.
Tip
This can used to override how the HTTPException
is converted into a problem details response as well.
from __future__ import annotations
from dataclasses import dataclass
from litestar import Litestar, post
from litestar.plugins.problem_details import ProblemDetailsConfig, ProblemDetailsException, ProblemDetailsPlugin
@dataclass
class PurchaseItem:
item_id: int
quantity: int
class PurchaseNotAllowedError(Exception):
def __init__(self, account_id: int, balance: int, detail: str) -> None:
self.account_id = account_id
self.balance = balance
self.detail = detail
@post("/purchase")
async def purchase(data: PurchaseItem) -> None:
raise PurchaseNotAllowedError(
account_id=12345,
balance=30,
detail="Your current balance is 30, but that costs 50.",
)
def convert_purchase_not_allowed_to_problem_details(exc: PurchaseNotAllowedError) -> ProblemDetailsException:
return ProblemDetailsException(
type_="https://example.com/probs/out-of-credit",
title="You do not have enough credit.",
detail=exc.detail,
instance=f"/account/{exc.account_id}/msgs/abc",
extra={"balance": exc.balance},
)
problem_details_plugin = ProblemDetailsPlugin(
ProblemDetailsConfig(
enable_for_all_http_exceptions=True,
exception_to_problem_detail_map={PurchaseNotAllowedError: convert_purchase_not_allowed_to_problem_details},
)
)
app = Litestar(route_handlers=[purchase], plugins=[problem_details_plugin])
Run it
> curl http://127.0.0.1:8000/purchase --header Content-Type: application/json --request POST --data {"item_id": 1234, "quantity": 2}
{"status":500,"type":"https://example.com/probs/out-of-credit","title":"You do not have enough credit.","instance":"/account/12345/msgs/abc","detail":"Your current balance is 30, but that costs 50.","balance":30}
Warning
If the extra
field is a Mapping
, then it’s merged into the problem details response, otherwise it’s included in the response with the key extra.