allframe_core/router/
rest.rs

1//! REST/HTTP protocol adapter
2//!
3//! Provides REST/HTTP support for the protocol-agnostic router.
4
5use std::{future::Future, pin::Pin};
6
7use super::ProtocolAdapter;
8
9/// REST route definition
10#[derive(Debug, Clone)]
11pub struct RestRoute {
12    /// HTTP method (GET, POST, etc.)
13    pub method: String,
14    /// URL path pattern (e.g., "/users/:id")
15    pub path: String,
16    /// Handler name to call
17    pub handler: String,
18}
19
20impl RestRoute {
21    /// Create a new REST route
22    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    /// Check if this route matches a given method and path
35    pub fn matches(&self, method: &str, path: &str) -> bool {
36        if self.method != method {
37            return false;
38        }
39
40        // Simple exact match for now (no path params yet)
41        self.path == path
42    }
43
44    /// Check if path matches with parameter support
45    pub fn matches_path(&self, path: &str) -> bool {
46        // Split both paths into segments
47        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        // Must have same number of segments
51        if route_segments.len() != path_segments.len() {
52            return false;
53        }
54
55        // Check each segment
56        for (route_seg, path_seg) in route_segments.iter().zip(path_segments.iter()) {
57            // If route segment is a parameter (starts with :), it matches anything
58            if route_seg.starts_with(':') {
59                continue;
60            }
61            // Otherwise must match exactly
62            if route_seg != path_seg {
63                return false;
64            }
65        }
66
67        true
68    }
69}
70
71/// REST adapter for HTTP requests
72///
73/// Handles REST/HTTP protocol-specific request/response transformation.
74pub struct RestAdapter {
75    routes: Vec<RestRoute>,
76}
77
78impl RestAdapter {
79    /// Create a new REST adapter
80    pub fn new() -> Self {
81        Self { routes: Vec::new() }
82    }
83
84    /// Register a REST route
85    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    /// Find a matching route for the given method and path
91    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    /// Parse an HTTP request string
98    ///
99    /// Format: "METHOD /path [body]"
100    /// Example: "GET /users", "POST /users {\"name\":\"John\"}"
101    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    /// Format an HTTP response
116    pub fn format_response(&self, status: u16, body: &str) -> String {
117        format!("HTTP {} {}", status, body)
118    }
119
120    /// Build a simulated HTTP request for testing
121    ///
122    /// In a real implementation, this would parse actual HTTP requests.
123    /// For MVP, we use a simple string-based representation.
124    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        // Parse the HTTP request before async block
154        let parse_result = self.parse_request(request);
155
156        // Clone routes for async block
157        let routes = self.routes.clone();
158
159        Box::pin(async move {
160            // Handle parse error
161            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            // Find matching route
170            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                    // In full implementation, would call handler here
177                    // For now, return success with handler name
178                    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                    // 404 Not Found
187                    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/// Simplified HTTP request representation
197///
198/// For MVP testing purposes. Full implementation will use proper HTTP types.
199#[derive(Debug, Clone)]
200pub struct RestRequest {
201    /// HTTP method (GET, POST, etc.)
202    pub method: String,
203    /// Request path
204    pub path: String,
205}
206
207/// Simplified HTTP response representation
208///
209/// For MVP testing purposes. Full implementation will use proper HTTP types.
210#[derive(Debug, Clone)]
211pub struct RestResponse {
212    status: u16,
213    body: String,
214}
215
216impl RestResponse {
217    /// Create a new response
218    pub fn new(status: u16, body: String) -> Self {
219        Self { status, body }
220    }
221
222    /// Get the HTTP status code
223    pub fn status(&self) -> u16 {
224        self.status
225    }
226
227    /// Get the response body
228    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        // Too few segments
310        let matched = adapter.match_route("GET", "/users");
311        assert!(matched.is_none());
312
313        // Too many segments
314        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}