1use http::StatusCode;
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, error, fmt};
4
5#[derive(Debug)]
7pub struct Builder {
8 kind: Option<(String, String)>,
9 detail: Option<String>,
10 status: Option<StatusCode>,
11}
12
13#[derive(Clone, Debug, Deserialize, Serialize)]
15pub struct Error {
16 #[serde(rename = "type")]
17 kind: String,
18 title: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 detail: Option<String>,
21 #[cfg(not(feature = "serialize-status-code"))]
22 #[serde(skip)]
23 status: StatusCode,
24 #[cfg(feature = "serialize-status-code")]
25 #[serde(with = "http_serde::status_code")]
26 status: StatusCode,
27 #[serde(skip)]
28 extras: HashMap<String, String>,
29}
30
31impl Builder {
32 fn new() -> Self {
33 Self {
34 kind: None,
35 detail: None,
36 status: None,
37 }
38 }
39
40 pub fn status(self, status: StatusCode) -> Self {
42 Self {
43 status: Some(status),
44 ..self
45 }
46 }
47
48 pub fn kind(self, kind: &str, title: &str) -> Self {
50 Self {
51 kind: Some((kind.to_owned(), title.to_owned())),
52 ..self
53 }
54 }
55
56 pub fn detail(self, detail: &str) -> Self {
58 Self {
59 detail: Some(detail.to_owned()),
60 ..self
61 }
62 }
63
64 pub fn build(self) -> Error {
66 let mut err = match (self.kind, self.status) {
67 (Some((ref kind, ref title)), Some(status)) => Error::new(kind, title, status),
68 (None, Some(status)) => Error::from(status),
69 _ => Error::from(StatusCode::INTERNAL_SERVER_ERROR),
70 };
71
72 match self.detail {
73 Some(ref detail) => {
74 err.set_detail(detail);
75 err
76 }
77 None => err,
78 }
79 }
80}
81
82impl Error {
83 pub fn new(kind: &str, title: &str, status: StatusCode) -> Self {
85 Self {
86 kind: kind.to_owned(),
87 title: title.to_owned(),
88 detail: None,
89 extras: HashMap::new(),
90 status,
91 }
92 }
93
94 pub fn set_kind(&mut self, kind: &str, title: &str) -> &mut Self {
96 self.kind = kind.to_owned();
97 self.title = title.to_owned();
98 self
99 }
100
101 pub fn kind(&self) -> &str {
103 &self.kind
104 }
105
106 pub fn title(&self) -> &str {
108 &self.title
109 }
110
111 pub fn set_status_code(&mut self, value: StatusCode) -> &mut Self {
113 self.status = value;
114 self
115 }
116
117 pub fn status_code(&self) -> StatusCode {
119 self.status
120 }
121
122 pub fn detail(&self) -> Option<&str> {
124 self.detail.as_deref()
125 }
126
127 pub fn set_detail(&mut self, value: &str) -> &mut Self {
129 self.detail = Some(value.to_owned());
130 self
131 }
132
133 pub fn extras(&self) -> &HashMap<String, String> {
135 &self.extras
136 }
137
138 pub fn set_extra(&mut self, key: &str, value: &str) -> &mut Self {
140 self.extras.insert(key.to_owned(), value.to_owned());
141 self
142 }
143
144 pub fn builder() -> Builder {
146 Builder::new()
147 }
148}
149
150impl error::Error for Error {}
151
152impl fmt::Display for Error {
153 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
154 write!(fmt, "[{}] {}", self.kind, self.title)?;
155
156 if let Some(ref detail) = self.detail {
157 write!(fmt, ": {detail}")?;
158 }
159
160 Ok(())
161 }
162}
163
164impl From<StatusCode> for Error {
165 fn from(status: StatusCode) -> Self {
166 let title = status.canonical_reason().unwrap_or("Unknown status code");
167 Self {
168 kind: String::from("about:blank"),
169 title: title.to_owned(),
170 detail: None,
171 extras: HashMap::new(),
172 status,
173 }
174 }
175}