http_problem/
http.rs

1use std::{error::Error, fmt};
2
3use crate::{
4    custom::{CustomProblem, StatusCode, Uri},
5    CowStr, Extensions, Problem,
6};
7
8/// Create a [`Problem`] instance with a [`BadRequest`] cause.
9#[track_caller]
10pub fn bad_request(msg: impl Into<CowStr>) -> Problem {
11    Problem::from(BadRequest { msg: msg.into() })
12}
13
14/// Create a [`Problem`] instance with an [`Unauthorized`] cause.
15#[track_caller]
16pub fn unauthorized() -> Problem {
17    Problem::from(Unauthorized { _inner: () })
18}
19
20/// Create a [`Problem`] instance with an [`Forbidden`] cause.
21#[track_caller]
22pub fn forbidden() -> Problem {
23    Problem::from(Forbidden { _inner: () })
24}
25
26/// Create a [`Problem`] instance with a [`NotFound`] cause.
27#[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/// Create a [`Problem`] instance with a [`NotFound`] cause with an unknown
36/// identifier.
37#[track_caller]
38pub fn not_found_unknown(entity: &'static str) -> Problem {
39    not_found(entity, "<unknown>")
40}
41
42/// Create a [`Problem`] instance with a [`Conflict`] cause.
43#[track_caller]
44pub fn conflict(msg: impl Into<CowStr>) -> Problem {
45    Problem::from(Conflict { msg: msg.into() })
46}
47
48/// Create a [`Problem`] instance with a [`PreconditionFailed`] cause.
49#[track_caller]
50pub fn failed_precondition() -> Problem {
51    Problem::from(PreconditionFailed { _inner: () })
52}
53
54/// Create a [`Problem`] instance with an [`UnprocessableEntity`] cause.
55#[track_caller]
56pub fn unprocessable(msg: impl Into<CowStr>) -> Problem {
57    Problem::from(UnprocessableEntity { msg: msg.into() })
58}
59
60/// Create a [`Problem`] instance with no available cause.
61///
62/// This error is meant to be used when an _unrecoverable_ error happens. Here,
63/// unrecoverable means errors that upper levels doesn't have any means to
64/// recover from other than retrying the operation or propagating it up.
65#[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/// Create a [`Problem`] instance with a [`ServiceUnavailable`] cause.
78#[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/// A `400 - Bad Request` error.
151///
152/// Used when the request is syntactically wrong.
153#[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    /// A `401 - Unauthorized` HTTP error.
174    ///
175    /// Used when a session doesn't have access to a given service.
176    Unauthorized,
177    UNAUTHORIZED,
178    "You don't have the necessary permissions"
179);
180
181zst_problem_type!(
182    /// A `403 - Forbidden` HTTP error.
183    ///
184    /// Used when a session doesn't have access to a given resource.
185    Forbidden,
186    FORBIDDEN,
187    "You don't have the necessary permissions"
188);
189
190zst_problem_type!(
191    /// A `419 - Precondition Failed` error.
192    ///
193    /// Used when some precondition in a condition request could not be
194    /// satisfied.
195    PreconditionFailed,
196    PRECONDITION_FAILED,
197    "Some request precondition could not be satisfied."
198);
199
200/// A `404 - Not Found` error.
201///
202/// Used when the application expected some entity to exist, but it didn't.
203#[derive(Debug)]
204pub struct NotFound {
205    identifier: String,
206    entity: &'static str,
207}
208
209impl NotFound {
210    /// The type of entity not found.
211    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/// A `409 - Conflict` error.
238///
239/// Used when a code invariant was broken due to a client provided information.
240#[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/// A `422 - Unprocessable Entity` error.
260///
261/// Used when the received client information doesn't follow the expected
262/// interface and requirements.
263#[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/// A `500 - Internal Server Error` error.
283///
284/// Used when there is an unexpected situation or when an unrecoverable error
285/// occurs.
286#[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    /// A `503 - Service Unavailable` error.
305    ///
306    /// Used when a necessary resource for the correctly execution of the
307    /// operation was unavailable (e.g. a database connection).
308    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}