http_types_2/
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
44                .try_into()
45                .expect("Could not convert into a valid `StatusCode`");
46            Error::new(status, error)
47        })
48    }
49
50    /// Wrap the error value with an additional status code that is evaluated
51    /// lazily only once an error does occur.
52    ///
53    /// # Panics
54    ///
55    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
56    ///
57    /// [status]: crate::Status
58    /// [statuscode]: crate::StatusCode
59    fn with_status<S, F>(self, f: F) -> Result<T, Error>
60    where
61        S: TryInto<StatusCode>,
62        S::Error: Debug,
63        F: FnOnce() -> S,
64    {
65        self.map_err(|error| {
66            let status = f()
67                .try_into()
68                .expect("Could not convert into a valid `StatusCode`");
69            Error::new(status, error)
70        })
71    }
72}
73
74impl<T> Status<T, Error> for Result<T, Error> {
75    /// Wrap the error value with an additional status code.
76    ///
77    /// # Panics
78    ///
79    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
80    ///
81    /// [status]: crate::Status
82    /// [statuscode]: crate::StatusCode
83    fn status<S>(self, status: S) -> Result<T, Error>
84    where
85        S: TryInto<StatusCode>,
86        S::Error: Debug,
87    {
88        self.map_err(|mut error| {
89            error.set_status(status);
90            error
91        })
92    }
93
94    /// Wrap the error value with an additional status code that is evaluated
95    /// lazily only once an error does occur.
96    ///
97    /// # Panics
98    ///
99    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
100    ///
101    /// [status]: crate::Status
102    /// [statuscode]: crate::StatusCode
103    fn with_status<S, F>(self, f: F) -> Result<T, Error>
104    where
105        S: TryInto<StatusCode>,
106        S::Error: Debug,
107        F: FnOnce() -> S,
108    {
109        self.map_err(|mut error| {
110            error.set_status(f());
111            error
112        })
113    }
114}
115
116impl<T> Status<T, Infallible> for Option<T> {
117    /// Wrap the error value with an additional status code.
118    ///
119    /// # Panics
120    ///
121    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
122    ///
123    /// [status]: crate::Status
124    /// [statuscode]: crate::StatusCode
125    fn status<S>(self, status: S) -> Result<T, Error>
126    where
127        S: TryInto<StatusCode>,
128        S::Error: Debug,
129    {
130        self.ok_or_else(|| {
131            let status = status
132                .try_into()
133                .expect("Could not convert into a valid `StatusCode`");
134            Error::from_str(status, "NoneError")
135        })
136    }
137
138    /// Wrap the error value with an additional status code that is evaluated
139    /// lazily only once an error does occur.
140    ///
141    /// # Panics
142    ///
143    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
144    ///
145    /// [status]: crate::Status
146    /// [statuscode]: crate::StatusCode
147    fn with_status<S, F>(self, f: F) -> Result<T, Error>
148    where
149        S: TryInto<StatusCode>,
150        S::Error: Debug,
151        F: FnOnce() -> S,
152    {
153        self.ok_or_else(|| {
154            let status = f()
155                .try_into()
156                .expect("Could not convert into a valid `StatusCode`");
157            Error::from_str(status, "NoneError")
158        })
159    }
160}
161
162pub(crate) mod private {
163    pub trait Sealed {}
164
165    impl<T, E> Sealed for Result<T, E> {}
166    impl<T> Sealed for Option<T> {}
167}
168
169#[cfg(test)]
170mod test {
171    use super::Status;
172
173    #[test]
174    fn construct_shorthand_with_valid_status_code() {
175        Some(()).status(200).unwrap();
176    }
177
178    #[test]
179    #[should_panic(expected = "Could not convert into a valid `StatusCode`")]
180    fn construct_shorthand_with_invalid_status_code() {
181        let res: Result<(), std::io::Error> =
182            Err(std::io::Error::other("oh no!"));
183        res.status(600).unwrap();
184    }
185}