1use std::{error::Error, fmt};
2
3use crate::{
4 custom::{CustomProblem, StatusCode, Uri},
5 CowStr, Extensions, Problem,
6};
7
8#[track_caller]
10pub fn bad_request(msg: impl Into<CowStr>) -> Problem {
11 Problem::from(BadRequest { msg: msg.into() })
12}
13
14#[track_caller]
16pub fn unauthorized() -> Problem {
17 Problem::from(Unauthorized { _inner: () })
18}
19
20#[track_caller]
22pub fn forbidden() -> Problem {
23 Problem::from(Forbidden { _inner: () })
24}
25
26#[track_caller]
28pub fn not_found(entity: &'static str, identifier: impl fmt::Display) -> Problem {
29 Problem::from(NotFound {
30 entity,
31 identifier: identifier.to_string(),
32 })
33}
34
35#[track_caller]
38pub fn not_found_unknown(entity: &'static str) -> Problem {
39 not_found(entity, "<unknown>")
40}
41
42#[track_caller]
44pub fn conflict(msg: impl Into<CowStr>) -> Problem {
45 Problem::from(Conflict { msg: msg.into() })
46}
47
48#[track_caller]
50pub fn failed_precondition() -> Problem {
51 Problem::from(PreconditionFailed { _inner: () })
52}
53
54#[track_caller]
56pub fn unprocessable(msg: impl Into<CowStr>) -> Problem {
57 Problem::from(UnprocessableEntity { msg: msg.into() })
58}
59
60#[track_caller]
66pub fn internal_error<M>(msg: M) -> Problem
67where
68 M: Error + Send + Sync + 'static,
69{
70 Problem::from_status(StatusCode::INTERNAL_SERVER_ERROR)
71 .with_detail("An unexpected error occurred")
72 .with_cause(InternalError {
73 inner: Box::new(msg),
74 })
75}
76
77#[track_caller]
79pub fn service_unavailable() -> Problem {
80 Problem::from(ServiceUnavailable { _inner: () })
81}
82
83macro_rules! http_problem_type {
84 ($status: ident) => {
85 fn problem_type(&self) -> Uri {
86 crate::blank_type_uri()
87 }
88
89 fn title(&self) -> &'static str {
90 self.status_code().canonical_reason().unwrap()
91 }
92
93 fn status_code(&self) -> StatusCode {
94 StatusCode::$status
95 }
96 };
97
98 ($status: ident, display) => {
99 http_problem_type!($status);
100
101 fn details(&self) -> CowStr {
102 self.to_string().into()
103 }
104 };
105
106 ($status: ident, details: $detail: expr) => {
107 http_problem_type!($status);
108
109 fn details(&self) -> CowStr {
110 $detail.into()
111 }
112 };
113
114 ($status: ident, details <- $detail: ident) => {
115 http_problem_type!($status);
116
117 fn details(&self) -> CowStr {
118 self.$detail.to_string().into()
119 }
120 };
121}
122
123macro_rules! zst_problem_type {
124 ($(#[$doc:meta])+ $name:ident, $status_code:ident, $details:literal) => {
125 $(#[$doc])+
126 #[derive(Debug, Copy, Clone)]
127 pub struct $name {
128 _inner: (),
129 }
130
131 impl fmt::Display for $name {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.write_str(stringify!($name))
134 }
135 }
136
137 impl Error for $name {}
138
139 impl CustomProblem for $name {
140 http_problem_type!(
141 $status_code,
142 details: $details
143 );
144
145 fn add_extensions(&self, _: &mut Extensions) {}
146 }
147 }
148}
149
150#[derive(Debug)]
154pub struct BadRequest {
155 msg: CowStr,
156}
157
158impl fmt::Display for BadRequest {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 f.write_str(&self.msg)
161 }
162}
163
164impl Error for BadRequest {}
165
166impl CustomProblem for BadRequest {
167 http_problem_type!(BAD_REQUEST, details <- msg);
168
169 fn add_extensions(&self, _: &mut Extensions) {}
170}
171
172zst_problem_type!(
173 Unauthorized,
177 UNAUTHORIZED,
178 "You don't have the necessary permissions"
179);
180
181zst_problem_type!(
182 Forbidden,
186 FORBIDDEN,
187 "You don't have the necessary permissions"
188);
189
190zst_problem_type!(
191 PreconditionFailed,
196 PRECONDITION_FAILED,
197 "Some request precondition could not be satisfied."
198);
199
200#[derive(Debug)]
204pub struct NotFound {
205 identifier: String,
206 entity: &'static str,
207}
208
209impl NotFound {
210 pub const fn entity(&self) -> &'static str {
212 self.entity
213 }
214}
215
216impl fmt::Display for NotFound {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(
219 f,
220 "The {} identified by '{}' wasn't found",
221 self.entity, self.identifier
222 )
223 }
224}
225
226impl Error for NotFound {}
227
228impl CustomProblem for NotFound {
229 http_problem_type!(NOT_FOUND, display);
230
231 fn add_extensions(&self, extensions: &mut Extensions) {
232 extensions.insert("identifier", &self.identifier);
233 extensions.insert("entity", self.entity);
234 }
235}
236
237#[derive(Debug, Clone)]
241pub struct Conflict {
242 msg: CowStr,
243}
244
245impl fmt::Display for Conflict {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "Conflict: {}", self.msg)
248 }
249}
250
251impl Error for Conflict {}
252
253impl CustomProblem for Conflict {
254 http_problem_type!(CONFLICT, details <- msg);
255
256 fn add_extensions(&self, _: &mut Extensions) {}
257}
258
259#[derive(Debug)]
264pub struct UnprocessableEntity {
265 msg: CowStr,
266}
267
268impl fmt::Display for UnprocessableEntity {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 write!(f, "Unprocessable Entity: {}", self.msg)
271 }
272}
273
274impl Error for UnprocessableEntity {}
275
276impl CustomProblem for UnprocessableEntity {
277 http_problem_type!(UNPROCESSABLE_ENTITY, details <- msg);
278
279 fn add_extensions(&self, _: &mut Extensions) {}
280}
281
282#[derive(Debug)]
287pub struct InternalError {
288 inner: Box<dyn Error + Send + Sync + 'static>,
289}
290
291impl fmt::Display for InternalError {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 self.inner.fmt(f)
294 }
295}
296
297impl Error for InternalError {
298 fn source(&self) -> Option<&(dyn Error + 'static)> {
299 Some(&*self.inner)
300 }
301}
302
303zst_problem_type!(
304 ServiceUnavailable,
309 SERVICE_UNAVAILABLE,
310 "The service is currently unavailable, try again after an exponential backoff"
311);
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 macro_rules! test_constructor {
318 ($test_fn: ident, $constructor: ident, $ty: ty $(,$arg: expr)*) => {
319 #[test]
320 fn $test_fn() {
321 let prd = $constructor($($arg),*);
322
323 assert!(prd.is::<$ty>());
324 }
325 };
326 }
327
328 test_constructor!(test_bad_request, bad_request, BadRequest, "bla");
329 test_constructor!(test_unauthorized, unauthorized, Unauthorized);
330 test_constructor!(test_forbidden, forbidden, Forbidden);
331 test_constructor!(test_not_found, not_found, NotFound, "bla", "foo");
332 test_constructor!(test_conflict, conflict, Conflict, "bla");
333 test_constructor!(
334 test_failed_precondition,
335 failed_precondition,
336 PreconditionFailed
337 );
338 test_constructor!(
339 test_unprocessable,
340 unprocessable,
341 UnprocessableEntity,
342 "bla"
343 );
344 test_constructor!(
345 test_internal_error,
346 internal_error,
347 InternalError,
348 std::io::Error::new(std::io::ErrorKind::Other, "bla")
349 );
350 test_constructor!(
351 test_service_unavailable,
352 service_unavailable,
353 ServiceUnavailable
354 );
355}