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.