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 mut event = ApiGatewayProxyRequest::default();
161 event.path = Some("/users/123/posts/456".to_string());
162 event.http_method = Method::GET;
163 event.resource = Some("/users/{id}/posts/{post_id}".to_string());
164 event.path_parameters = path_parameters;
165
166 let lambda_context = lambda_runtime::Context::default();
167 let lambda_event = LambdaEvent::new(event, lambda_context);
168
169 let result = router
170 .handle_request(lambda_event, Arc::new(TestState {}))
171 .await
172 .unwrap();
173
174 assert_eq!(result["params"]["id"], "123");
175 assert_eq!(result["params"]["post_id"], "456");
176 }
177
178 #[tokio::test]
179 async fn test_method_matching_with_apigw() {
180 let mut router = Router::<TestState, ApiGatewayProxyRequest>::new();
181
182 router.register_route("GET", "/quotes", |_| async move {
184 Ok(json!({ "method": "GET" }))
185 });
186
187 router.register_route("POST", "/quotes", |_| async move {
188 Ok(json!({ "method": "POST" }))
189 });
190
191 let mut post_event = ApiGatewayProxyRequest::default();
193 post_event.path = Some("/quotes".to_string());
194 post_event.http_method = Method::POST;
195 post_event.resource = Some("/quotes".to_string());
196 post_event.path_parameters = HashMap::new();
197
198 let lambda_context = lambda_runtime::Context::default();
199 let lambda_event = LambdaEvent::new(post_event, lambda_context);
200
201 let result = router
203 .handle_request(lambda_event, Arc::new(TestState {}))
204 .await
205 .unwrap();
206 assert_eq!(
207 result["method"], "POST",
208 "POST request should be handled by POST handler"
209 );
210
211 let mut get_event = ApiGatewayProxyRequest::default();
213 get_event.path = Some("/quotes".to_string());
214 get_event.http_method = Method::GET;
215 get_event.resource = Some("/quotes".to_string());
216 get_event.path_parameters = HashMap::new();
217
218 let lambda_context = lambda_runtime::Context::default();
219 let lambda_event = LambdaEvent::new(get_event, lambda_context);
220
221 let result = router
223 .handle_request(lambda_event, Arc::new(TestState {}))
224 .await
225 .unwrap();
226 assert_eq!(
227 result["method"], "GET",
228 "GET request should be handled by GET handler"
229 );
230 }
231}