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;