sms_web_rocket/
lib.rs

1use rocket::{http::Status, State};
2use sms_core::{Headers, InboundRegistry};
3use sms_web_generic::{ResponseConverter, WebhookProcessor};
4
5#[derive(Clone)]
6pub struct AppState {
7    pub registry: InboundRegistry,
8}
9
10/// Raw body data for Rocket
11#[derive(Debug)]
12pub struct RawBody(pub Vec<u8>);
13
14#[rocket::async_trait]
15impl<'r> rocket::data::FromData<'r> for RawBody {
16    type Error = Box<dyn std::error::Error + Send + Sync>;
17
18    async fn from_data(
19        _req: &'r rocket::Request<'_>,
20        data: rocket::Data<'r>,
21    ) -> rocket::data::Outcome<'r, Self> {
22        use rocket::data::ToByteUnit;
23
24        match data.open(2.megabytes()).into_bytes().await {
25            Ok(bytes) if bytes.is_complete() => {
26                rocket::data::Outcome::Success(RawBody(bytes.into_inner()))
27            }
28            Ok(_) => rocket::data::Outcome::Error((
29                Status::PayloadTooLarge,
30                Box::new(std::io::Error::other("Body too large")),
31            )),
32            Err(e) => rocket::data::Outcome::Error((Status::BadRequest, Box::new(e))),
33        }
34    }
35}
36
37/// Rocket-specific response converter
38pub struct RocketResponseConverter;
39
40impl ResponseConverter for RocketResponseConverter {
41    type ResponseType = (Status, (rocket::http::ContentType, String));
42
43    fn from_webhook_response(response: sms_core::WebhookResponse) -> Self::ResponseType {
44        let status = match response.status.as_u16() {
45            200 => Status::Ok,
46            400 => Status::BadRequest,
47            401 => Status::Unauthorized,
48            404 => Status::NotFound,
49            _ => Status::InternalServerError,
50        };
51
52        let content_type = match response.content_type.as_str() {
53            "application/json" => rocket::http::ContentType::JSON,
54            _ => rocket::http::ContentType::Plain,
55        };
56
57        (status, (content_type, response.body))
58    }
59}
60
61/// Unified webhook handler for Rocket
62/// Note: Rocket's handler API doesn't easily allow access to raw headers,
63/// so we pass empty headers. For production use, you might want to use
64/// custom request guards to extract specific headers you need.
65#[rocket::post("/webhooks/<provider>", data = "<body>")]
66pub fn unified_webhook(
67    provider: String,
68    body: RawBody,
69    state: &State<AppState>,
70) -> (Status, (rocket::http::ContentType, String)) {
71    let processor = WebhookProcessor::new(state.registry.clone());
72    // Rocket doesn't easily expose all headers in handlers, so we pass empty headers
73    // For production use, you'd want to use request guards to extract specific headers
74    let headers: Headers = vec![];
75    let response = processor.process_webhook(&provider, headers, &body.0);
76    RocketResponseConverter::from_webhook_response(response)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn rocket_types_compile() {
85        let registry = InboundRegistry::new();
86        let _state = AppState { registry };
87        // Rocket testing requires a full rocket instance, which is complex to set up
88        // This test just ensures the types compile correctly
89    }
90}