1use lambda_http::{Body, Request, Response, run, service_fn};
2use slack_volt::App;
3use slack_volt::middleware::Headers;
4use std::sync::Arc;
5use tracing;
6
7pub struct LambdaAdapter;
8
9impl LambdaAdapter {
10 pub async fn run(app: App) -> Result<(), lambda_http::Error> {
11 let app = Arc::new(app);
12 run(service_fn(move |req: Request| {
13 let app = app.clone();
14 async move { handle_request(req, &app).await }
15 }))
16 .await
17 }
18}
19
20async fn handle_request(
21 req: Request,
22 app: &App,
23) -> Result<Response<Body>, lambda_http::Error> {
24 let headers = extract_headers(&req);
25 let content_type = headers.content_type.clone();
26 let body = match req.body() {
27 Body::Text(s) => s.clone(),
28 Body::Binary(b) => String::from_utf8_lossy(b).to_string(),
29 Body::Empty => String::new(),
30 };
31
32 tracing::debug!(content_type = %content_type, body_len = body.len(), "incoming slack request");
33
34 match app.dispatch_async(&content_type, &body, headers).await {
35 Ok(ack) => {
36 if ack.is_empty() {
37 let resp = Response::builder()
38 .status(200)
39 .body(Body::Empty)?;
40 Ok(resp)
41 } else {
42 let response_body = serde_json::to_string(&ack)?;
43 let resp = Response::builder()
44 .status(200)
45 .header("content-type", "application/json")
46 .body(Body::Text(response_body))?;
47 Ok(resp)
48 }
49 }
50 Err(slack_volt::Error::NoHandler { kind, id }) => {
51 tracing::warn!(kind, id, "no handler registered");
52 let resp = Response::builder()
53 .status(200)
54 .body(Body::Empty)?;
55 Ok(resp)
56 }
57 Err(slack_volt::Error::SignatureVerification(msg)) => {
58 tracing::error!(msg, "signature verification failed");
59 let resp = Response::builder()
60 .status(401)
61 .body(Body::Text("unauthorized".to_string()))?;
62 Ok(resp)
63 }
64 Err(e) => {
65 tracing::error!(error = %e, "handler error");
66 let resp = Response::builder()
67 .status(200)
68 .body(Body::Empty)?;
69 Ok(resp)
70 }
71 }
72}
73
74fn extract_headers(req: &Request) -> Headers {
75 let get = |name: &str| {
76 req.headers()
77 .get(name)
78 .and_then(|v| v.to_str().ok())
79 .unwrap_or("")
80 .to_string()
81 };
82
83 Headers {
84 timestamp: get("x-slack-request-timestamp"),
85 signature: get("x-slack-signature"),
86 content_type: get("content-type"),
87 }
88}