http_types/
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, Infallible> for Option<T> {
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.ok_or_else(|| {
89            let status = status
90                .try_into()
91                .expect("Could not convert into a valid `StatusCode`");
92            Error::from_str(status, "NoneError")
93        })
94    }
95
96    /// Wrap the error value with an additional status code that is evaluated
97    /// lazily only once an error does occur.
98    ///
99    /// # Panics
100    ///
101    /// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
102    ///
103    /// [status]: crate::Status
104    /// [statuscode]: crate::StatusCode
105    fn with_status<S, F>(self, f: F) -> Result<T, Error>
106    where
107        S: TryInto<StatusCode>,
108        S::Error: Debug,
109        F: FnOnce() -> S,
110    {
111        self.ok_or_else(|| {
112            let status = f()
113                .try_into()
114                .expect("Could not convert into a valid `StatusCode`");
115            Error::from_str(status, "NoneError")
116        })
117    }
118}
119
120pub(crate) mod private {
121    pub trait Sealed {}
122
123    impl<T, E> Sealed for Result<T, E> {}
124    impl<T> Sealed for Option<T> {}
125}
126
127#[cfg(test)]
128mod test {
129    use super::Status;
130
131    #[test]
132    fn construct_shorthand_with_valid_status_code() {
133        let _res = Some(()).status(200).unwrap();
134    }
135
136    #[test]
137    #[should_panic(expected = "Could not convert into a valid `StatusCode`")]
138    fn construct_shorthand_with_invalid_status_code() {
139        let res: Result<(), std::io::Error> =
140            Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"));
141        let _res = res.status(600).unwrap();
142    }
143}