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