1use crate::fault::SoapFault;
3use async_trait::async_trait;
4use bytes::Bytes;
5use std::future::Future;
6
7#[async_trait]
11pub trait SoapHandler: Send + Sync + 'static {
12 async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault>;
13
14 async fn handle_with_headers(
18 &self,
19 body: Bytes,
20 _headers: &[Bytes],
21 ) -> Result<Bytes, SoapFault> {
22 self.handle(body).await
23 }
24}
25
26pub struct FnHandler<F> {
28 f: F,
29}
30
31impl<F, Fut> FnHandler<F>
32where
33 F: Fn(Bytes) -> Fut + Send + Sync + 'static,
34 Fut: Future<Output = Result<Bytes, SoapFault>> + Send + 'static,
35{
36 pub fn new(f: F) -> Self {
37 Self { f }
38 }
39}
40
41#[async_trait]
42impl<F, Fut> SoapHandler for FnHandler<F>
43where
44 F: Fn(Bytes) -> Fut + Send + Sync + 'static,
45 Fut: Future<Output = Result<Bytes, SoapFault>> + Send + 'static,
46{
47 async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault> {
48 (self.f)(body).await
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::fault::{FaultCode, SoapFault};
56 use std::sync::{Arc, Mutex};
57
58 #[tokio::test]
59 async fn fn_handler_ok_passthrough() {
60 let handler = FnHandler::new(|body: Bytes| async move { Ok::<Bytes, SoapFault>(body) });
61 let input = Bytes::from_static(b"<hello/>");
62 let result = handler.handle(input.clone()).await.unwrap();
63 assert_eq!(result, input);
64 }
65
66 #[tokio::test]
67 async fn fn_handler_returns_fault() {
68 let handler = FnHandler::new(|_body: Bytes| async move {
69 Err::<Bytes, SoapFault>(SoapFault::sender("not allowed"))
70 });
71 let result = handler.handle(Bytes::from_static(b"<test/>")).await;
72 assert!(result.is_err());
73 let fault = result.unwrap_err();
74 assert_eq!(fault.code, FaultCode::Sender);
75 assert!(fault.reason.contains("not allowed"));
76 }
77
78 #[tokio::test]
79 async fn fn_handler_closure_captures_context() {
80 let expected_response = Bytes::from_static(b"<response>ok</response>");
81 let resp_clone = expected_response.clone();
82 let handler = FnHandler::new(move |_body: Bytes| {
83 let resp = resp_clone.clone();
84 async move { Ok::<Bytes, SoapFault>(resp) }
85 });
86 let result = handler
87 .handle(Bytes::from_static(b"<request/>"))
88 .await
89 .unwrap();
90 assert_eq!(result, expected_response);
91 }
92
93 #[tokio::test]
94 async fn soap_handler_trait_object() {
95 let handler: Box<dyn SoapHandler> = Box::new(FnHandler::new(|_body: Bytes| async move {
97 Ok::<Bytes, SoapFault>(Bytes::from_static(b"<resp/>"))
98 }));
99 let result = handler.handle(Bytes::from_static(b"<req/>")).await.unwrap();
100 assert_eq!(result, Bytes::from_static(b"<resp/>"));
101 }
102
103 struct HeaderCapturingHandler {
108 captured_headers: Arc<Mutex<Vec<Vec<u8>>>>,
109 }
110
111 #[async_trait::async_trait]
112 impl SoapHandler for HeaderCapturingHandler {
113 async fn handle(&self, _body: Bytes) -> Result<Bytes, SoapFault> {
114 Ok(Bytes::from_static(b"<resp/>"))
115 }
116
117 async fn handle_with_headers(
118 &self,
119 _body: Bytes,
120 headers: &[Bytes],
121 ) -> Result<Bytes, SoapFault> {
122 let mut captured = self.captured_headers.lock().unwrap();
123 for h in headers {
124 captured.push(h.to_vec());
125 }
126 Ok(Bytes::from_static(b"<resp-with-headers/>"))
127 }
128 }
129
130 #[tokio::test]
131 async fn handle_with_headers_override_receives_header_fragments() {
132 let captured = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
133 let handler = HeaderCapturingHandler {
134 captured_headers: captured.clone(),
135 };
136
137 let header1 = Bytes::from_static(b"<wsa:To>http://example.com/service</wsa:To>");
138 let header2 = Bytes::from_static(b"<tev:SubscriptionId>sub-123</tev:SubscriptionId>");
139 let headers = [header1.clone(), header2.clone()];
140
141 let result = handler
142 .handle_with_headers(Bytes::from_static(b"<req/>"), &headers)
143 .await
144 .unwrap();
145
146 assert_eq!(result, Bytes::from_static(b"<resp-with-headers/>"));
148
149 let seen = captured.lock().unwrap();
151 assert_eq!(seen.len(), 2);
152 assert_eq!(seen[0], header1.as_ref());
153 assert_eq!(seen[1], header2.as_ref());
154 }
155
156 #[tokio::test]
157 async fn handle_with_headers_default_falls_through_to_handle() {
158 let handler = FnHandler::new(|body: Bytes| async move {
161 Ok::<Bytes, SoapFault>(body)
163 });
164
165 let body = Bytes::from_static(b"<echo>data</echo>");
166 let header = Bytes::from_static(b"<some:Header>ignored</some:Header>");
167
168 let result = handler
169 .handle_with_headers(body.clone(), &[header])
170 .await
171 .unwrap();
172
173 assert_eq!(result, body);
175 }
176}