armature_lambda/
runtime.rs1use lambda_http::{Body, Error, Request, Response, run, service_fn};
4use std::sync::Arc;
5use tracing::{debug, info};
6
7use crate::{LambdaRequest, LambdaResponse};
8
9#[derive(Debug, Clone)]
11pub struct LambdaConfig {
12 pub log_requests: bool,
14 pub log_responses: bool,
16 pub base_path: Option<String>,
18}
19
20impl Default for LambdaConfig {
21 fn default() -> Self {
22 Self {
23 log_requests: true,
24 log_responses: false,
25 base_path: None,
26 }
27 }
28}
29
30impl LambdaConfig {
31 pub fn log_requests(mut self, enabled: bool) -> Self {
33 self.log_requests = enabled;
34 self
35 }
36
37 pub fn log_responses(mut self, enabled: bool) -> Self {
39 self.log_responses = enabled;
40 self
41 }
42
43 pub fn base_path(mut self, path: impl Into<String>) -> Self {
45 self.base_path = Some(path.into());
46 self
47 }
48}
49
50pub struct LambdaRuntime<App> {
55 app: Arc<App>,
56 config: LambdaConfig,
57}
58
59impl<App> LambdaRuntime<App>
60where
61 App: Send + Sync + 'static,
62{
63 pub fn new(app: App) -> Self {
65 Self {
66 app: Arc::new(app),
67 config: LambdaConfig::default(),
68 }
69 }
70
71 pub fn with_config(mut self, config: LambdaConfig) -> Self {
73 self.config = config;
74 self
75 }
76
77 pub async fn run(self) -> Result<(), Error>
81 where
82 App: RequestHandler,
83 {
84 info!("Starting Armature Lambda runtime");
85
86 let app = self.app.clone();
87 let config = self.config.clone();
88
89 run(service_fn(move |request: Request| {
90 let app = app.clone();
91 let config = config.clone();
92 async move { handle_request(app, config, request).await }
93 }))
94 .await
95 }
96}
97
98#[async_trait::async_trait]
102pub trait RequestHandler: Send + Sync {
103 async fn handle(&self, request: LambdaRequest) -> LambdaResponse;
105}
106
107async fn handle_request<App: RequestHandler>(
109 app: Arc<App>,
110 config: LambdaConfig,
111 request: Request,
112) -> Result<Response<Body>, Error> {
113 let mut lambda_request = LambdaRequest::from_lambda_request(request);
115
116 if let Some(base_path) = &config.base_path
118 && lambda_request.path.starts_with(base_path)
119 {
120 lambda_request.path = lambda_request
121 .path
122 .strip_prefix(base_path)
123 .unwrap_or(&lambda_request.path)
124 .to_string();
125 if lambda_request.path.is_empty() {
126 lambda_request.path = "/".to_string();
127 }
128 }
129
130 if config.log_requests {
132 debug!(
133 method = %lambda_request.method,
134 path = %lambda_request.path,
135 request_id = ?lambda_request.request_context.request_id,
136 "Handling Lambda request"
137 );
138 }
139
140 let response = app.handle(lambda_request).await;
142
143 if config.log_responses {
145 debug!(status = response.status, "Lambda response");
146 }
147
148 Ok(response.into_lambda_response())
149}
150
151#[macro_export]
160macro_rules! impl_request_handler {
161 ($app_type:ty) => {
162 #[async_trait::async_trait]
163 impl $crate::runtime::RequestHandler for $app_type {
164 async fn handle(&self, request: $crate::LambdaRequest) -> $crate::LambdaResponse {
165 match self
169 .handle_request(request.method, &request.path, request.body)
170 .await
171 {
172 Ok(response) => {
173 let mut lambda_response =
174 $crate::LambdaResponse::new(response.status, response.body);
175 for (name, value) in response.headers {
176 lambda_response = lambda_response.header(name, value);
177 }
178 lambda_response
179 }
180 Err(e) => $crate::LambdaResponse::internal_error(e.to_string()),
181 }
182 }
183 }
184 };
185}
186
187#[async_trait::async_trait]
189impl<F, Fut> RequestHandler for F
190where
191 F: Fn(LambdaRequest) -> Fut + Send + Sync,
192 Fut: std::future::Future<Output = LambdaResponse> + Send,
193{
194 async fn handle(&self, request: LambdaRequest) -> LambdaResponse {
195 self(request).await
196 }
197}