1use indexmap::IndexMap;
5use serde_derive::{Deserialize, Serialize};
6use serde_json::Value;
7
8use super::extensions::Extensions;
9use super::request_body::RequestBody;
10use super::response::{Response, Responses};
11use super::security::SecurityRequirement;
12use super::{Deprecated, ExternalDocs, RefOr, Schema, Server};
13
14#[non_exhaustive]
21#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
22#[cfg_attr(feature = "debug", derive(Debug))]
23#[builder(on(_, into))]
24pub struct Paths {
25 #[serde(flatten)]
28 #[builder(field)]
29 pub paths: IndexMap<String, PathItem>,
30
31 #[serde(skip_serializing_if = "Option::is_none", flatten)]
33 pub extensions: Option<Extensions>,
34}
35
36impl Paths {
37 pub fn new() -> Self {
39 Default::default()
40 }
41
42 pub fn get_path_item<P: AsRef<str>>(&self, path: P) -> Option<&PathItem> {
54 self.paths.get(path.as_ref())
55 }
56
57 pub fn get_path_operation<P: AsRef<str>>(&self, path: P, http_method: HttpMethod) -> Option<&Operation> {
71 self.paths.get(path.as_ref()).and_then(|path| match http_method {
72 HttpMethod::Get => path.get.as_ref(),
73 HttpMethod::Put => path.put.as_ref(),
74 HttpMethod::Post => path.post.as_ref(),
75 HttpMethod::Delete => path.delete.as_ref(),
76 HttpMethod::Options => path.options.as_ref(),
77 HttpMethod::Head => path.head.as_ref(),
78 HttpMethod::Patch => path.patch.as_ref(),
79 HttpMethod::Trace => path.trace.as_ref(),
80 })
81 }
82
83 pub fn add_path_operation<P: AsRef<str>, O: Into<Operation>>(
91 &mut self,
92 path: P,
93 http_methods: Vec<HttpMethod>,
94 operation: O,
95 ) {
96 let path = path.as_ref();
97 let operation = operation.into();
98 if let Some(existing_item) = self.paths.get_mut(path) {
99 for http_method in http_methods {
100 match http_method {
101 HttpMethod::Get => existing_item.get = Some(operation.clone()),
102 HttpMethod::Put => existing_item.put = Some(operation.clone()),
103 HttpMethod::Post => existing_item.post = Some(operation.clone()),
104 HttpMethod::Delete => existing_item.delete = Some(operation.clone()),
105 HttpMethod::Options => existing_item.options = Some(operation.clone()),
106 HttpMethod::Head => existing_item.head = Some(operation.clone()),
107 HttpMethod::Patch => existing_item.patch = Some(operation.clone()),
108 HttpMethod::Trace => existing_item.trace = Some(operation.clone()),
109 };
110 }
111 } else {
112 self.paths
113 .insert(String::from(path), PathItem::from_http_methods(http_methods, operation));
114 }
115 }
116
117 pub fn merge(&mut self, other_paths: Paths) {
121 for (path, that) in other_paths.paths {
122 if let Some(this) = self.paths.get_mut(&path) {
123 this.merge_operations(that);
124 } else {
125 self.paths.insert(path, that);
126 }
127 }
128
129 if let Some(other_paths_extensions) = other_paths.extensions {
130 let paths_extensions = self.extensions.get_or_insert(Extensions::default());
131 paths_extensions.merge(other_paths_extensions);
132 }
133 }
134}
135
136impl<S: paths_builder::State> PathsBuilder<S> {
137 pub fn path(mut self, path: impl Into<String>, item: impl Into<PathItem>) -> Self {
140 let path_string = path.into();
141 let item = item.into();
142 if let Some(existing_item) = self.paths.get_mut(&path_string) {
143 existing_item.merge_operations(item);
144 } else {
145 self.paths.insert(path_string, item);
146 }
147
148 self
149 }
150
151 pub fn paths<I: Into<String>, P: Into<PathItem>>(self, items: impl IntoIterator<Item = (I, P)>) -> Self {
154 items.into_iter().fold(self, |this, (i, p)| this.path(i, p))
155 }
156}
157
158impl<S: paths_builder::IsComplete> From<PathsBuilder<S>> for Paths {
159 fn from(builder: PathsBuilder<S>) -> Self {
160 builder.build()
161 }
162}
163
164#[non_exhaustive]
169#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
170#[cfg_attr(feature = "debug", derive(Debug))]
171#[serde(rename_all = "camelCase")]
172#[builder(on(_, into))]
173pub struct PathItem {
174 #[serde(skip_serializing_if = "Option::is_none", default)]
176 pub summary: Option<String>,
177
178 #[serde(skip_serializing_if = "Option::is_none", default)]
181 pub description: Option<String>,
182
183 #[serde(skip_serializing_if = "Option::is_none", default)]
186 pub servers: Option<Vec<Server>>,
187
188 #[serde(skip_serializing_if = "Option::is_none", default)]
192 pub parameters: Option<Vec<Parameter>>,
193
194 #[serde(skip_serializing_if = "Option::is_none", default)]
196 pub get: Option<Operation>,
197
198 #[serde(skip_serializing_if = "Option::is_none", default)]
200 pub put: Option<Operation>,
201
202 #[serde(skip_serializing_if = "Option::is_none", default)]
204 pub post: Option<Operation>,
205
206 #[serde(skip_serializing_if = "Option::is_none", default)]
208 pub delete: Option<Operation>,
209
210 #[serde(skip_serializing_if = "Option::is_none", default)]
212 pub options: Option<Operation>,
213
214 #[serde(skip_serializing_if = "Option::is_none", default)]
216 pub head: Option<Operation>,
217
218 #[serde(skip_serializing_if = "Option::is_none", default)]
220 pub patch: Option<Operation>,
221
222 #[serde(skip_serializing_if = "Option::is_none", default)]
224 pub trace: Option<Operation>,
225
226 #[serde(skip_serializing_if = "Option::is_none", flatten)]
228 pub extensions: Option<Extensions>,
229}
230
231impl<S: path_item_builder::IsComplete> From<PathItemBuilder<S>> for PathItem {
232 fn from(builder: PathItemBuilder<S>) -> Self {
233 builder.build()
234 }
235}
236
237impl PathItem {
238 pub fn new<O: Into<Operation>>(http_method: HttpMethod, operation: O) -> Self {
240 let mut path_item = Self::default();
241 match http_method {
242 HttpMethod::Get => path_item.get = Some(operation.into()),
243 HttpMethod::Put => path_item.put = Some(operation.into()),
244 HttpMethod::Post => path_item.post = Some(operation.into()),
245 HttpMethod::Delete => path_item.delete = Some(operation.into()),
246 HttpMethod::Options => path_item.options = Some(operation.into()),
247 HttpMethod::Head => path_item.head = Some(operation.into()),
248 HttpMethod::Patch => path_item.patch = Some(operation.into()),
249 HttpMethod::Trace => path_item.trace = Some(operation.into()),
250 };
251
252 path_item
253 }
254
255 pub fn from_http_methods<I: IntoIterator<Item = HttpMethod>, O: Into<Operation>>(http_methods: I, operation: O) -> Self {
257 let mut path_item = Self::default();
258 let operation = operation.into();
259 for method in http_methods {
260 match method {
261 HttpMethod::Get => path_item.get = Some(operation.clone()),
262 HttpMethod::Put => path_item.put = Some(operation.clone()),
263 HttpMethod::Post => path_item.post = Some(operation.clone()),
264 HttpMethod::Delete => path_item.delete = Some(operation.clone()),
265 HttpMethod::Options => path_item.options = Some(operation.clone()),
266 HttpMethod::Head => path_item.head = Some(operation.clone()),
267 HttpMethod::Patch => path_item.patch = Some(operation.clone()),
268 HttpMethod::Trace => path_item.trace = Some(operation.clone()),
269 };
270 }
271
272 path_item
273 }
274
275 pub fn merge_operations(&mut self, path_item: PathItem) {
278 if path_item.get.is_some() && self.get.is_none() {
279 self.get = path_item.get;
280 }
281 if path_item.put.is_some() && self.put.is_none() {
282 self.put = path_item.put;
283 }
284 if path_item.post.is_some() && self.post.is_none() {
285 self.post = path_item.post;
286 }
287 if path_item.delete.is_some() && self.delete.is_none() {
288 self.delete = path_item.delete;
289 }
290 if path_item.options.is_some() && self.options.is_none() {
291 self.options = path_item.options;
292 }
293 if path_item.head.is_some() && self.head.is_none() {
294 self.head = path_item.head;
295 }
296 if path_item.patch.is_some() && self.patch.is_none() {
297 self.patch = path_item.patch;
298 }
299 if path_item.trace.is_some() && self.trace.is_none() {
300 self.trace = path_item.trace;
301 }
302 }
303}
304
305#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
309#[serde(rename_all = "lowercase")]
310#[cfg_attr(feature = "debug", derive(Debug))]
311pub enum HttpMethod {
312 Get,
314 Post,
316 Put,
318 Delete,
320 Options,
322 Head,
324 Patch,
326 Trace,
328}
329
330impl HttpMethod {
331 pub const fn as_str(&self) -> &str {
333 match self {
334 Self::Get => "get",
335 Self::Post => "post",
336 Self::Put => "put",
337 Self::Delete => "delete",
338 Self::Options => "options",
339 Self::Head => "head",
340 Self::Patch => "patch",
341 Self::Trace => "trace",
342 }
343 }
344}
345
346impl std::fmt::Display for HttpMethod {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 f.write_str(self.as_str())
349 }
350}
351
352#[non_exhaustive]
356#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
357#[cfg_attr(feature = "debug", derive(Debug))]
358#[serde(rename_all = "camelCase")]
359#[builder(on(_, into))]
360pub struct Operation {
361 #[serde(skip_serializing_if = "Option::is_none", default)]
363 #[builder(field)]
364 pub tags: Option<Vec<String>>,
365
366 #[serde(skip_serializing_if = "Option::is_none", default)]
368 #[builder(field)]
369 pub parameters: Option<Vec<Parameter>>,
370
371 #[builder(field)]
373 pub responses: Responses,
374
375 #[serde(skip_serializing_if = "Option::is_none", default)]
377 #[builder(field)]
378 pub servers: Option<Vec<Server>>,
379
380 #[serde(skip_serializing_if = "Option::is_none", default)]
386 #[builder(field)]
387 pub security: Option<Vec<SecurityRequirement>>,
388
389 #[serde(skip_serializing_if = "Option::is_none", default)]
391 pub summary: Option<String>,
392
393 #[serde(skip_serializing_if = "Option::is_none", default)]
395 pub description: Option<String>,
396
397 #[serde(skip_serializing_if = "Option::is_none", default)]
399 pub operation_id: Option<String>,
400
401 #[serde(skip_serializing_if = "Option::is_none", default)]
403 pub external_docs: Option<ExternalDocs>,
404
405 #[serde(skip_serializing_if = "Option::is_none", default)]
407 pub request_body: Option<RequestBody>,
408
409 #[allow(missing_docs)]
411 #[serde(skip_serializing_if = "Option::is_none", default)]
412 pub callbacks: Option<String>,
413
414 #[serde(skip_serializing_if = "Option::is_none", default)]
416 pub deprecated: Option<Deprecated>,
417
418 #[serde(skip_serializing_if = "Option::is_none", default, flatten)]
420 pub extensions: Option<Extensions>,
421}
422
423impl<S: operation_builder::IsComplete> From<OperationBuilder<S>> for Operation {
424 fn from(builder: OperationBuilder<S>) -> Self {
425 builder.build()
426 }
427}
428
429impl Operation {
430 pub fn new() -> Self {
432 Default::default()
433 }
434}
435
436impl<S: operation_builder::State> OperationBuilder<S> {
437 pub fn tag(mut self, tag: impl Into<String>) -> Self {
439 self.tags.get_or_insert_default().push(tag.into());
440 self
441 }
442
443 pub fn tags<T: Into<String>>(self, tags: impl IntoIterator<Item = T>) -> Self {
445 tags.into_iter().fold(self, |this, t| this.tag(t))
446 }
447
448 pub fn parameters<P: Into<Parameter>>(self, parameters: impl IntoIterator<Item = P>) -> Self {
450 parameters.into_iter().fold(self, |this, p| this.parameter(p))
451 }
452
453 pub fn parameter(mut self, parameter: impl Into<Parameter>) -> Self {
455 self.parameters.get_or_insert_default().push(parameter.into());
456 self
457 }
458
459 pub fn responses<R: Into<RefOr<Response>>, C: Into<String>>(self, responses: impl IntoIterator<Item = (C, R)>) -> Self {
461 responses.into_iter().fold(self, |this, (c, r)| this.response(c, r))
462 }
463
464 pub fn response(mut self, code: impl Into<String>, response: impl Into<RefOr<Response>>) -> Self {
469 self.responses.responses.insert(code.into(), response.into());
470
471 self
472 }
473
474 pub fn security(mut self, security: impl Into<SecurityRequirement>) -> Self {
476 self.security.get_or_insert_default().push(security.into());
477 self
478 }
479
480 pub fn securities<R: Into<SecurityRequirement>>(self, securities: impl IntoIterator<Item = R>) -> Self {
482 securities.into_iter().fold(self, |this, s| this.security(s))
483 }
484
485 pub fn servers<E: Into<Server>>(self, servers: impl IntoIterator<Item = E>) -> Self {
487 servers.into_iter().fold(self, |this, e| this.server(e))
488 }
489
490 pub fn server(mut self, server: impl Into<Server>) -> Self {
492 self.servers.get_or_insert_default().push(server.into());
493 self
494 }
495}
496
497impl Operation {
498 pub fn tag(&mut self, tag: impl Into<String>) -> &mut Self {
500 self.tags.get_or_insert_default().push(tag.into());
501 self
502 }
503
504 pub fn tags<T: Into<String>>(&mut self, tags: impl IntoIterator<Item = T>) -> &mut Self {
506 tags.into_iter().fold(self, |this, t| this.tag(t))
507 }
508
509 pub fn parameters<P: Into<Parameter>>(&mut self, parameters: impl IntoIterator<Item = P>) -> &mut Self {
511 parameters.into_iter().fold(self, |this, p| this.parameter(p))
512 }
513
514 pub fn parameter(&mut self, parameter: impl Into<Parameter>) -> &mut Self {
516 self.parameters.get_or_insert_default().push(parameter.into());
517 self
518 }
519
520 pub fn responses<R: Into<RefOr<Response>>, C: Into<String>>(
522 &mut self,
523 responses: impl IntoIterator<Item = (C, R)>,
524 ) -> &mut Self {
525 responses.into_iter().fold(self, |this, (c, r)| this.response(c, r))
526 }
527
528 pub fn response(&mut self, code: impl Into<String>, response: impl Into<RefOr<Response>>) -> &mut Self {
533 self.responses.responses.insert(code.into(), response.into());
534
535 self
536 }
537
538 pub fn security(&mut self, security: impl Into<SecurityRequirement>) -> &mut Self {
540 self.security.get_or_insert_default().push(security.into());
541 self
542 }
543
544 pub fn securities<R: Into<SecurityRequirement>>(&mut self, securities: impl IntoIterator<Item = R>) -> &mut Self {
546 securities.into_iter().fold(self, |this, s| this.security(s))
547 }
548
549 pub fn servers<E: Into<Server>>(&mut self, servers: impl IntoIterator<Item = E>) -> &mut Self {
551 servers.into_iter().fold(self, |this, e| this.server(e))
552 }
553
554 pub fn server(&mut self, server: impl Into<Server>) -> &mut Self {
556 self.servers.get_or_insert_default().push(server.into());
557 self
558 }
559}
560
561#[non_exhaustive]
565#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
566#[cfg_attr(feature = "debug", derive(Debug))]
567#[serde(rename_all = "camelCase")]
568#[builder(on(_, into))]
569pub struct Parameter {
570 pub name: String,
575
576 #[serde(rename = "in")]
578 pub parameter_in: ParameterIn,
579
580 #[serde(skip_serializing_if = "Option::is_none", default)]
582 pub description: Option<String>,
583
584 pub required: bool,
588
589 #[serde(skip_serializing_if = "Option::is_none", default)]
591 pub deprecated: Option<Deprecated>,
592 #[serde(skip_serializing_if = "Option::is_none", default)]
595 pub schema: Option<Schema>,
596
597 #[serde(skip_serializing_if = "Option::is_none", default)]
600 pub style: Option<ParameterStyle>,
601
602 #[serde(skip_serializing_if = "Option::is_none", default)]
615 pub explode: Option<bool>,
616
617 #[serde(skip_serializing_if = "Option::is_none", default)]
621 pub allow_reserved: Option<bool>,
622
623 #[serde(skip_serializing_if = "Option::is_none", default)]
626 example: Option<Value>,
627
628 #[serde(skip_serializing_if = "Option::is_none", default, flatten)]
630 pub extensions: Option<Extensions>,
631}
632
633impl Parameter {
634 pub fn new<S: Into<String>>(name: S) -> Self {
636 Self {
637 name: name.into(),
638 required: true,
639 ..Default::default()
640 }
641 }
642}
643
644#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
646#[serde(rename_all = "lowercase")]
647#[cfg_attr(feature = "debug", derive(Debug))]
648pub enum ParameterIn {
649 Query,
651 Path,
653 Header,
655 Cookie,
657}
658
659impl Default for ParameterIn {
660 fn default() -> Self {
661 Self::Path
662 }
663}
664
665#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
667#[cfg_attr(feature = "debug", derive(Debug))]
668#[serde(rename_all = "camelCase")]
669pub enum ParameterStyle {
670 Matrix,
674 Label,
678 Form,
682 Simple,
685 SpaceDelimited,
688 PipeDelimited,
691 DeepObject,
694}
695
696#[cfg(test)]
697#[cfg(feature = "debug")]
698#[cfg_attr(coverage_nightly, coverage(off))]
699mod tests {
700 use super::{HttpMethod, Operation};
701 use crate::security::SecurityRequirement;
702 use crate::server::Server;
703 use crate::{PathItem, Paths};
704
705 #[test]
706 fn test_path_order() {
707 let paths_list = Paths::builder()
708 .path("/todo", PathItem::new(HttpMethod::Get, Operation::new()))
709 .path("/todo", PathItem::new(HttpMethod::Post, Operation::new()))
710 .path("/todo/{id}", PathItem::new(HttpMethod::Delete, Operation::new()))
711 .path("/todo/{id}", PathItem::new(HttpMethod::Get, Operation::new()))
712 .path("/todo/{id}", PathItem::new(HttpMethod::Put, Operation::new()))
713 .path("/todo/search", PathItem::new(HttpMethod::Get, Operation::new()))
714 .build();
715
716 let actual_value = paths_list
717 .paths
718 .iter()
719 .flat_map(|(path, path_item)| {
720 let mut path_methods = Vec::<(&str, &HttpMethod)>::with_capacity(paths_list.paths.len());
721 if path_item.get.is_some() {
722 path_methods.push((path, &HttpMethod::Get));
723 }
724 if path_item.put.is_some() {
725 path_methods.push((path, &HttpMethod::Put));
726 }
727 if path_item.post.is_some() {
728 path_methods.push((path, &HttpMethod::Post));
729 }
730 if path_item.delete.is_some() {
731 path_methods.push((path, &HttpMethod::Delete));
732 }
733 if path_item.options.is_some() {
734 path_methods.push((path, &HttpMethod::Options));
735 }
736 if path_item.head.is_some() {
737 path_methods.push((path, &HttpMethod::Head));
738 }
739 if path_item.patch.is_some() {
740 path_methods.push((path, &HttpMethod::Patch));
741 }
742 if path_item.trace.is_some() {
743 path_methods.push((path, &HttpMethod::Trace));
744 }
745
746 path_methods
747 })
748 .collect::<Vec<_>>();
749
750 let get = HttpMethod::Get;
751 let post = HttpMethod::Post;
752 let put = HttpMethod::Put;
753 let delete = HttpMethod::Delete;
754
755 let expected_value = vec![
756 ("/todo", &get),
757 ("/todo", &post),
758 ("/todo/{id}", &get),
759 ("/todo/{id}", &put),
760 ("/todo/{id}", &delete),
761 ("/todo/search", &get),
762 ];
763 assert_eq!(actual_value, expected_value);
764 }
765
766 #[test]
767 fn operation_new() {
768 let operation = Operation::new();
769
770 assert!(operation.tags.is_none());
771 assert!(operation.summary.is_none());
772 assert!(operation.description.is_none());
773 assert!(operation.operation_id.is_none());
774 assert!(operation.external_docs.is_none());
775 assert!(operation.parameters.is_none());
776 assert!(operation.request_body.is_none());
777 assert!(operation.responses.responses.is_empty());
778 assert!(operation.callbacks.is_none());
779 assert!(operation.deprecated.is_none());
780 assert!(operation.security.is_none());
781 assert!(operation.servers.is_none());
782 }
783
784 #[test]
785 fn operation_builder_security() {
786 let security_requirement1 = SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
787 let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
788 let operation = Operation::builder()
789 .security(security_requirement1)
790 .security(security_requirement2)
791 .build();
792
793 assert!(operation.security.is_some());
794 }
795
796 #[test]
797 fn operation_builder_server() {
798 let server1 = Server::new("/api");
799 let server2 = Server::new("/admin");
800 let operation = Operation::builder().server(server1).server(server2).build();
801
802 assert!(operation.servers.is_some());
803 }
804}