sms_web_warp/
lib.rs

1use bytes::Bytes;
2use sms_core::{Headers, InboundRegistry};
3use sms_web_generic::{HeaderConverter, ResponseConverter, WebhookProcessor};
4use warp::{http::HeaderMap, hyper::StatusCode, Filter, Rejection, Reply};
5
6#[derive(Clone)]
7pub struct AppState {
8    pub registry: InboundRegistry,
9}
10
11/// Warp-specific header converter
12pub struct WarpHeaderConverter;
13
14impl HeaderConverter for WarpHeaderConverter {
15    type HeaderType = HeaderMap;
16
17    fn to_generic_headers(headers: &Self::HeaderType) -> Headers {
18        headers
19            .iter()
20            .map(|(k, v)| {
21                (
22                    k.as_str().to_string(),
23                    v.to_str().unwrap_or_default().to_string(),
24                )
25            })
26            .collect()
27    }
28}
29
30/// Warp-specific response converter
31pub struct WarpResponseConverter;
32
33impl ResponseConverter for WarpResponseConverter {
34    type ResponseType = warp::reply::Response;
35
36    fn from_webhook_response(response: sms_core::WebhookResponse) -> Self::ResponseType {
37        let status = StatusCode::from_u16(response.status.as_u16())
38            .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
39
40        warp::reply::with_status(
41            warp::reply::with_header(response.body, "content-type", response.content_type),
42            status,
43        )
44        .into_response()
45    }
46}
47
48/// Unified webhook handler for Warp
49pub async fn unified_webhook_handler(
50    provider: String,
51    headers: HeaderMap,
52    body: Bytes,
53    state: AppState,
54) -> Result<warp::reply::Response, Rejection> {
55    let processor = WebhookProcessor::new(state.registry);
56    let generic_headers = WarpHeaderConverter::to_generic_headers(&headers);
57    let response = processor.process_webhook(&provider, generic_headers, &body);
58    Ok(WarpResponseConverter::from_webhook_response(response))
59}
60
61/// Helper function to create a Warp filter for SMS webhooks
62pub fn webhook_filter(
63    state: AppState,
64) -> impl warp::Filter<Extract = (warp::reply::Response,), Error = Rejection> + Clone {
65    warp::path!("webhooks" / String)
66        .and(warp::post())
67        .and(warp::header::headers_cloned())
68        .and(warp::body::bytes())
69        .and(warp::any().map(move || state.clone()))
70        .and_then(unified_webhook_handler)
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use sms_core::InboundRegistry;
77
78    #[tokio::test]
79    async fn webhook_filter_compiles() {
80        let registry = InboundRegistry::new();
81        let state = AppState { registry };
82        let _filter = webhook_filter(state);
83    }
84}