use crate::fault::SoapFault;
use async_trait::async_trait;
use bytes::Bytes;
use std::future::Future;
#[async_trait]
pub trait SoapHandler: Send + Sync + 'static {
async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault>;
async fn handle_with_headers(
&self,
body: Bytes,
_headers: &[Bytes],
) -> Result<Bytes, SoapFault> {
self.handle(body).await
}
}
pub struct FnHandler<F> {
f: F,
}
impl<F, Fut> FnHandler<F>
where
F: Fn(Bytes) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Bytes, SoapFault>> + Send + 'static,
{
pub fn new(f: F) -> Self {
Self { f }
}
}
#[async_trait]
impl<F, Fut> SoapHandler for FnHandler<F>
where
F: Fn(Bytes) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Bytes, SoapFault>> + Send + 'static,
{
async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault> {
(self.f)(body).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fault::{FaultCode, SoapFault};
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn fn_handler_ok_passthrough() {
let handler = FnHandler::new(|body: Bytes| async move { Ok::<Bytes, SoapFault>(body) });
let input = Bytes::from_static(b"<hello/>");
let result = handler.handle(input.clone()).await.unwrap();
assert_eq!(result, input);
}
#[tokio::test]
async fn fn_handler_returns_fault() {
let handler = FnHandler::new(|_body: Bytes| async move {
Err::<Bytes, SoapFault>(SoapFault::sender("not allowed"))
});
let result = handler.handle(Bytes::from_static(b"<test/>")).await;
assert!(result.is_err());
let fault = result.unwrap_err();
assert_eq!(fault.code, FaultCode::Sender);
assert!(fault.reason.contains("not allowed"));
}
#[tokio::test]
async fn fn_handler_closure_captures_context() {
let expected_response = Bytes::from_static(b"<response>ok</response>");
let resp_clone = expected_response.clone();
let handler = FnHandler::new(move |_body: Bytes| {
let resp = resp_clone.clone();
async move { Ok::<Bytes, SoapFault>(resp) }
});
let result = handler
.handle(Bytes::from_static(b"<request/>"))
.await
.unwrap();
assert_eq!(result, expected_response);
}
#[tokio::test]
async fn soap_handler_trait_object() {
let handler: Box<dyn SoapHandler> = Box::new(FnHandler::new(|_body: Bytes| async move {
Ok::<Bytes, SoapFault>(Bytes::from_static(b"<resp/>"))
}));
let result = handler.handle(Bytes::from_static(b"<req/>")).await.unwrap();
assert_eq!(result, Bytes::from_static(b"<resp/>"));
}
struct HeaderCapturingHandler {
captured_headers: Arc<Mutex<Vec<Vec<u8>>>>,
}
#[async_trait::async_trait]
impl SoapHandler for HeaderCapturingHandler {
async fn handle(&self, _body: Bytes) -> Result<Bytes, SoapFault> {
Ok(Bytes::from_static(b"<resp/>"))
}
async fn handle_with_headers(
&self,
_body: Bytes,
headers: &[Bytes],
) -> Result<Bytes, SoapFault> {
let mut captured = self.captured_headers.lock().unwrap();
for h in headers {
captured.push(h.to_vec());
}
Ok(Bytes::from_static(b"<resp-with-headers/>"))
}
}
#[tokio::test]
async fn handle_with_headers_override_receives_header_fragments() {
let captured = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
let handler = HeaderCapturingHandler {
captured_headers: captured.clone(),
};
let header1 = Bytes::from_static(b"<wsa:To>http://example.com/service</wsa:To>");
let header2 = Bytes::from_static(b"<tev:SubscriptionId>sub-123</tev:SubscriptionId>");
let headers = [header1.clone(), header2.clone()];
let result = handler
.handle_with_headers(Bytes::from_static(b"<req/>"), &headers)
.await
.unwrap();
assert_eq!(result, Bytes::from_static(b"<resp-with-headers/>"));
let seen = captured.lock().unwrap();
assert_eq!(seen.len(), 2);
assert_eq!(seen[0], header1.as_ref());
assert_eq!(seen[1], header2.as_ref());
}
#[tokio::test]
async fn handle_with_headers_default_falls_through_to_handle() {
let handler = FnHandler::new(|body: Bytes| async move {
Ok::<Bytes, SoapFault>(body)
});
let body = Bytes::from_static(b"<echo>data</echo>");
let header = Bytes::from_static(b"<some:Header>ignored</some:Header>");
let result = handler
.handle_with_headers(body.clone(), &[header])
.await
.unwrap();
assert_eq!(result, body);
}
}