1#[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 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 pub fn is_safe(&self) -> bool {
33 matches!(
34 self,
35 HttpMethod::GET | HttpMethod::HEAD | HttpMethod::OPTIONS | HttpMethod::TRACE
36 )
37 }
38
39 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#[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 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 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
109 self.middleware = middleware;
110 self
111 }
112
113 pub fn add_middleware(mut self, middleware: impl Into<String>) -> Self {
115 self.middleware.push(middleware.into());
116 self
117 }
118
119 pub fn with_description(mut self, description: impl Into<String>) -> Self {
121 self.description = Some(description.into());
122 self
123 }
124
125 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
127 self.tags = tags;
128 self
129 }
130
131 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
133 self.tags.push(tag.into());
134 self
135 }
136
137 pub fn with_parameters(mut self, parameters: Vec<RouteParameter>) -> Self {
139 self.parameters = parameters;
140 self
141 }
142
143 pub fn add_parameter(mut self, parameter: RouteParameter) -> Self {
145 self.parameters.push(parameter);
146 self
147 }
148
149 pub fn path_pattern(&self) -> String {
151 self.path.clone()
152 }
153
154 pub fn matches(&self, method: &HttpMethod, path: &str) -> bool {
156 self.method == *method && self.matches_path(path)
157 }
158
159 fn matches_path(&self, path: &str) -> bool {
161 self.path == path
164 }
165}
166
167#[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 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 pub fn optional(mut self) -> Self {
191 self.required = false;
192 self
193 }
194
195 pub fn with_description(mut self, description: impl Into<String>) -> Self {
197 self.description = Some(description.into());
198 self
199 }
200
201 pub fn with_default(mut self, default: impl Into<String>) -> Self {
203 self.default_value = Some(default.into());
204 self.required = false; self
206 }
207}
208
209#[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 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#[derive(Debug, Clone)]
242pub struct MiddlewareDefinition {
243 pub name: String,
244 pub priority: i32, pub description: Option<String>,
246 pub enabled: bool,
247 pub config: std::collections::HashMap<String, String>,
248}
249
250impl MiddlewareDefinition {
251 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 pub fn with_description(mut self, description: impl Into<String>) -> Self {
264 self.description = Some(description.into());
265 self
266 }
267
268 pub fn enabled(mut self, enabled: bool) -> Self {
270 self.enabled = enabled;
271 self
272 }
273
274 pub fn with_config(mut self, config: std::collections::HashMap<String, String>) -> Self {
276 self.config = config;
277 self
278 }
279
280 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#[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 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 pub fn with_name(mut self, name: impl Into<String>) -> Self {
331 self.name = Some(name.into());
332 self
333 }
334
335 pub fn with_description(mut self, description: impl Into<String>) -> Self {
337 self.description = Some(description.into());
338 self
339 }
340
341 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
343 self.middleware = middleware;
344 self
345 }
346
347 pub fn with_routes(mut self, routes: Vec<RouteDefinition>) -> Self {
349 self.routes = routes;
350 self
351 }
352
353 pub fn add_route(mut self, route: RouteDefinition) -> Self {
355 self.routes.push(route);
356 self
357 }
358
359 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 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}