elif_core/modules/
routing.rs

1/// HTTP method enumeration for route definitions
2#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3pub enum HttpMethod {
4    GET,
5    POST,
6    PUT,
7    PATCH,
8    DELETE,
9    OPTIONS,
10    HEAD,
11    CONNECT,
12    TRACE,
13}
14
15impl HttpMethod {
16    /// Get the method as a string
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            HttpMethod::GET => "GET",
20            HttpMethod::POST => "POST",
21            HttpMethod::PUT => "PUT",
22            HttpMethod::PATCH => "PATCH",
23            HttpMethod::DELETE => "DELETE",
24            HttpMethod::OPTIONS => "OPTIONS",
25            HttpMethod::HEAD => "HEAD",
26            HttpMethod::CONNECT => "CONNECT",
27            HttpMethod::TRACE => "TRACE",
28        }
29    }
30    
31    /// Check if method is safe (no side effects)
32    pub fn is_safe(&self) -> bool {
33        matches!(self, HttpMethod::GET | HttpMethod::HEAD | HttpMethod::OPTIONS | HttpMethod::TRACE)
34    }
35    
36    /// Check if method is idempotent
37    pub fn is_idempotent(&self) -> bool {
38        matches!(
39            self,
40            HttpMethod::GET
41                | HttpMethod::HEAD
42                | HttpMethod::PUT
43                | HttpMethod::DELETE
44                | HttpMethod::OPTIONS
45                | HttpMethod::TRACE
46        )
47    }
48}
49
50impl std::fmt::Display for HttpMethod {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "{}", self.as_str())
53    }
54}
55
56impl std::str::FromStr for HttpMethod {
57    type Err = crate::errors::CoreError;
58    
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        match s.to_uppercase().as_str() {
61            "GET" => Ok(HttpMethod::GET),
62            "POST" => Ok(HttpMethod::POST),
63            "PUT" => Ok(HttpMethod::PUT),
64            "PATCH" => Ok(HttpMethod::PATCH),
65            "DELETE" => Ok(HttpMethod::DELETE),
66            "OPTIONS" => Ok(HttpMethod::OPTIONS),
67            "HEAD" => Ok(HttpMethod::HEAD),
68            "CONNECT" => Ok(HttpMethod::CONNECT),
69            "TRACE" => Ok(HttpMethod::TRACE),
70            _ => Err(crate::errors::CoreError::validation(
71                format!("Invalid HTTP method: {}", s)
72            )),
73        }
74    }
75}
76
77/// Route definition for module routing
78#[derive(Debug, Clone)]
79pub struct RouteDefinition {
80    pub method: HttpMethod,
81    pub path: String,
82    pub handler: String,
83    pub middleware: Vec<String>,
84    pub description: Option<String>,
85    pub tags: Vec<String>,
86    pub parameters: Vec<RouteParameter>,
87}
88
89impl RouteDefinition {
90    /// Create a new route definition
91    pub fn new(method: HttpMethod, path: impl Into<String>, handler: impl Into<String>) -> Self {
92        Self {
93            method,
94            path: path.into(),
95            handler: handler.into(),
96            middleware: Vec::new(),
97            description: None,
98            tags: Vec::new(),
99            parameters: Vec::new(),
100        }
101    }
102    
103    /// Add middleware to the route
104    pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
105        self.middleware = middleware;
106        self
107    }
108    
109    /// Add a single middleware
110    pub fn add_middleware(mut self, middleware: impl Into<String>) -> Self {
111        self.middleware.push(middleware.into());
112        self
113    }
114    
115    /// Set route description
116    pub fn with_description(mut self, description: impl Into<String>) -> Self {
117        self.description = Some(description.into());
118        self
119    }
120    
121    /// Add tags to the route
122    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
123        self.tags = tags;
124        self
125    }
126    
127    /// Add a single tag
128    pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
129        self.tags.push(tag.into());
130        self
131    }
132    
133    /// Add parameters to the route
134    pub fn with_parameters(mut self, parameters: Vec<RouteParameter>) -> Self {
135        self.parameters = parameters;
136        self
137    }
138    
139    /// Add a single parameter
140    pub fn add_parameter(mut self, parameter: RouteParameter) -> Self {
141        self.parameters.push(parameter);
142        self
143    }
144    
145    /// Get route path with parameter placeholders
146    pub fn path_pattern(&self) -> String {
147        self.path.clone()
148    }
149    
150    /// Check if route matches a given path and method
151    pub fn matches(&self, method: &HttpMethod, path: &str) -> bool {
152        self.method == *method && self.matches_path(path)
153    }
154    
155    /// Check if route path matches (basic implementation)
156    fn matches_path(&self, path: &str) -> bool {
157        // This is a simplified implementation
158        // In a real router, you would implement parameter matching
159        self.path == path
160    }
161}
162
163/// Route parameter definition
164#[derive(Debug, Clone)]
165pub struct RouteParameter {
166    pub name: String,
167    pub parameter_type: ParameterType,
168    pub required: bool,
169    pub description: Option<String>,
170    pub default_value: Option<String>,
171}
172
173impl RouteParameter {
174    /// Create a new route parameter
175    pub fn new(name: impl Into<String>, parameter_type: ParameterType) -> Self {
176        Self {
177            name: name.into(),
178            parameter_type,
179            required: true,
180            description: None,
181            default_value: None,
182        }
183    }
184    
185    /// Make parameter optional
186    pub fn optional(mut self) -> Self {
187        self.required = false;
188        self
189    }
190    
191    /// Set parameter description
192    pub fn with_description(mut self, description: impl Into<String>) -> Self {
193        self.description = Some(description.into());
194        self
195    }
196    
197    /// Set default value
198    pub fn with_default(mut self, default: impl Into<String>) -> Self {
199        self.default_value = Some(default.into());
200        self.required = false; // Default implies optional
201        self
202    }
203}
204
205/// Parameter type enumeration
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum ParameterType {
208    String,
209    Integer,
210    Float,
211    Boolean,
212    Uuid,
213    Path,
214    Query,
215    Header,
216    Body,
217}
218
219impl ParameterType {
220    /// Get parameter type as string
221    pub fn as_str(&self) -> &'static str {
222        match self {
223            ParameterType::String => "string",
224            ParameterType::Integer => "integer",
225            ParameterType::Float => "float",
226            ParameterType::Boolean => "boolean",
227            ParameterType::Uuid => "uuid",
228            ParameterType::Path => "path",
229            ParameterType::Query => "query",
230            ParameterType::Header => "header",
231            ParameterType::Body => "body",
232        }
233    }
234}
235
236/// Middleware definition for module middleware
237#[derive(Debug, Clone)]
238pub struct MiddlewareDefinition {
239    pub name: String,
240    pub priority: i32, // Lower numbers = higher priority (executed first)
241    pub description: Option<String>,
242    pub enabled: bool,
243    pub config: std::collections::HashMap<String, String>,
244}
245
246impl MiddlewareDefinition {
247    /// Create a new middleware definition
248    pub fn new(name: impl Into<String>, priority: i32) -> Self {
249        Self {
250            name: name.into(),
251            priority,
252            description: None,
253            enabled: true,
254            config: std::collections::HashMap::new(),
255        }
256    }
257    
258    /// Set middleware description
259    pub fn with_description(mut self, description: impl Into<String>) -> Self {
260        self.description = Some(description.into());
261        self
262    }
263    
264    /// Set middleware enabled status
265    pub fn enabled(mut self, enabled: bool) -> Self {
266        self.enabled = enabled;
267        self
268    }
269    
270    /// Add configuration to middleware
271    pub fn with_config(mut self, config: std::collections::HashMap<String, String>) -> Self {
272        self.config = config;
273        self
274    }
275    
276    /// Add a single configuration value
277    pub fn add_config(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
278        self.config.insert(key.into(), value.into());
279        self
280    }
281}
282
283impl PartialOrd for MiddlewareDefinition {
284    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
285        Some(self.cmp(other))
286    }
287}
288
289impl Ord for MiddlewareDefinition {
290    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
291        self.priority.cmp(&other.priority)
292    }
293}
294
295impl PartialEq for MiddlewareDefinition {
296    fn eq(&self, other: &Self) -> bool {
297        self.name == other.name && self.priority == other.priority
298    }
299}
300
301impl Eq for MiddlewareDefinition {}
302
303/// Route group for organizing related routes
304#[derive(Debug, Clone)]
305pub struct RouteGroup {
306    pub prefix: String,
307    pub middleware: Vec<String>,
308    pub routes: Vec<RouteDefinition>,
309    pub name: Option<String>,
310    pub description: Option<String>,
311}
312
313impl RouteGroup {
314    /// Create a new route group
315    pub fn new(prefix: impl Into<String>) -> Self {
316        Self {
317            prefix: prefix.into(),
318            middleware: Vec::new(),
319            routes: Vec::new(),
320            name: None,
321            description: None,
322        }
323    }
324    
325    /// Set group name
326    pub fn with_name(mut self, name: impl Into<String>) -> Self {
327        self.name = Some(name.into());
328        self
329    }
330    
331    /// Set group description
332    pub fn with_description(mut self, description: impl Into<String>) -> Self {
333        self.description = Some(description.into());
334        self
335    }
336    
337    /// Add middleware to the group
338    pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
339        self.middleware = middleware;
340        self
341    }
342    
343    /// Add routes to the group
344    pub fn with_routes(mut self, routes: Vec<RouteDefinition>) -> Self {
345        self.routes = routes;
346        self
347    }
348    
349    /// Add a single route
350    pub fn add_route(mut self, route: RouteDefinition) -> Self {
351        self.routes.push(route);
352        self
353    }
354    
355    /// Get all routes with prefix applied
356    pub fn prefixed_routes(&self) -> Vec<RouteDefinition> {
357        self.routes
358            .iter()
359            .map(|route| {
360                let mut prefixed_route = route.clone();
361                prefixed_route.path = format!("{}{}", self.prefix, route.path);
362                // Add group middleware to route middleware
363                let mut middleware = self.middleware.clone();
364                middleware.extend(route.middleware.clone());
365                prefixed_route.middleware = middleware;
366                prefixed_route
367            })
368            .collect()
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    
376    #[test]
377    fn test_http_method() {
378        assert_eq!(HttpMethod::GET.as_str(), "GET");
379        assert!(HttpMethod::GET.is_safe());
380        assert!(HttpMethod::GET.is_idempotent());
381        assert!(!HttpMethod::POST.is_safe());
382        assert!(!HttpMethod::POST.is_idempotent());
383        
384        assert_eq!("GET".parse::<HttpMethod>().unwrap(), HttpMethod::GET);
385        assert!("INVALID".parse::<HttpMethod>().is_err());
386    }
387    
388    #[test]
389    fn test_route_definition() {
390        let route = RouteDefinition::new(HttpMethod::GET, "/users/{id}", "get_user")
391            .with_description("Get user by ID")
392            .add_middleware("auth")
393            .add_tag("users");
394        
395        assert_eq!(route.method, HttpMethod::GET);
396        assert_eq!(route.path, "/users/{id}");
397        assert_eq!(route.handler, "get_user");
398        assert_eq!(route.middleware, vec!["auth"]);
399        assert_eq!(route.tags, vec!["users"]);
400        assert!(route.matches(&HttpMethod::GET, "/users/{id}"));
401        assert!(!route.matches(&HttpMethod::POST, "/users/{id}"));
402    }
403    
404    #[test]
405    fn test_route_group() {
406        let group = RouteGroup::new("/api/v1")
407            .with_name("API v1")
408            .add_route(RouteDefinition::new(HttpMethod::GET, "/users", "list_users"))
409            .add_route(RouteDefinition::new(HttpMethod::POST, "/users", "create_user"));
410        
411        let prefixed_routes = group.prefixed_routes();
412        assert_eq!(prefixed_routes.len(), 2);
413        assert_eq!(prefixed_routes[0].path, "/api/v1/users");
414        assert_eq!(prefixed_routes[1].path, "/api/v1/users");
415    }
416}