http_types_rs/
status.rs

1use crate::{Error, StatusCode};
2use core::convert::{Infallible, TryInto};
3use std::error::Error as StdError;
4use std::fmt::Debug;
5
6/// Provides the `status` method for `Result` and `Option`.
7///
8/// This trait is sealed and cannot be implemented outside of `http-types`.
9pub trait Status<T, E>: private::Sealed {
10    /// Wrap the error value with an additional status code.
11    fn status<S>(self, status: S) -> Result<T, Error>
12    where
13        S: TryInto<StatusCode>,
14        S::Error: Debug;
15
16    /// Wrap the error value with an additional status code that is evaluated
17    /// lazily only once an error does occur.
18    fn with_status<S, F>(self, f: F) -> Result<T, Error>
19    where
20        S: TryInto<StatusCode>,
21        S::Error: Debug,
22        F: FnOnce() -> S;
23}
24
25impl<T, E> Status<T, E> for Result<T, E>
26where
27    E: StdError + Send + Sync + 'static,
28{
29    /// Wrap the error value with an additional status code.
30    ///
31    /// # Panics
32    ///
33    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
34    ///
35    /// [status]: crate::Status
36    /// [statuscode]: crate::StatusCode
37    fn status<S>(self, status: S) -> Result<T, Error>
38    where
39        S: TryInto<StatusCode>,
40        S::Error: Debug,
41    {
42        self.map_err(|error| {
43            let status = status.try_into().expect("Could not convert into a valid `StatusCode`");
44            Error::new(status, error)
45        })
46    }
47
48    /// Wrap the error value with an additional status code that is evaluated
49    /// lazily only once an error does occur.
50    ///
51    /// # Panics
52    ///
53    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
54    ///
55    /// [status]: crate::Status
56    /// [statuscode]: crate::StatusCode
57    fn with_status<S, F>(self, f: F) -> Result<T, Error>
58    where
59        S: TryInto<StatusCode>,
60        S::Error: Debug,
61        F: FnOnce() -> S,
62    {
63        self.map_err(|error| {
64            let status = f().try_into().expect("Could not convert into a valid `StatusCode`");
65            Error::new(status, error)
66        })
67    }
68}
69
70impl<T> Status<T, Error> for Result<T, Error> {
71    /// Wrap the error value with an additional status code.
72    ///
73    /// # Panics
74    ///
75    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
76    ///
77    /// [status]: crate::Status
78    /// [statuscode]: crate::StatusCode
79    fn status<S>(self, status: S) -> Result<T, Error>
80    where
81        S: TryInto<StatusCode>,
82        S::Error: Debug,
83    {
84        self.map_err(|mut error| {
85            error.set_status(status);
86            error
87        })
88    }
89
90    /// Wrap the error value with an additional status code that is evaluated
91    /// lazily only once an error does occur.
92    ///
93    /// # Panics
94    ///
95    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
96    ///
97    /// [status]: crate::Status
98    /// [statuscode]: crate::StatusCode
99    fn with_status<S, F>(self, f: F) -> Result<T, Error>
100    where
101        S: TryInto<StatusCode>,
102        S::Error: Debug,
103        F: FnOnce() -> S,
104    {
105        self.map_err(|mut error| {
106            error.set_status(f());
107            error
108        })
109    }
110}
111
112impl<T> Status<T, Infallible> for Option<T> {
113    /// Wrap the error value with an additional status code.
114    ///
115    /// # Panics
116    ///
117    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
118    ///
119    /// [status]: crate::Status
120    /// [statuscode]: crate::StatusCode
121    fn status<S>(self, status: S) -> Result<T, Error>
122    where
123        S: TryInto<StatusCode>,
124        S::Error: Debug,
125    {
126        self.ok_or_else(|| {
127            let status = status.try_into().expect("Could not convert into a valid `StatusCode`");
128            Error::from_str(status, "NoneError")
129        })
130    }
131
132    /// Wrap the error value with an additional status code that is evaluated
133    /// lazily only once an error does occur.
134    ///
135    /// # Panics
136    ///
137    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
138    ///
139    /// [status]: crate::Status
140    /// [statuscode]: crate::StatusCode
141    fn with_status<S, F>(self, f: F) -> Result<T, Error>
142    where
143        S: TryInto<StatusCode>,
144        S::Error: Debug,
145        F: FnOnce() -> S,
146    {
147        self.ok_or_else(|| {
148            let status = f().try_into().expect("Could not convert into a valid `StatusCode`");
149            Error::from_str(status, "NoneError")
150        })
151    }
152}
153
154pub(crate) mod private {
155    pub trait Sealed {}
156
157    impl<T, E> Sealed for Result<T, E> {}
158    impl<T> Sealed for Option<T> {}
159}
160
161#[cfg(test)]
162mod test {
163    use super::Status;
164
165    #[test]
166    fn construct_shorthand_with_valid_status_code() {
167        Some(()).status(200).unwrap();
168    }
169
170    #[test]
171    #[should_panic(expected = "Could not convert into a valid `StatusCode`")]
172    fn construct_shorthand_with_invalid_status_code() {
173        let res: Result<(), std::io::Error> = Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"));
174        res.status(600).unwrap();
175    }
176}