problem_details/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
//! 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`](ProblemDetails::type), [`status`](ProblemDetails::status),
//! [`title`](ProblemDetails::title), [`detail`](ProblemDetails::detail),
//! [`instance`](ProblemDetails::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`](ProblemDetails::with_extensions), the type is adjusted
//! automatically for you.
//!
//! Extension fields are flattened into the problem details object when serialized.
//!
//! ```rust
//! 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`](std::collections::HashMap)
//! as extensions object.
//!
//! ```rust
//! 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](https://www.rfc-editor.org/rfc/rfc9457.pdf#name-the-problem-details-json-ob).
//!
//! ```rust
//! 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_)
//! - **axum**: Enables axum `IntoResponse` types for the `ProblemDetails` struct (_implies `serde`_)
//! - **poem**: Enables poem `IntoResponse` impl for the `ProblemDetails` struct (_implies `serde`_)
//! - **xml**: Enables serde XML support for the `ProblemDetails` struct using
//! [`quick-xml`](https://crates.io/crates/quick-xml) when using the integration into
//! a web framework (_implies `serde`, only useful together with `axum` or `poem`_)
//!
//! # 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](https://www.rfc-editor.org/rfc/rfc9457.pdf#name-members-of-a-problem-detail)).
#![warn(missing_docs)]
#![forbid(unsafe_code)]
mod problem_details;
mod problem_type;
pub use problem_details::*;
pub use problem_type::*;
// Axum Support
#[cfg(feature = "axum")]
pub mod axum;
// Poem Support
#[cfg(feature = "poem")]
pub mod poem;
// Serde related extensions for http
#[cfg(feature = "serde")]
mod serde;