Skip to main content

mq_bridge/
response.rs

1use crate::traits::{Handled, HandlerError};
2use crate::CanonicalMessage;
3
4/// Internal trait to convert success types into the [Handled] enum.
5pub trait IntoHandled {
6    fn into_handled(self) -> Handled;
7}
8
9impl IntoHandled for Handled {
10    fn into_handled(self) -> Handled {
11        self
12    }
13}
14
15impl IntoHandled for () {
16    fn into_handled(self) -> Handled {
17        Handled::Ack
18    }
19}
20
21impl IntoHandled for CanonicalMessage {
22    fn into_handled(self) -> Handled {
23        Handled::Publish(self)
24    }
25}
26
27impl IntoHandled for Option<CanonicalMessage> {
28    fn into_handled(self) -> Handled {
29        match self {
30            Some(msg) => Handled::Publish(msg),
31            None => Handled::Ack,
32        }
33    }
34}
35
36/// A trait for types that can be converted into a `Result<Handled, HandlerError>`.
37///
38/// This allows handlers to return ergonomic types instead of the explicit `Handled` enum.
39///
40/// ### Supported Success Types:
41/// - `()`: Equivalent to `Handled::Ack` (Processed successfully).
42/// - `CanonicalMessage`: Equivalent to `Handled::Publish(msg)` (Send a response).
43/// - `Option<CanonicalMessage>`: `Some` sends a response, `None` acknowledges (Ack).
44/// - `Handled`: The original enum (for full control).
45///
46/// ### Supported Error Types (via `ToHandlerError`):
47/// - `anyhow::Error`: Treated as `Retryable` by default.
48/// - `String` / `&str`: Treated as `NonRetryable`.
49/// - `HandlerError`: The internal error type for explicit control.
50///
51/// # Examples
52///
53/// ```rust
54/// use mq_bridge::{CanonicalMessage, Handled};
55///
56/// struct MyData { id: u32 }
57///
58/// async fn handle_reply(_: MyData) -> CanonicalMessage {
59///     CanonicalMessage::from("Response")
60/// }
61///
62/// // 2. Use anyhow::Result (Ok(()) is now inferred correctly in real logic)
63/// async fn handle_fail(_: MyData) -> anyhow::Result<()> {
64///     // Some logic that might use the '?' operator
65///     if true { anyhow::bail!("Something went wrong"); }
66///     
67///     Ok(()) // No Handled::Ack needed
68/// }
69/// ```
70pub trait IntoHandlerResult: Send + Sync + 'static {
71    fn into_handler_result(self) -> Result<Handled, HandlerError>;
72}
73
74/// A marker for types that are considered 'ergonomic' (not the standard Result<Handled, HandlerError>).
75/// This prevents type inference ambiguity in existing code.
76pub trait ErgonomicResponse: IntoHandlerResult {}
77
78impl IntoHandlerResult for Handled {
79    fn into_handler_result(self) -> Result<Handled, HandlerError> {
80        Ok(self)
81    }
82}
83impl ErgonomicResponse for Handled {}
84
85impl IntoHandlerResult for () {
86    fn into_handler_result(self) -> Result<Handled, HandlerError> {
87        Ok(Handled::Ack)
88    }
89}
90impl ErgonomicResponse for () {}
91
92impl IntoHandlerResult for CanonicalMessage {
93    fn into_handler_result(self) -> Result<Handled, HandlerError> {
94        Ok(Handled::Publish(self))
95    }
96}
97impl ErgonomicResponse for CanonicalMessage {}
98
99impl IntoHandlerResult for Option<CanonicalMessage> {
100    fn into_handler_result(self) -> Result<Handled, HandlerError> {
101        Ok(self.into_handled())
102    }
103}
104impl ErgonomicResponse for Option<CanonicalMessage> {}
105
106/// Explicit implementation for the standard result type.
107/// Note: This is NOT an ErgonomicResponse to maintain disjoint implementations in IntoTypedHandler.
108impl IntoHandlerResult for Result<Handled, HandlerError> {
109    fn into_handler_result(self) -> Result<Handled, HandlerError> {
110        self
111    }
112}
113
114impl<E> IntoHandlerResult for Result<(), E>
115where
116    E: ToHandlerError + Send + Sync + 'static,
117{
118    fn into_handler_result(self) -> Result<Handled, HandlerError> {
119        match self {
120            Ok(_) => Ok(Handled::Ack),
121            Err(err) => Err(err.to_handler_error()),
122        }
123    }
124}
125impl<E> ErgonomicResponse for Result<(), E> where E: ToHandlerError + Send + Sync + 'static {}
126
127impl<E> IntoHandlerResult for Result<CanonicalMessage, E>
128where
129    E: ToHandlerError + Send + Sync + 'static,
130{
131    fn into_handler_result(self) -> Result<Handled, HandlerError> {
132        match self {
133            Ok(msg) => Ok(Handled::Publish(msg)),
134            Err(err) => Err(err.to_handler_error()),
135        }
136    }
137}
138impl<E> ErgonomicResponse for Result<CanonicalMessage, E> where
139    E: ToHandlerError + Send + Sync + 'static
140{
141}
142
143impl<E> IntoHandlerResult for Result<Option<CanonicalMessage>, E>
144where
145    E: ToHandlerError + Send + Sync + 'static,
146{
147    fn into_handler_result(self) -> Result<Handled, HandlerError> {
148        match self {
149            Ok(val) => Ok(val.into_handled()),
150            Err(err) => Err(err.to_handler_error()),
151        }
152    }
153}
154impl<E> ErgonomicResponse for Result<Option<CanonicalMessage>, E> where
155    E: ToHandlerError + Send + Sync + 'static
156{
157}
158
159/// Internal trait to convert error types into [HandlerError].
160pub trait ToHandlerError {
161    fn to_handler_error(self) -> HandlerError;
162}
163
164impl ToHandlerError for HandlerError {
165    fn to_handler_error(self) -> HandlerError {
166        self
167    }
168}
169
170impl ToHandlerError for anyhow::Error {
171    fn to_handler_error(self) -> HandlerError {
172        crate::errors::ProcessingError::Retryable(self)
173    }
174}
175
176impl ToHandlerError for String {
177    fn to_handler_error(self) -> HandlerError {
178        crate::errors::ProcessingError::NonRetryable(anyhow::anyhow!(self))
179    }
180}
181
182impl ToHandlerError for &str {
183    fn to_handler_error(self) -> HandlerError {
184        crate::errors::ProcessingError::NonRetryable(anyhow::anyhow!(self.to_string()))
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::errors::ProcessingError;
192
193    #[test]
194    fn test_into_handled_converts_supported_success_types() {
195        assert!(matches!(().into_handled(), Handled::Ack));
196        assert!(matches!(
197            None::<CanonicalMessage>.into_handled(),
198            Handled::Ack
199        ));
200
201        let message = CanonicalMessage::from("payload");
202        match message.clone().into_handled() {
203            Handled::Publish(published) => assert_eq!(published.get_payload_str(), "payload"),
204            Handled::Ack => panic!("expected publish result"),
205        }
206
207        match Some(message).into_handled() {
208            Handled::Publish(published) => assert_eq!(published.get_payload_str(), "payload"),
209            Handled::Ack => panic!("expected publish result"),
210        }
211    }
212
213    #[test]
214    fn test_into_handler_result_converts_ok_variants() {
215        assert!(matches!(().into_handler_result().unwrap(), Handled::Ack));
216        assert!(matches!(
217            Option::<CanonicalMessage>::None
218                .into_handler_result()
219                .unwrap(),
220            Handled::Ack
221        ));
222
223        match CanonicalMessage::from("reply")
224            .into_handler_result()
225            .unwrap()
226        {
227            Handled::Publish(message) => assert_eq!(message.get_payload_str(), "reply"),
228            Handled::Ack => panic!("expected publish result"),
229        }
230
231        match Result::<Option<CanonicalMessage>, &str>::Ok(Some(CanonicalMessage::from("maybe")))
232            .into_handler_result()
233            .unwrap()
234        {
235            Handled::Publish(message) => assert_eq!(message.get_payload_str(), "maybe"),
236            Handled::Ack => panic!("expected publish result"),
237        }
238    }
239
240    #[test]
241    fn test_into_handler_result_and_to_handler_error_convert_errors() {
242        let retryable =
243            Result::<CanonicalMessage, anyhow::Error>::Err(anyhow::anyhow!("temporary"))
244                .into_handler_result()
245                .unwrap_err();
246        assert!(matches!(retryable, ProcessingError::Retryable(_)));
247
248        let non_retryable = Result::<(), &str>::Err("permanent")
249            .into_handler_result()
250            .unwrap_err();
251        assert!(matches!(non_retryable, ProcessingError::NonRetryable(_)));
252
253        let string_error = "string failure".to_string().to_handler_error();
254        assert!(matches!(string_error, ProcessingError::NonRetryable(_)));
255
256        let str_error = "str failure".to_handler_error();
257        assert!(matches!(str_error, ProcessingError::NonRetryable(_)));
258    }
259}