ft_sdk/
error.rs

1#[derive(Debug, thiserror::Error, PartialEq)]
2pub enum SpecialError {
3    #[error("single error {0}: {1}")]
4    Single(String, String),
5    #[error("multi error {0:?}")]
6    Multi(ft_sdk::FormError),
7    #[error("not found: {0}")]
8    NotFound(String),
9    #[error("server error: {0}")]
10    ServerError(String),
11    #[error("unauthorised: {0}")]
12    Unauthorised(String),
13}
14
15/// Create a page not found response.
16#[macro_export]
17macro_rules! server_error {
18    ($($t:tt)*) => {{
19        let msg = format!($($t)*);
20        $crate::server_error_(msg)
21    }};
22}
23
24#[doc(hidden)]
25pub fn server_error_(msg: String) -> SpecialError {
26    SpecialError::ServerError(msg)
27}
28
29/// Create a page not found response.
30#[macro_export]
31macro_rules! not_found {
32    ($($t:tt)*) => {{
33        let msg = format!($($t)*);
34        $crate::not_found_(msg)
35    }};
36}
37
38#[doc(hidden)]
39pub fn not_found_(msg: String) -> SpecialError {
40    SpecialError::NotFound(msg)
41}
42
43/// Create a page not found response.
44#[macro_export]
45macro_rules! unauthorised {
46    ($($t:tt)*) => {{
47        let msg = format!($($t)*);
48        $crate::unauthorised_(msg)
49    }};
50}
51
52#[doc(hidden)]
53pub fn unauthorised_(msg: String) -> SpecialError {
54    SpecialError::Unauthorised(msg)
55}
56
57pub fn single_error<K: AsRef<str>, E: AsRef<str>>(k: K, e: E) -> SpecialError {
58    SpecialError::Single(k.as_ref().to_string(), e.as_ref().to_string())
59}
60
61fn je(r: Result<http::Response<bytes::Bytes>, ft_sdk::Error>) -> http::Response<bytes::Bytes> {
62    r.unwrap_or_else(|e| {
63        http::Response::builder()
64            .status(http::StatusCode::INTERNAL_SERVER_ERROR)
65            .body(format!("json error: {e:?}\n").into())
66            .unwrap()
67    })
68}
69
70pub fn handle_error(e: anyhow::Error) -> http::Response<bytes::Bytes> {
71    if let Some(field_error) = e.downcast_ref::<SpecialError>() {
72        ft_sdk::println!("special error: {field_error}");
73        return match field_error {
74            SpecialError::Single(k, se) => je(crate::json(serde_json::json!({"errors": {k: se}}))),
75            SpecialError::Multi(me) => je(crate::json(serde_json::json!({"errors": me}))),
76            SpecialError::NotFound(msg) => http::Response::builder()
77                .status(http::StatusCode::NOT_FOUND)
78                .body(format!("page not found: {msg}\n").into())
79                .unwrap(),
80            SpecialError::Unauthorised(msg) => http::Response::builder()
81                .status(http::StatusCode::UNAUTHORIZED)
82                .body(format!("unauthorised: {msg}\n").into())
83                .unwrap(),
84            SpecialError::ServerError(msg) => http::Response::builder()
85                .status(http::StatusCode::INTERNAL_SERVER_ERROR)
86                .body(format!("server error: {msg}\n").into())
87                .unwrap(),
88        };
89    }
90    http::Response::builder()
91        .status(http::StatusCode::INTERNAL_SERVER_ERROR)
92        .body(format!("json error: {e:?}\n").into())
93        .unwrap()
94}
95
96#[cfg(test)]
97mod test {
98    use anyhow::Context;
99
100    #[derive(thiserror::Error, Debug)]
101    enum EFirst {
102        #[error("yo")]
103        Yo,
104    }
105
106    fn outer() -> Result<(), anyhow::Error> {
107        anyhow::Ok(out()?).context(http::StatusCode::CREATED)
108    }
109
110    fn out() -> Result<(), anyhow::Error> {
111        anyhow::Ok(first()?).context(http::StatusCode::ACCEPTED)
112    }
113
114    fn first() -> Result<(), anyhow::Error> {
115        Err(EFirst::Yo).context(http::StatusCode::SEE_OTHER)
116    }
117
118    #[test]
119    fn t() {
120        let e = outer().unwrap_err();
121        assert_eq!(
122            *e.downcast_ref::<http::StatusCode>().unwrap(),
123            http::StatusCode::SEE_OTHER
124        );
125        // in this example .chain() can not be used as .downcast_ref() on iter returned
126        // by chain requires std::error::Error, and http::StatusCode does not implement
127        // std::error::Error trait.
128    }
129
130    #[derive(thiserror::Error, Debug, PartialEq)]
131    enum Status {
132        #[error("created")]
133        Created,
134        #[error("accepted")]
135        Accepted,
136        #[error("see-other")]
137        SeeOther,
138    }
139
140    fn outer2() -> Result<(), anyhow::Error> {
141        anyhow::Ok(out2()?).context(Status::Created)
142    }
143
144    fn out2() -> Result<(), anyhow::Error> {
145        anyhow::Ok(first2()?).context(Status::Accepted)
146    }
147
148    fn first2() -> Result<(), anyhow::Error> {
149        Err(EFirst::Yo).context(Status::SeeOther)
150    }
151
152    #[test]
153    fn t2() {
154        let e = outer2().unwrap_err();
155        println!("status1: {:?}", e.downcast_ref::<Status>());
156        for cause in e.chain() {
157            println!("status: {:?}", cause.downcast_ref::<Status>());
158        }
159
160        // In this example, the code compiles, but it only finds the status attached
161        // by the first function that converts non anyhow::Error to anyhow::Error using
162        // the .context(), as shown in first2(). Status attached by out2 and outer2 are
163        // simply lost.
164    }
165
166    fn outer3() -> Result<(), anyhow::Error> {
167        Ok(do_something()?)
168    }
169
170    #[derive(thiserror::Error, Debug)]
171    enum DoSomethingError {
172        #[error("get user error {0}")]
173        // [from] important
174        GetUser(#[from] GetUserError),
175    }
176
177    fn do_something() -> Result<(), DoSomethingError> {
178        get_user()?;
179        Ok(())
180    }
181
182    #[derive(thiserror::Error, Debug)]
183    enum GetUserError {
184        #[error("unauthorised {0}")]
185        // note that [from] is important here, else error is not added to error chain
186        Unauthorised(#[from] super::SpecialError),
187    }
188
189    fn get_user() -> Result<i32, GetUserError> {
190        Err(GetUserError::Unauthorised(
191            super::SpecialError::Unauthorised("yo".to_string()),
192        ))
193    }
194
195    #[test]
196    fn t3() {
197        let e = outer3().unwrap_err();
198
199        assert_eq!(e.downcast_ref::<super::SpecialError>(), None);
200        let mut iter = e.chain();
201        assert_eq!(
202            iter.next().unwrap().downcast_ref::<super::SpecialError>(),
203            None
204        );
205        assert_eq!(
206            iter.next().unwrap().downcast_ref::<super::SpecialError>(),
207            None
208        );
209        assert_eq!(
210            iter.next().unwrap().downcast_ref::<super::SpecialError>(),
211            Some(super::SpecialError::Unauthorised("yo".to_string())).as_ref()
212        );
213        assert!(iter.next().is_none());
214
215        // This example works, but end-user has to make sure std::error::Error traits
216        // source() works correctly (here we have used `[from]` to ensure that)
217    }
218}