Skip to main content

soap_server/
handler.rs

1// Raw handler trait — receives XML bytes, returns XML bytes or SoapFault
2use crate::fault::SoapFault;
3use async_trait::async_trait;
4use bytes::Bytes;
5use std::future::Future;
6
7/// A SOAP operation handler that receives the Body first child element as
8/// self-contained XML bytes (all ancestor namespace declarations re-emitted
9/// on the root) and returns response body XML bytes or a SoapFault.
10#[async_trait]
11pub trait SoapHandler: Send + Sync + 'static {
12    async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault>;
13
14    /// Handle a request with access to the SOAP header element fragments (each is the
15    /// raw bytes of one direct child of `<Header>`). Defaults to ignoring headers and
16    /// calling `handle`. Handlers needing WS-Addressing/WS-Security header data override this.
17    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
26/// Wraps a closure as a SoapHandler for ergonomic registration.
27pub 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        // Verify FnHandler can be used as a trait object
96        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    // ── handle_with_headers tests (round-2 #5) ────────────────────────────────
104
105    /// A handler that overrides handle_with_headers, records received header fragments,
106    /// and returns a fixed response.
107    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        // Overriding handler produces its own response.
147        assert_eq!(result, Bytes::from_static(b"<resp-with-headers/>"));
148
149        // Both header fragments were delivered.
150        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        // FnHandler does NOT override handle_with_headers — the default impl must
159        // delegate to handle() and the handler response must come through unchanged.
160        let handler = FnHandler::new(|body: Bytes| async move {
161            // Echo back the body to prove handle() was actually called.
162            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        // Default impl calls handle(body) — should receive the body back.
174        assert_eq!(result, body);
175    }
176}