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