serverust_lambda/lib.rs
1//! Runtime dual do framework serverust: HTTP local e AWS Lambda.
2//!
3//! Use a trait [`AppRuntime`] (importada deste crate) para chamar `.run()`
4//! diretamente em [`serverust_core::App`] — a função escolhe entre Lambda e
5//! HTTP local olhando para `AWS_LAMBDA_RUNTIME_API`.
6
7use std::net::SocketAddr;
8
9use lambda_http::Error as LambdaError;
10use serverust_core::App;
11use tokio::net::ToSocketAddrs;
12
13pub use aws_lambda_events;
14pub use lambda_http;
15
16/// Tipo de runtime escolhido pela detecção de ambiente.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Runtime {
19 /// AWS Lambda detectado (`AWS_LAMBDA_RUNTIME_API` presente).
20 Lambda,
21 /// Execução local em servidor HTTP.
22 Http,
23}
24
25/// Função pura usada pelo runtime dispatcher e por testes.
26///
27/// Considera Lambda apenas quando a variável existe e não está vazia, para
28/// evitar falso-positivo se algum operador exportar `AWS_LAMBDA_RUNTIME_API=`
29/// sem valor.
30pub fn detect_runtime(env_value: Option<&str>) -> Runtime {
31 match env_value {
32 Some(value) if !value.is_empty() => Runtime::Lambda,
33 _ => Runtime::Http,
34 }
35}
36
37fn current_runtime() -> Runtime {
38 detect_runtime(std::env::var("AWS_LAMBDA_RUNTIME_API").ok().as_deref())
39}
40
41/// Sobe a App no runtime Lambda (consumindo eventos do API Gateway / Function URL).
42///
43/// Define `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH=true` automaticamente para que
44/// rotas funcionem idênticas em todos os triggers (REST v1 incluiria o `stage`
45/// no path por default). A var só é definida se ainda não estiver presente,
46/// permitindo override pelo operador.
47pub async fn run_lambda(app: App) -> Result<(), LambdaError> {
48 if std::env::var_os("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_none() {
49 // SAFETY: chamado uma única vez na inicialização do binário Lambda,
50 // antes do runtime spawnar worker tasks. Não há concorrência com
51 // outras leituras/escritas de env.
52 unsafe { std::env::set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true") };
53 }
54 let router = app.into_router();
55 lambda_http::run(router).await
56}
57
58/// Sobe a App em modo HTTP local atado a `addr`.
59pub async fn run_http<A: ToSocketAddrs>(app: App, addr: A) -> std::io::Result<()> {
60 app.run_http(addr).await
61}
62
63/// Dispatcher: escolhe entre Lambda e HTTP local conforme o ambiente.
64///
65/// Em modo HTTP local, utiliza o endereço default `0.0.0.0:3000`. Quem precisar
66/// customizar deve chamar [`run_http`] diretamente.
67pub async fn run(app: App) -> Result<(), LambdaError> {
68 match current_runtime() {
69 Runtime::Lambda => run_lambda(app).await,
70 Runtime::Http => {
71 let addr: SocketAddr = "0.0.0.0:3000".parse().expect("addr literal sempre parseia");
72 run_http(app, addr).await.map_err(LambdaError::from)
73 }
74 }
75}
76
77/// Extensão que permite chamar `.run()` e `.run_lambda()` em [`App`] via dot-chain.
78///
79/// A função [`run`](Self::run) detecta automaticamente o ambiente: em AWS
80/// Lambda (`AWS_LAMBDA_RUNTIME_API` presente), invoca `lambda_http::run`; em
81/// outros casos, sobe servidor HTTP local em `0.0.0.0:3000`.
82///
83/// ```no_run
84/// use serverust_core::App;
85/// use serverust_lambda::AppRuntime;
86/// use serverust_macros::get;
87///
88/// #[get("/")]
89/// async fn hello() -> &'static str { "hello" }
90///
91/// #[tokio::main]
92/// async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
93/// App::new().route(hello).run().await?;
94/// Ok(())
95/// }
96/// ```
97pub trait AppRuntime {
98 /// Detecta o ambiente e despacha entre Lambda e HTTP local.
99 fn run(self) -> impl std::future::Future<Output = Result<(), LambdaError>>;
100 /// Força runtime Lambda (`lambda_http::run`) independentemente do ambiente.
101 fn run_lambda(self) -> impl std::future::Future<Output = Result<(), LambdaError>>;
102}
103
104impl AppRuntime for App {
105 fn run(self) -> impl std::future::Future<Output = Result<(), LambdaError>> {
106 run(self)
107 }
108 fn run_lambda(self) -> impl std::future::Future<Output = Result<(), LambdaError>> {
109 run_lambda(self)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn detect_runtime_returns_lambda_when_env_var_is_set() {
119 assert_eq!(
120 detect_runtime(Some("127.0.0.1:9001")),
121 Runtime::Lambda,
122 "presença da var indica execução Lambda"
123 );
124 }
125
126 #[test]
127 fn detect_runtime_returns_http_when_env_var_is_absent() {
128 assert_eq!(detect_runtime(None), Runtime::Http);
129 }
130
131 #[test]
132 fn detect_runtime_returns_http_when_env_var_is_empty() {
133 assert_eq!(
134 detect_runtime(Some("")),
135 Runtime::Http,
136 "valor vazio não deve ativar runtime Lambda"
137 );
138 }
139}