1use std::{future::Future, pin::Pin};
6
7use super::ProtocolAdapter;
8
9#[derive(Debug, Clone)]
11pub struct RestRoute {
12 pub method: String,
14 pub path: String,
16 pub handler: String,
18}
19
20impl RestRoute {
21 pub fn new(
23 method: impl Into<String>,
24 path: impl Into<String>,
25 handler: impl Into<String>,
26 ) -> Self {
27 Self {
28 method: method.into(),
29 path: path.into(),
30 handler: handler.into(),
31 }
32 }
33
34 pub fn matches(&self, method: &str, path: &str) -> bool {
36 if self.method != method {
37 return false;
38 }
39
40 self.path == path
42 }
43
44 pub fn matches_path(&self, path: &str) -> bool {
46 let route_segments: Vec<&str> = self.path.split('/').filter(|s| !s.is_empty()).collect();
48 let path_segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
49
50 if route_segments.len() != path_segments.len() {
52 return false;
53 }
54
55 for (route_seg, path_seg) in route_segments.iter().zip(path_segments.iter()) {
57 if route_seg.starts_with(':') {
59 continue;
60 }
61 if route_seg != path_seg {
63 return false;
64 }
65 }
66
67 true
68 }
69}
70
71pub struct RestAdapter {
75 routes: Vec<RestRoute>,
76}
77
78impl RestAdapter {
79 pub fn new() -> Self {
81 Self { routes: Vec::new() }
82 }
83
84 pub fn route(&mut self, method: &str, path: &str, handler: &str) -> &mut Self {
86 self.routes.push(RestRoute::new(method, path, handler));
87 self
88 }
89
90 pub fn match_route(&self, method: &str, path: &str) -> Option<&RestRoute> {
92 self.routes
93 .iter()
94 .find(|r| r.method == method && r.matches_path(path))
95 }
96
97 pub fn parse_request(&self, request: &str) -> Result<(String, String, Option<String>), String> {
102 let parts: Vec<&str> = request.splitn(3, ' ').collect();
103
104 if parts.len() < 2 {
105 return Err("Invalid request format. Expected: METHOD /path [body]".to_string());
106 }
107
108 let method = parts[0].to_string();
109 let path = parts[1].to_string();
110 let body = parts.get(2).map(|s| s.to_string());
111
112 Ok((method, path, body))
113 }
114
115 pub fn format_response(&self, status: u16, body: &str) -> String {
117 format!("HTTP {} {}", status, body)
118 }
119
120 pub fn build_request(
125 &self,
126 method: &str,
127 path: &str,
128 _body: Option<&str>,
129 _headers: Option<&str>,
130 ) -> RestRequest {
131 RestRequest {
132 method: method.to_string(),
133 path: path.to_string(),
134 }
135 }
136}
137
138impl Default for RestAdapter {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl ProtocolAdapter for RestAdapter {
145 fn name(&self) -> &str {
146 "rest"
147 }
148
149 fn handle(
150 &self,
151 request: &str,
152 ) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + '_>> {
153 let parse_result = self.parse_request(request);
155
156 let routes = self.routes.clone();
158
159 Box::pin(async move {
160 let (method, path, _body) = match parse_result {
162 Ok(parsed) => parsed,
163 Err(e) => {
164 let response = format!("HTTP 400 {}", e);
165 return Ok(response);
166 }
167 };
168
169 let matched_route = routes
171 .iter()
172 .find(|r| r.method == method && r.matches_path(&path));
173
174 match matched_route {
175 Some(route) => {
176 let response_body = format!(
179 "{{\"handler\":\"{}\",\"method\":\"{}\",\"path\":\"{}\"}}",
180 route.handler, method, path
181 );
182 let response = format!("HTTP 200 {}", response_body);
183 Ok(response)
184 }
185 None => {
186 let error = format!("{{\"error\":\"Not Found\",\"path\":\"{}\"}}", path);
188 let response = format!("HTTP 404 {}", error);
189 Ok(response)
190 }
191 }
192 })
193 }
194}
195
196#[derive(Debug, Clone)]
200pub struct RestRequest {
201 pub method: String,
203 pub path: String,
205}
206
207#[derive(Debug, Clone)]
211pub struct RestResponse {
212 status: u16,
213 body: String,
214}
215
216impl RestResponse {
217 pub fn new(status: u16, body: String) -> Self {
219 Self { status, body }
220 }
221
222 pub fn status(&self) -> u16 {
224 self.status
225 }
226
227 pub fn body(&self) -> &str {
229 &self.body
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_rest_adapter_creation() {
239 let adapter = RestAdapter::new();
240 assert_eq!(adapter.name(), "rest");
241 }
242
243 #[test]
244 fn test_build_request() {
245 let adapter = RestAdapter::new();
246 let request = adapter.build_request("GET", "/users/42", None, None);
247 assert_eq!(request.method, "GET");
248 assert_eq!(request.path, "/users/42");
249 }
250
251 #[test]
252 fn test_route_registration() {
253 let mut adapter = RestAdapter::new();
254 adapter.route("GET", "/users", "list_users");
255 adapter.route("POST", "/users", "create_user");
256
257 assert_eq!(adapter.routes.len(), 2);
258 assert_eq!(adapter.routes[0].method, "GET");
259 assert_eq!(adapter.routes[0].path, "/users");
260 assert_eq!(adapter.routes[0].handler, "list_users");
261 }
262
263 #[test]
264 fn test_route_matching_exact() {
265 let mut adapter = RestAdapter::new();
266 adapter.route("GET", "/users", "list_users");
267
268 let matched = adapter.match_route("GET", "/users");
269 assert!(matched.is_some());
270 assert_eq!(matched.unwrap().handler, "list_users");
271 }
272
273 #[test]
274 fn test_route_matching_not_found() {
275 let mut adapter = RestAdapter::new();
276 adapter.route("GET", "/users", "list_users");
277
278 let matched = adapter.match_route("GET", "/posts");
279 assert!(matched.is_none());
280 }
281
282 #[test]
283 fn test_route_matching_wrong_method() {
284 let mut adapter = RestAdapter::new();
285 adapter.route("GET", "/users", "list_users");
286
287 let matched = adapter.match_route("POST", "/users");
288 assert!(matched.is_none());
289 }
290
291 #[test]
292 fn test_route_matching_with_params() {
293 let mut adapter = RestAdapter::new();
294 adapter.route("GET", "/users/:id", "get_user");
295
296 let matched = adapter.match_route("GET", "/users/42");
297 assert!(matched.is_some());
298 assert_eq!(matched.unwrap().handler, "get_user");
299
300 let matched2 = adapter.match_route("GET", "/users/123");
301 assert!(matched2.is_some());
302 }
303
304 #[test]
305 fn test_route_matching_params_wrong_length() {
306 let mut adapter = RestAdapter::new();
307 adapter.route("GET", "/users/:id", "get_user");
308
309 let matched = adapter.match_route("GET", "/users");
311 assert!(matched.is_none());
312
313 let matched2 = adapter.match_route("GET", "/users/42/posts");
315 assert!(matched2.is_none());
316 }
317
318 #[test]
319 fn test_parse_request_get() {
320 let adapter = RestAdapter::new();
321 let result = adapter.parse_request("GET /users");
322
323 assert!(result.is_ok());
324 let (method, path, body) = result.unwrap();
325 assert_eq!(method, "GET");
326 assert_eq!(path, "/users");
327 assert!(body.is_none());
328 }
329
330 #[test]
331 fn test_parse_request_post_with_body() {
332 let adapter = RestAdapter::new();
333 let result = adapter.parse_request("POST /users {\"name\":\"John\"}");
334
335 assert!(result.is_ok());
336 let (method, path, body) = result.unwrap();
337 assert_eq!(method, "POST");
338 assert_eq!(path, "/users");
339 assert_eq!(body.unwrap(), "{\"name\":\"John\"}");
340 }
341
342 #[test]
343 fn test_parse_request_invalid() {
344 let adapter = RestAdapter::new();
345 let result = adapter.parse_request("INVALID");
346
347 assert!(result.is_err());
348 assert!(result.unwrap_err().contains("Invalid request format"));
349 }
350
351 #[test]
352 fn test_format_response_200() {
353 let adapter = RestAdapter::new();
354 let response = adapter.format_response(200, "{\"success\":true}");
355
356 assert!(response.contains("HTTP 200"));
357 assert!(response.contains("{\"success\":true}"));
358 }
359
360 #[test]
361 fn test_format_response_404() {
362 let adapter = RestAdapter::new();
363 let response = adapter.format_response(404, "{\"error\":\"Not Found\"}");
364
365 assert!(response.contains("HTTP 404"));
366 assert!(response.contains("Not Found"));
367 }
368
369 #[tokio::test]
370 async fn test_handle_request_success() {
371 let mut adapter = RestAdapter::new();
372 adapter.route("GET", "/users", "list_users");
373
374 let result = adapter.handle("GET /users").await;
375 assert!(result.is_ok());
376
377 let response = result.unwrap();
378 assert!(response.contains("HTTP 200"));
379 assert!(response.contains("list_users"));
380 }
381
382 #[tokio::test]
383 async fn test_handle_request_not_found() {
384 let adapter = RestAdapter::new();
385 let result = adapter.handle("GET /users").await;
386
387 assert!(result.is_ok());
388 let response = result.unwrap();
389 assert!(response.contains("HTTP 404"));
390 assert!(response.contains("Not Found"));
391 }
392
393 #[tokio::test]
394 async fn test_handle_request_invalid() {
395 let adapter = RestAdapter::new();
396 let result = adapter.handle("INVALID").await;
397
398 assert!(result.is_ok());
399 let response = result.unwrap();
400 assert!(response.contains("HTTP 400"));
401 }
402
403 #[tokio::test]
404 async fn test_handle_request_with_params() {
405 let mut adapter = RestAdapter::new();
406 adapter.route("GET", "/users/:id", "get_user");
407
408 let result = adapter.handle("GET /users/42").await;
409 assert!(result.is_ok());
410
411 let response = result.unwrap();
412 assert!(response.contains("HTTP 200"));
413 assert!(response.contains("get_user"));
414 assert!(response.contains("/users/42"));
415 }
416
417 #[test]
418 fn test_rest_route_new() {
419 let route = RestRoute::new("GET", "/users", "list_users");
420 assert_eq!(route.method, "GET");
421 assert_eq!(route.path, "/users");
422 assert_eq!(route.handler, "list_users");
423 }
424
425 #[test]
426 fn test_rest_route_matches() {
427 let route = RestRoute::new("GET", "/users", "list_users");
428 assert!(route.matches("GET", "/users"));
429 assert!(!route.matches("POST", "/users"));
430 assert!(!route.matches("GET", "/posts"));
431 }
432
433 #[test]
434 fn test_rest_route_matches_path_with_params() {
435 let route = RestRoute::new("GET", "/users/:id/posts/:post_id", "handler");
436
437 assert!(route.matches_path("/users/42/posts/100"));
438 assert!(route.matches_path("/users/123/posts/456"));
439 assert!(!route.matches_path("/users/42"));
440 assert!(!route.matches_path("/users/42/posts"));
441 assert!(!route.matches_path("/users/42/posts/100/extra"));
442 }
443}