svc_error/
error.rs

1use http::StatusCode;
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, error, fmt};
4
5/// Configure and build an error.
6#[derive(Debug)]
7pub struct Builder {
8    kind: Option<(String, String)>,
9    detail: Option<String>,
10    status: Option<StatusCode>,
11}
12
13/// Error object.
14#[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    /// Set status of the error.
41    pub fn status(self, status: StatusCode) -> Self {
42        Self {
43            status: Some(status),
44            ..self
45        }
46    }
47
48    /// Set kind and title of the error.
49    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    /// Set detailed information about the error.
57    pub fn detail(self, detail: &str) -> Self {
58        Self {
59            detail: Some(detail.to_owned()),
60            ..self
61        }
62    }
63
64    /// Create an error object.
65    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    /// Create an error object.
84    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    /// Set kind and title of the error.
95    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    /// Return a kind for this error.
102    pub fn kind(&self) -> &str {
103        &self.kind
104    }
105
106    /// Return a title for this error.
107    pub fn title(&self) -> &str {
108        &self.title
109    }
110
111    /// Set a status code information about the error.
112    pub fn set_status_code(&mut self, value: StatusCode) -> &mut Self {
113        self.status = value;
114        self
115    }
116
117    /// Return a status code for this error.
118    pub fn status_code(&self) -> StatusCode {
119        self.status
120    }
121
122    /// Return a detail for this error.
123    pub fn detail(&self) -> Option<&str> {
124        self.detail.as_deref()
125    }
126
127    /// Set detailed information about the error.
128    pub fn set_detail(&mut self, value: &str) -> &mut Self {
129        self.detail = Some(value.to_owned());
130        self
131    }
132
133    /// Return all extras for this error.
134    pub fn extras(&self) -> &HashMap<String, String> {
135        &self.extras
136    }
137
138    /// Set detailed information about the error.
139    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    /// Create an error builder object.
145    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}