armature_lambda/
runtime.rs

1//! Lambda runtime for Armature applications.
2
3use lambda_http::{Body, Error, Request, Response, run, service_fn};
4use std::sync::Arc;
5use tracing::{debug, info};
6
7use crate::{LambdaRequest, LambdaResponse};
8
9/// Lambda runtime configuration.
10#[derive(Debug, Clone)]
11pub struct LambdaConfig {
12    /// Enable request logging.
13    pub log_requests: bool,
14    /// Enable response logging.
15    pub log_responses: bool,
16    /// Custom base path to strip (e.g., "/prod", "/dev").
17    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    /// Enable request logging.
32    pub fn log_requests(mut self, enabled: bool) -> Self {
33        self.log_requests = enabled;
34        self
35    }
36
37    /// Enable response logging.
38    pub fn log_responses(mut self, enabled: bool) -> Self {
39        self.log_responses = enabled;
40        self
41    }
42
43    /// Set a base path to strip from requests.
44    pub fn base_path(mut self, path: impl Into<String>) -> Self {
45        self.base_path = Some(path.into());
46        self
47    }
48}
49
50/// Lambda runtime for Armature applications.
51///
52/// This wraps an Armature application and runs it on the Lambda runtime,
53/// translating API Gateway/ALB requests to Armature requests.
54pub struct LambdaRuntime<App> {
55    app: Arc<App>,
56    config: LambdaConfig,
57}
58
59impl<App> LambdaRuntime<App>
60where
61    App: Send + Sync + 'static,
62{
63    /// Create a new Lambda runtime.
64    pub fn new(app: App) -> Self {
65        Self {
66            app: Arc::new(app),
67            config: LambdaConfig::default(),
68        }
69    }
70
71    /// Set the runtime configuration.
72    pub fn with_config(mut self, config: LambdaConfig) -> Self {
73        self.config = config;
74        self
75    }
76
77    /// Run the Lambda runtime.
78    ///
79    /// This function never returns under normal operation.
80    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/// Request handler trait for Armature applications.
99///
100/// This is automatically implemented for Armature Application types.
101#[async_trait::async_trait]
102pub trait RequestHandler: Send + Sync {
103    /// Handle an HTTP request.
104    async fn handle(&self, request: LambdaRequest) -> LambdaResponse;
105}
106
107/// Handle a Lambda request.
108async fn handle_request<App: RequestHandler>(
109    app: Arc<App>,
110    config: LambdaConfig,
111    request: Request,
112) -> Result<Response<Body>, Error> {
113    // Convert Lambda request
114    let mut lambda_request = LambdaRequest::from_lambda_request(request);
115
116    // Strip base path if configured
117    if let Some(base_path) = &config.base_path {
118        if lambda_request.path.starts_with(base_path) {
119            lambda_request.path = lambda_request
120                .path
121                .strip_prefix(base_path)
122                .unwrap_or(&lambda_request.path)
123                .to_string();
124            if lambda_request.path.is_empty() {
125                lambda_request.path = "/".to_string();
126            }
127        }
128    }
129
130    // Log request if enabled
131    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    // Handle request
141    let response = app.handle(lambda_request).await;
142
143    // Log response if enabled
144    if config.log_responses {
145        debug!(status = response.status, "Lambda response");
146    }
147
148    Ok(response.into_lambda_response())
149}
150
151/// Macro to implement RequestHandler for Armature applications.
152///
153/// Usage:
154/// ```rust,ignore
155/// use armature_lambda::impl_request_handler;
156///
157/// impl_request_handler!(MyApplication);
158/// ```
159#[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                // Convert to Armature HttpRequest and handle
166                // This is a simplified implementation - full version would
167                // properly convert all request data
168                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/// Example implementation for a simple handler function.
188#[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}