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.

Basic usage of the problem details plugin.
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.

Converting HTTPException into problem details response.
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.

Converting custom exceptions into problem details response.
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.