Crate problem_details

source ·
Expand description

RFC 9457 / RFC 7807 problem details for HTTP APIs.

This crate can be used to represent a problem details object as defined in RFC 9457 (which obsoletes RFC 7807).

The ProblemDetails struct includes the standard fields (type, status, title, detail, instance), as well as type-safe custom extensions.

Extensions

To add extensions, you need to define a struct that holds the extension fields, and use this struct as the generic parameter for ProblemDetails<Ext>. Using with_extensions, the type is adjusted automatically for you.

Extension fields are flattened into the problem details object when serialized.

use problem_details::ProblemDetails;

struct MyExt {
    foo: String,
    bar: u32,
}

let details = ProblemDetails::new()
    .with_extensions(MyExt {
        foo: "Hello".to_string(),
        bar: 42,
    });

// details is of type ProblemDetails<MyExt>
let typecheck: ProblemDetails<MyExt> = details;

If you need dynamic extensions, you can use a HashMap as extensions object.

use std::collections::HashMap;
use problem_details::ProblemDetails;

let mut extensions = HashMap::<String, serde_json::Value>::new();
extensions.insert("foo".to_string(), serde_json::json!("Hello"));
extensions.insert("bar".to_string(), serde_json::json!(42));

let details = ProblemDetails::new()
   .with_extensions(extensions);

// details is of type ProblemDetails<HashMap<String, serde_json::Value>>
let typecheck: ProblemDetails<HashMap<String, serde_json::Value>> = details;

Example

The following example shows how to create a problem details object that produces the example JSON from the RFC.

use http::Uri;
use problem_details::ProblemDetails;

#[derive(serde::Serialize)]
struct OutOfCreditExt {
   balance: u32,
   accounts: Vec<String>,
}

let details = ProblemDetails::new()
    .with_type(Uri::from_static("https://example.com/probs/out-of-credit"))
    .with_title("You do not have enough credit.")
    .with_detail("Your current balance is 30, but that costs 50.")
    .with_instance(Uri::from_static("/account/12345/msgs/abc"))
    .with_extensions(OutOfCreditExt {
        balance: 30,
        accounts: vec![
            "/account/12345".to_string(),
            "/account/67890".to_string(),
        ],
    });

let json = serde_json::to_value(&details).unwrap();

assert_eq!(json, serde_json::json!({
  "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",
  "balance": 30,
  "accounts": [
    "/account/12345",
    "/account/67890"
  ]
}));

Features

  • serde: Enables serde support for the ProblemDetails struct (enabled by default)
  • json: Enables serialization to JSON when using web framework integrations (_enabled by default, implies serde)
  • xml: Enables serialization to XML when using web framework integrations (implies serde)
  • axum: Enables integration with the axum web framework, allowing to return ProblemDetails as responses.
  • poem: Enables integration with the poem web framework, allowing to return ProblemDetails as responses and errors.

Caveats

This crate is not fully compliant with the RFC, because it fails to deserialize JSON values containing properties with incorrect types (required by Chapter 3.1 of the RFC).

Modules

Structs

  • ProblemDetails that is encoded to JSON when used with web framework integrations.
  • A RFC 9457 / RFC 7807 problem details object.
  • A type that represents a problem type URI.
  • ProblemDetails that is encoded to XML when used with web framework integrations.