Skip to main content

foxtive/ext/
result.rs

1use crate::Error;
2use crate::enums::AppMessage;
3use crate::prelude::AppResult;
4use std::borrow::Cow;
5use std::future::Future;
6
7pub trait RecoverAppResultExt<T> {
8    fn recover_from<F>(self, func: F) -> AppResult<T>
9    where
10        F: FnOnce(AppMessage) -> AppResult<T>;
11
12    fn recover_from_async<F, Fut>(self, func: F) -> impl Future<Output = AppResult<T>> + Send
13    where
14        F: FnOnce(AppMessage) -> Fut + Send,
15        Fut: Future<Output = AppResult<T>> + Send;
16}
17
18pub trait AppErrorExt {
19    fn message(&self) -> Cow<'_, str>;
20}
21
22impl<T: Send> RecoverAppResultExt<T> for AppResult<T> {
23    fn recover_from<F>(self, func: F) -> AppResult<T>
24    where
25        F: FnOnce(AppMessage) -> AppResult<T>,
26    {
27        match self {
28            Ok(val) => Ok(val),
29            Err(err) => match err.downcast::<AppMessage>() {
30                Ok(message) => func(message),
31                Err(err) => Err(err),
32            },
33        }
34    }
35
36    async fn recover_from_async<F, Fut>(self, func: F) -> AppResult<T>
37    where
38        F: FnOnce(AppMessage) -> Fut + Send,
39        Fut: Future<Output = AppResult<T>> + Send,
40    {
41        match self {
42            Ok(val) => Ok(val),
43            Err(err) => match err.downcast::<AppMessage>() {
44                Ok(message) => func(message).await,
45                Err(err) => Err(err),
46            },
47        }
48    }
49}
50
51impl<T> RecoverAppResultExt<T> for Error {
52    fn recover_from<F>(self, func: F) -> AppResult<T>
53    where
54        F: FnOnce(AppMessage) -> AppResult<T>,
55    {
56        match self.downcast::<AppMessage>() {
57            Ok(message) => func(message),
58            Err(err) => Err(err),
59        }
60    }
61
62    async fn recover_from_async<F, Fut>(self, func: F) -> AppResult<T>
63    where
64        F: FnOnce(AppMessage) -> Fut + Send,
65        Fut: Future<Output = AppResult<T>> + Send,
66    {
67        match self.downcast::<AppMessage>() {
68            Ok(message) => func(message).await,
69            Err(err) => Err(err),
70        }
71    }
72}
73
74impl AppErrorExt for Error {
75    fn message(&self) -> Cow<'_, str> {
76        match self.downcast_ref::<AppMessage>() {
77            None => Cow::from(self.to_string()),
78            Some(msg) => msg.message(),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::{internal_server_error, invalid};
87    use http::StatusCode;
88
89    #[test]
90    fn test_recover_from_error() {
91        let result: AppResult<String> = internal_server_error!("Internal Server Error")
92            .recover_from(|err| {
93                assert_eq!(err.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
94                assert_eq!(err.message(), "Internal Server Error");
95                Ok("recovered".to_string())
96            });
97        assert_eq!(result.unwrap(), "recovered");
98    }
99
100    #[test]
101    fn test_recover_from_result() {
102        let result: AppResult<String> = Err(AppMessage::success("User created").into_anyhow())
103            .recover_from(|err| {
104                assert_eq!(err.status_code(), StatusCode::OK);
105                assert_eq!(err.message(), "User created");
106                Ok("recovered".to_string())
107            });
108        assert_eq!(result.unwrap(), "recovered");
109    }
110
111    #[tokio::test]
112    async fn test_recover_from_async_error() {
113        let result: AppResult<String> = internal_server_error!("Internal Server Error")
114            .recover_from_async(|err| {
115                assert_eq!(err.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
116                assert_eq!(err.message(), "Internal Server Error");
117                async { Ok("recovered".to_string()) }
118            })
119            .await;
120        assert_eq!(result.unwrap(), "recovered");
121    }
122
123    #[tokio::test]
124    async fn test_recover_from_async_result() {
125        let result: AppResult<String> = Err(AppMessage::success("User created").into_anyhow())
126            .recover_from_async(|err| {
127                assert_eq!(err.status_code(), StatusCode::OK);
128                assert_eq!(err.message(), "User created");
129                async { Ok("recovered".to_string()) }
130            })
131            .await;
132        assert_eq!(result.unwrap(), "recovered");
133    }
134
135    #[test]
136    fn test_msg() {
137        let err = internal_server_error!("Internal Server Error");
138        let result = err.message();
139        assert_eq!(result, "Internal Server Error");
140
141        let err = invalid!("User has already been suspended");
142        let result = err.message();
143        assert_eq!(result, "User has already been suspended");
144    }
145}