lambda_lw_http_router_core/
lib.rs1#![allow(clippy::type_complexity)]
2
3pub use ctor;
25pub use ctor::ctor as ctor_attribute;
26mod routable_http_event;
27mod route_context;
28mod router;
29pub use routable_http_event::RoutableHttpEvent;
30pub use route_context::RouteContext;
31pub use router::{register_route, Router, RouterBuilder};
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use aws_lambda_events::apigw::ApiGatewayProxyRequest;
37 use aws_lambda_events::http::Method;
38 use lambda_runtime::LambdaEvent;
39 use serde_json::json;
40 use std::collections::HashMap;
41 use std::sync::Arc;
42
43 #[derive(Clone)]
45 struct TestHttpEvent {
46 path: String,
47 method: String,
48 }
49
50 impl RoutableHttpEvent for TestHttpEvent {
51 fn path(&self) -> Option<String> {
52 Some(self.path.clone())
53 }
54
55 fn http_method(&self) -> String {
56 self.method.clone()
57 }
58 }
59
60 #[derive(Clone)]
62 struct TestState {}
63
64 #[tokio::test]
65 async fn test_path_parameter_extraction() {
66 let mut router = Router::<TestState, TestHttpEvent>::new();
67
68 router.register_route("GET", "/users/{id}/posts/{post_id}", |ctx| async move {
70 Ok(json!({
71 "user_id": ctx.params.get("id"),
72 "post_id": ctx.params.get("post_id"),
73 }))
74 });
75
76 let event = TestHttpEvent {
78 path: "/users/123/posts/456".to_string(),
79 method: "GET".to_string(),
80 };
81 let lambda_context = lambda_runtime::Context::default();
82 let lambda_event = LambdaEvent::new(event, lambda_context);
83
84 let result = router
86 .handle_request(lambda_event, Arc::new(TestState {}))
87 .await
88 .unwrap();
89
90 assert_eq!(result["user_id"], "123");
92 assert_eq!(result["post_id"], "456");
93 }
94
95 #[tokio::test]
96 async fn test_greedy_path_parameter() {
97 let mut router = Router::<TestState, TestHttpEvent>::new();
98
99 router.register_route("GET", "/files/{path+}", |ctx| async move {
101 Ok(json!({
102 "path": ctx.params.get("path"),
103 }))
104 });
105
106 let event = TestHttpEvent {
108 path: "/files/documents/2024/report.pdf".to_string(),
109 method: "GET".to_string(),
110 };
111 let lambda_context = lambda_runtime::Context::default();
112 let lambda_event = LambdaEvent::new(event, lambda_context);
113
114 let result = router
116 .handle_request(lambda_event, Arc::new(TestState {}))
117 .await
118 .unwrap();
119
120 assert_eq!(result["path"], "documents/2024/report.pdf");
122 }
123
124 #[tokio::test]
125 async fn test_no_match_returns_404() {
126 let router = Router::<TestState, TestHttpEvent>::new();
127
128 let event = TestHttpEvent {
130 path: "/nonexistent".to_string(),
131 method: "GET".to_string(),
132 };
133 let lambda_context = lambda_runtime::Context::default();
134 let lambda_event = LambdaEvent::new(event, lambda_context);
135
136 let result = router
138 .handle_request(lambda_event, Arc::new(TestState {}))
139 .await
140 .unwrap();
141
142 assert_eq!(result["statusCode"], 404);
144 }
145
146 #[tokio::test]
147 async fn test_apigw_resource_path_parameters() {
148 let mut router = Router::<TestState, ApiGatewayProxyRequest>::new();
149
150 router.register_route("GET", "/users/{id}/posts/{post_id}", |ctx| async move {
151 Ok(json!({
152 "params": ctx.params,
153 }))
154 });
155
156 let mut path_parameters = HashMap::new();
157 path_parameters.insert("id".to_string(), "123".to_string());
158 path_parameters.insert("post_id".to_string(), "456".to_string());
159
160 let event = ApiGatewayProxyRequest {
161 path: Some("/users/123/posts/456".to_string()),
162 http_method: Method::GET,
163 resource: Some("/users/{id}/posts/{post_id}".to_string()),
164 path_parameters,
165 ..Default::default()
166 };
167
168 let lambda_context = lambda_runtime::Context::default();
169 let lambda_event = LambdaEvent::new(event, lambda_context);
170
171 let result = router
172 .handle_request(lambda_event, Arc::new(TestState {}))
173 .await
174 .unwrap();
175
176 assert_eq!(result["params"]["id"], "123");
177 assert_eq!(result["params"]["post_id"], "456");
178 }
179
180 #[tokio::test]
181 async fn test_method_matching_with_apigw() {
182 let mut router = Router::<TestState, ApiGatewayProxyRequest>::new();
183
184 router.register_route("GET", "/quotes", |_| async move {
186 Ok(json!({ "method": "GET" }))
187 });
188
189 router.register_route("POST", "/quotes", |_| async move {
190 Ok(json!({ "method": "POST" }))
191 });
192
193 let post_event = ApiGatewayProxyRequest {
195 path: Some("/quotes".to_string()),
196 http_method: Method::POST,
197 resource: Some("/quotes".to_string()),
198 path_parameters: HashMap::new(),
199 ..Default::default()
200 };
201
202 let lambda_context = lambda_runtime::Context::default();
203 let lambda_event = LambdaEvent::new(post_event, lambda_context);
204
205 let result = router
207 .handle_request(lambda_event, Arc::new(TestState {}))
208 .await
209 .unwrap();
210 assert_eq!(
211 result["method"], "POST",
212 "POST request should be handled by POST handler"
213 );
214
215 let get_event = ApiGatewayProxyRequest {
217 path: Some("/quotes".to_string()),
218 http_method: Method::GET,
219 resource: Some("/quotes".to_string()),
220 path_parameters: HashMap::new(),
221 ..Default::default()
222 };
223
224 let lambda_context = lambda_runtime::Context::default();
225 let lambda_event = LambdaEvent::new(get_event, lambda_context);
226
227 let result = router
229 .handle_request(lambda_event, Arc::new(TestState {}))
230 .await
231 .unwrap();
232 assert_eq!(
233 result["method"], "GET",
234 "GET request should be handled by GET handler"
235 );
236 }
237}