1use serde::{Deserialize, Serialize};
4use std::collections::{BTreeMap, HashMap};
5
6use crate::SchemaRef;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "UPPERCASE")]
11pub enum HttpMethod {
12 Get,
13 Post,
14 Put,
15 Patch,
16 Delete,
17 Head,
18 Options,
19 Trace,
20}
21
22impl std::fmt::Display for HttpMethod {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Get => write!(f, "GET"),
26 Self::Post => write!(f, "POST"),
27 Self::Put => write!(f, "PUT"),
28 Self::Patch => write!(f, "PATCH"),
29 Self::Delete => write!(f, "DELETE"),
30 Self::Head => write!(f, "HEAD"),
31 Self::Options => write!(f, "OPTIONS"),
32 Self::Trace => write!(f, "TRACE"),
33 }
34 }
35}
36
37impl TryFrom<&str> for HttpMethod {
38 type Error = String;
39
40 fn try_from(value: &str) -> Result<Self, Self::Error> {
41 match value.to_uppercase().as_str() {
42 "GET" => Ok(Self::Get),
43 "POST" => Ok(Self::Post),
44 "PUT" => Ok(Self::Put),
45 "PATCH" => Ok(Self::Patch),
46 "DELETE" => Ok(Self::Delete),
47 "HEAD" => Ok(Self::Head),
48 "OPTIONS" => Ok(Self::Options),
49 "TRACE" => Ok(Self::Trace),
50 other => Err(format!("unknown HTTP method: {other}")),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58pub enum ParameterLocation {
59 Query,
60 Header,
61 Path,
62 Cookie,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct Parameter {
69 pub name: String,
71 pub r#in: ParameterLocation,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub description: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub required: Option<bool>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub schema: Option<SchemaRef>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub example: Option<serde_json::Value>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct RequestBody {
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub description: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub required: Option<bool>,
97 pub content: BTreeMap<String, MediaType>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct MediaType {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub schema: Option<SchemaRef>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub example: Option<serde_json::Value>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub examples: Option<HashMap<String, Example>>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct Example {
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub summary: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub description: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub value: Option<serde_json::Value>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct Response {
135 pub description: String,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub headers: Option<HashMap<String, Header>>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub content: Option<BTreeMap<String, MediaType>>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct Header {
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub description: Option<String>,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub schema: Option<SchemaRef>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct Operation {
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub operation_id: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub tags: Option<Vec<String>>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub summary: Option<String>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub description: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub parameters: Option<Vec<Parameter>>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub request_body: Option<RequestBody>,
179 pub responses: BTreeMap<String, Response>,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct PathItem {
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub get: Option<Operation>,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub post: Option<Operation>,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub put: Option<Operation>,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub patch: Option<Operation>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub delete: Option<Operation>,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub head: Option<Operation>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub options: Option<Operation>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub trace: Option<Operation>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub parameters: Option<Vec<Parameter>>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub summary: Option<String>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub description: Option<String>,
223}
224
225impl PathItem {
226 pub fn set_operation(&mut self, method: HttpMethod, operation: Operation) {
228 match method {
229 HttpMethod::Get => self.get = Some(operation),
230 HttpMethod::Post => self.post = Some(operation),
231 HttpMethod::Put => self.put = Some(operation),
232 HttpMethod::Patch => self.patch = Some(operation),
233 HttpMethod::Delete => self.delete = Some(operation),
234 HttpMethod::Head => self.head = Some(operation),
235 HttpMethod::Options => self.options = Some(operation),
236 HttpMethod::Trace => self.trace = Some(operation),
237 }
238 }
239
240 #[must_use]
242 pub const fn get_operation(&self, method: &HttpMethod) -> Option<&Operation> {
243 match method {
244 HttpMethod::Get => self.get.as_ref(),
245 HttpMethod::Post => self.post.as_ref(),
246 HttpMethod::Put => self.put.as_ref(),
247 HttpMethod::Patch => self.patch.as_ref(),
248 HttpMethod::Delete => self.delete.as_ref(),
249 HttpMethod::Head => self.head.as_ref(),
250 HttpMethod::Options => self.options.as_ref(),
251 HttpMethod::Trace => self.trace.as_ref(),
252 }
253 }
254}
255
256#[derive(Debug, Clone)]
258pub struct RouteInfo {
259 pub method: HttpMethod,
261 pub path: String,
263 pub operation: Operation,
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use rstest::rstest;
271
272 #[rstest]
273 #[case("GET", HttpMethod::Get)]
274 #[case("get", HttpMethod::Get)]
275 #[case("Get", HttpMethod::Get)]
276 #[case("POST", HttpMethod::Post)]
277 #[case("post", HttpMethod::Post)]
278 #[case("Post", HttpMethod::Post)]
279 #[case("PUT", HttpMethod::Put)]
280 #[case("put", HttpMethod::Put)]
281 #[case("Put", HttpMethod::Put)]
282 #[case("PATCH", HttpMethod::Patch)]
283 #[case("patch", HttpMethod::Patch)]
284 #[case("Patch", HttpMethod::Patch)]
285 #[case("DELETE", HttpMethod::Delete)]
286 #[case("delete", HttpMethod::Delete)]
287 #[case("Delete", HttpMethod::Delete)]
288 #[case("HEAD", HttpMethod::Head)]
289 #[case("head", HttpMethod::Head)]
290 #[case("Head", HttpMethod::Head)]
291 #[case("OPTIONS", HttpMethod::Options)]
292 #[case("options", HttpMethod::Options)]
293 #[case("Options", HttpMethod::Options)]
294 #[case("TRACE", HttpMethod::Trace)]
295 #[case("trace", HttpMethod::Trace)]
296 #[case("Trace", HttpMethod::Trace)]
297 fn test_http_method_from_str(#[case] input: &str, #[case] expected: HttpMethod) {
298 let result = HttpMethod::try_from(input).unwrap();
299 assert_eq!(result, expected);
300 }
301
302 #[test]
303 fn test_http_method_from_invalid_str() {
304 let result = HttpMethod::try_from("INVALID");
305 assert!(result.is_err());
306 }
307
308 #[test]
309 fn test_http_method_serialization() {
310 let method = HttpMethod::Get;
312 let serialized = serde_json::to_string(&method).unwrap();
313 assert_eq!(serialized, "\"GET\"");
314
315 let method = HttpMethod::Post;
316 let serialized = serde_json::to_string(&method).unwrap();
317 assert_eq!(serialized, "\"POST\"");
318
319 let method = HttpMethod::Delete;
320 let serialized = serde_json::to_string(&method).unwrap();
321 assert_eq!(serialized, "\"DELETE\"");
322 }
323
324 #[test]
325 fn test_http_method_deserialization() {
326 let method: HttpMethod = serde_json::from_str("\"GET\"").unwrap();
328 assert_eq!(method, HttpMethod::Get);
329
330 let method: HttpMethod = serde_json::from_str("\"POST\"").unwrap();
331 assert_eq!(method, HttpMethod::Post);
332
333 let method: HttpMethod = serde_json::from_str("\"DELETE\"").unwrap();
334 assert_eq!(method, HttpMethod::Delete);
335 }
336
337 #[test]
338 fn test_path_item_set_operation() {
339 let mut path_item = PathItem {
340 get: None,
341 post: None,
342 put: None,
343 patch: None,
344 delete: None,
345 head: None,
346 options: None,
347 trace: None,
348 parameters: None,
349 summary: None,
350 description: None,
351 };
352
353 let operation = Operation {
354 operation_id: Some("test_operation".to_string()),
355 tags: None,
356 summary: None,
357 description: None,
358 parameters: None,
359 request_body: None,
360 responses: BTreeMap::new(),
361 security: None,
362 };
363
364 path_item.set_operation(HttpMethod::Get, operation.clone());
366 assert!(path_item.get.is_some());
367 assert_eq!(
368 path_item.get.as_ref().unwrap().operation_id,
369 Some("test_operation".to_string())
370 );
371
372 let mut operation_post = operation.clone();
374 operation_post.operation_id = Some("post_operation".to_string());
375 path_item.set_operation(HttpMethod::Post, operation_post);
376 assert!(path_item.post.is_some());
377 assert_eq!(
378 path_item.post.as_ref().unwrap().operation_id,
379 Some("post_operation".to_string())
380 );
381
382 let mut operation_put = operation.clone();
384 operation_put.operation_id = Some("put_operation".to_string());
385 path_item.set_operation(HttpMethod::Put, operation_put);
386 assert!(path_item.put.is_some());
387
388 let mut operation_patch = operation.clone();
390 operation_patch.operation_id = Some("patch_operation".to_string());
391 path_item.set_operation(HttpMethod::Patch, operation_patch);
392 assert!(path_item.patch.is_some());
393
394 let mut operation_delete = operation.clone();
396 operation_delete.operation_id = Some("delete_operation".to_string());
397 path_item.set_operation(HttpMethod::Delete, operation_delete);
398 assert!(path_item.delete.is_some());
399
400 let mut operation_head = operation.clone();
402 operation_head.operation_id = Some("head_operation".to_string());
403 path_item.set_operation(HttpMethod::Head, operation_head);
404 assert!(path_item.head.is_some());
405
406 let mut operation_options = operation.clone();
408 operation_options.operation_id = Some("options_operation".to_string());
409 path_item.set_operation(HttpMethod::Options, operation_options);
410 assert!(path_item.options.is_some());
411
412 let mut operation_trace = operation;
414 operation_trace.operation_id = Some("trace_operation".to_string());
415 path_item.set_operation(HttpMethod::Trace, operation_trace);
416 assert!(path_item.trace.is_some());
417 }
418
419 #[test]
420 fn test_path_item_get_operation() {
421 let mut path_item = PathItem {
422 get: None,
423 post: None,
424 put: None,
425 patch: None,
426 delete: None,
427 head: None,
428 options: None,
429 trace: None,
430 parameters: None,
431 summary: None,
432 description: None,
433 };
434
435 let operation = Operation {
436 operation_id: Some("test_operation".to_string()),
437 tags: None,
438 summary: None,
439 description: None,
440 parameters: None,
441 request_body: None,
442 responses: BTreeMap::new(),
443 security: None,
444 };
445
446 assert!(path_item.get_operation(&HttpMethod::Get).is_none());
448 assert!(path_item.get_operation(&HttpMethod::Post).is_none());
449
450 path_item.set_operation(HttpMethod::Get, operation.clone());
452 let retrieved = path_item.get_operation(&HttpMethod::Get);
453 assert!(retrieved.is_some());
454 assert_eq!(
455 retrieved.unwrap().operation_id,
456 Some("test_operation".to_string())
457 );
458
459 let mut operation_post = operation.clone();
461 operation_post.operation_id = Some("post_operation".to_string());
462 path_item.set_operation(HttpMethod::Post, operation_post);
463 let retrieved = path_item.get_operation(&HttpMethod::Post);
464 assert!(retrieved.is_some());
465 assert_eq!(
466 retrieved.unwrap().operation_id,
467 Some("post_operation".to_string())
468 );
469
470 path_item.set_operation(HttpMethod::Put, operation.clone());
472 assert!(path_item.get_operation(&HttpMethod::Put).is_some());
473
474 path_item.set_operation(HttpMethod::Patch, operation.clone());
475 assert!(path_item.get_operation(&HttpMethod::Patch).is_some());
476
477 path_item.set_operation(HttpMethod::Delete, operation.clone());
478 assert!(path_item.get_operation(&HttpMethod::Delete).is_some());
479
480 path_item.set_operation(HttpMethod::Head, operation.clone());
481 assert!(path_item.get_operation(&HttpMethod::Head).is_some());
482
483 path_item.set_operation(HttpMethod::Options, operation.clone());
484 assert!(path_item.get_operation(&HttpMethod::Options).is_some());
485
486 path_item.set_operation(HttpMethod::Trace, operation);
487 assert!(path_item.get_operation(&HttpMethod::Trace).is_some());
488 }
489
490 #[test]
491 fn test_path_item_set_operation_overwrites() {
492 let mut path_item = PathItem {
493 get: None,
494 post: None,
495 put: None,
496 patch: None,
497 delete: None,
498 head: None,
499 options: None,
500 trace: None,
501 parameters: None,
502 summary: None,
503 description: None,
504 };
505
506 let operation1 = Operation {
507 operation_id: Some("first".to_string()),
508 tags: None,
509 summary: None,
510 description: None,
511 parameters: None,
512 request_body: None,
513 responses: BTreeMap::new(),
514 security: None,
515 };
516
517 let operation2 = Operation {
518 operation_id: Some("second".to_string()),
519 tags: None,
520 summary: None,
521 description: None,
522 parameters: None,
523 request_body: None,
524 responses: BTreeMap::new(),
525 security: None,
526 };
527
528 path_item.set_operation(HttpMethod::Get, operation1);
530 assert_eq!(
531 path_item.get.as_ref().unwrap().operation_id,
532 Some("first".to_string())
533 );
534
535 path_item.set_operation(HttpMethod::Get, operation2);
537 assert_eq!(
538 path_item.get.as_ref().unwrap().operation_id,
539 Some("second".to_string())
540 );
541 }
542
543 #[rstest]
544 #[case(HttpMethod::Get, "GET")]
545 #[case(HttpMethod::Post, "POST")]
546 #[case(HttpMethod::Put, "PUT")]
547 #[case(HttpMethod::Patch, "PATCH")]
548 #[case(HttpMethod::Delete, "DELETE")]
549 #[case(HttpMethod::Head, "HEAD")]
550 #[case(HttpMethod::Options, "OPTIONS")]
551 #[case(HttpMethod::Trace, "TRACE")]
552 fn test_http_method_display(#[case] method: HttpMethod, #[case] expected: &str) {
553 assert_eq!(method.to_string(), expected);
554 }
555
556 #[test]
557 fn test_http_method_equality() {
558 let method1 = HttpMethod::Get;
559 let method2 = HttpMethod::Get;
560 let method3 = HttpMethod::Post;
561
562 assert_eq!(method1, method2);
563 assert_ne!(method1, method3);
564 }
565
566 #[test]
567 fn test_http_method_clone() {
568 let method = HttpMethod::Get;
569 let cloned = method;
570 assert_eq!(method, cloned);
571 }
572
573 #[test]
574 fn test_http_method_hash() {
575 use std::collections::HashMap;
576
577 let mut map = HashMap::new();
578 map.insert(HttpMethod::Get, "GET method");
579 map.insert(HttpMethod::Post, "POST method");
580
581 assert_eq!(map.get(&HttpMethod::Get), Some(&"GET method"));
582 assert_eq!(map.get(&HttpMethod::Post), Some(&"POST method"));
583 assert_eq!(map.get(&HttpMethod::Put), None);
584 }
585}