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!(self, HttpMethod::GET | HttpMethod::HEAD | HttpMethod::OPTIONS | HttpMethod::TRACE)
34 }
35
36 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#[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 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 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
105 self.middleware = middleware;
106 self
107 }
108
109 pub fn add_middleware(mut self, middleware: impl Into<String>) -> Self {
111 self.middleware.push(middleware.into());
112 self
113 }
114
115 pub fn with_description(mut self, description: impl Into<String>) -> Self {
117 self.description = Some(description.into());
118 self
119 }
120
121 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
123 self.tags = tags;
124 self
125 }
126
127 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
129 self.tags.push(tag.into());
130 self
131 }
132
133 pub fn with_parameters(mut self, parameters: Vec<RouteParameter>) -> Self {
135 self.parameters = parameters;
136 self
137 }
138
139 pub fn add_parameter(mut self, parameter: RouteParameter) -> Self {
141 self.parameters.push(parameter);
142 self
143 }
144
145 pub fn path_pattern(&self) -> String {
147 self.path.clone()
148 }
149
150 pub fn matches(&self, method: &HttpMethod, path: &str) -> bool {
152 self.method == *method && self.matches_path(path)
153 }
154
155 fn matches_path(&self, path: &str) -> bool {
157 self.path == path
160 }
161}
162
163#[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 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 pub fn optional(mut self) -> Self {
187 self.required = false;
188 self
189 }
190
191 pub fn with_description(mut self, description: impl Into<String>) -> Self {
193 self.description = Some(description.into());
194 self
195 }
196
197 pub fn with_default(mut self, default: impl Into<String>) -> Self {
199 self.default_value = Some(default.into());
200 self.required = false; self
202 }
203}
204
205#[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 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#[derive(Debug, Clone)]
238pub struct MiddlewareDefinition {
239 pub name: String,
240 pub priority: i32, pub description: Option<String>,
242 pub enabled: bool,
243 pub config: std::collections::HashMap<String, String>,
244}
245
246impl MiddlewareDefinition {
247 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 pub fn with_description(mut self, description: impl Into<String>) -> Self {
260 self.description = Some(description.into());
261 self
262 }
263
264 pub fn enabled(mut self, enabled: bool) -> Self {
266 self.enabled = enabled;
267 self
268 }
269
270 pub fn with_config(mut self, config: std::collections::HashMap<String, String>) -> Self {
272 self.config = config;
273 self
274 }
275
276 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#[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 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 pub fn with_name(mut self, name: impl Into<String>) -> Self {
327 self.name = Some(name.into());
328 self
329 }
330
331 pub fn with_description(mut self, description: impl Into<String>) -> Self {
333 self.description = Some(description.into());
334 self
335 }
336
337 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
339 self.middleware = middleware;
340 self
341 }
342
343 pub fn with_routes(mut self, routes: Vec<RouteDefinition>) -> Self {
345 self.routes = routes;
346 self
347 }
348
349 pub fn add_route(mut self, route: RouteDefinition) -> Self {
351 self.routes.push(route);
352 self
353 }
354
355 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 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}