1use std::iter;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use super::{
10 builder,
11 request_body::RequestBody,
12 response::{Response, Responses},
13 set_value, Deprecated, ExternalDocs, RefOr, Required, Schema, SecurityRequirement, Server,
14};
15
16#[cfg(not(feature = "preserve_path_order"))]
17type PathsMap<K, V> = std::collections::BTreeMap<K, V>;
18#[cfg(feature = "preserve_path_order")]
19type PathsMap<K, V> = indexmap::IndexMap<K, V>;
20
21builder! {
22 PathsBuilder;
23
24 #[non_exhaustive]
31 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
32 #[cfg_attr(feature = "debug", derive(Debug))]
33 pub struct Paths {
34 pub paths: PathsMap<String, PathItem>,
37 }
38}
39
40impl Paths {
41 pub fn new() -> Self {
43 Default::default()
44 }
45
46 pub fn get_path_item<P: AsRef<str>>(&self, path: P) -> Option<&PathItem> {
58 self.paths.get(path.as_ref())
59 }
60
61 pub fn get_path_operation<P: AsRef<str>>(
75 &self,
76 path: P,
77 item_type: PathItemType,
78 ) -> Option<&Operation> {
79 self.paths
80 .get(path.as_ref())
81 .and_then(|path| path.operations.get(&item_type))
82 }
83}
84
85impl PathsBuilder {
86 pub fn path<I: Into<String>>(mut self, path: I, item: PathItem) -> Self {
89 let path_string = path.into();
90 if let Some(existing_item) = self.paths.get_mut(&path_string) {
91 existing_item
92 .operations
93 .extend(&mut item.operations.into_iter());
94 } else {
95 self.paths.insert(path_string, item);
96 }
97
98 self
99 }
100}
101
102builder! {
103 PathItemBuilder;
104
105 #[non_exhaustive]
110 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
111 #[cfg_attr(feature = "debug", derive(Debug))]
112 #[serde(rename_all = "camelCase")]
113 pub struct PathItem {
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub summary: Option<String>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
121 pub description: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
126 pub servers: Option<Vec<Server>>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
132 pub parameters: Option<Vec<Parameter>>,
133
134 #[serde(flatten)]
137 pub operations: PathsMap<PathItemType, Operation>,
138 }
139}
140
141impl PathItem {
142 pub fn new<O: Into<Operation>>(path_item_type: PathItemType, operation: O) -> Self {
144 let operations = PathsMap::from_iter(iter::once((path_item_type, operation.into())));
145
146 Self {
147 operations,
148 ..Default::default()
149 }
150 }
151}
152
153impl PathItemBuilder {
154 pub fn operation<O: Into<Operation>>(
157 mut self,
158 path_item_type: PathItemType,
159 operation: O,
160 ) -> Self {
161 self.operations.insert(path_item_type, operation.into());
162
163 self
164 }
165
166 pub fn summary<S: Into<String>>(mut self, summary: Option<S>) -> Self {
168 set_value!(self summary summary.map(|summary| summary.into()))
169 }
170
171 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
174 set_value!(self description description.map(|description| description.into()))
175 }
176
177 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
180 set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
181 }
182
183 pub fn parameters<I: IntoIterator<Item = Parameter>>(mut self, parameters: Option<I>) -> Self {
185 set_value!(self parameters parameters.map(|parameters| parameters.into_iter().collect()))
186 }
187}
188
189#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
191#[serde(rename_all = "lowercase")]
192#[cfg_attr(feature = "debug", derive(Debug))]
193pub enum PathItemType {
194 Get,
196 Post,
198 Put,
200 Delete,
202 Options,
204 Head,
206 Patch,
208 Trace,
210 Connect,
212}
213
214builder! {
215 OperationBuilder;
216
217 #[non_exhaustive]
221 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
222 #[cfg_attr(feature = "debug", derive(Debug))]
223 #[serde(rename_all = "camelCase")]
224 pub struct Operation {
225 #[serde(skip_serializing_if = "Option::is_none")]
235 pub tags: Option<Vec<String>>,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
244 pub summary: Option<String>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
253 pub description: Option<String>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
262 pub operation_id: Option<String>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub external_docs: Option<ExternalDocs>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub parameters: Option<Vec<Parameter>>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub request_body: Option<RequestBody>,
275
276 pub responses: Responses,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub callbacks: Option<String>,
282
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub deprecated: Option<Deprecated>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
293 pub security: Option<Vec<SecurityRequirement>>,
294
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub servers: Option<Vec<Server>>,
298 }
299}
300
301impl Operation {
302 pub fn new() -> Self {
304 Default::default()
305 }
306}
307
308impl OperationBuilder {
309 pub fn tags<I: IntoIterator<Item = String>>(mut self, tags: Option<I>) -> Self {
311 set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
312 }
313
314 pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
316 let tag_string = tag.into();
317 match self.tags {
318 Some(ref mut tags) => tags.push(tag_string),
319 None => {
320 self.tags = Some(vec![tag_string]);
321 }
322 }
323
324 self
325 }
326
327 pub fn summary<S: Into<String>>(mut self, summary: Option<S>) -> Self {
329 set_value!(self summary summary.map(|summary| summary.into()))
330 }
331
332 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
334 set_value!(self description description.map(|description| description.into()))
335 }
336
337 pub fn operation_id<S: Into<String>>(mut self, operation_id: Option<S>) -> Self {
339 set_value!(self operation_id operation_id.map(|operation_id| operation_id.into()))
340 }
341
342 pub fn parameters<I: IntoIterator<Item = P>, P: Into<Parameter>>(
344 mut self,
345 parameters: Option<I>,
346 ) -> Self {
347 self.parameters = parameters.map(|parameters| {
348 if let Some(mut params) = self.parameters {
349 params.extend(parameters.into_iter().map(|parameter| parameter.into()));
350 params
351 } else {
352 parameters
353 .into_iter()
354 .map(|parameter| parameter.into())
355 .collect()
356 }
357 });
358
359 self
360 }
361
362 pub fn parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
364 match self.parameters {
365 Some(ref mut parameters) => parameters.push(parameter.into()),
366 None => {
367 self.parameters = Some(vec![parameter.into()]);
368 }
369 }
370
371 self
372 }
373
374 pub fn request_body(mut self, request_body: Option<RequestBody>) -> Self {
376 set_value!(self request_body request_body)
377 }
378
379 pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
381 set_value!(self responses responses.into())
382 }
383
384 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
389 mut self,
390 code: S,
391 response: R,
392 ) -> Self {
393 self.responses
394 .responses
395 .insert(code.into(), response.into());
396
397 self
398 }
399
400 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
402 set_value!(self deprecated deprecated)
403 }
404
405 pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(
407 mut self,
408 securities: Option<I>,
409 ) -> Self {
410 set_value!(self security securities.map(|securities| securities.into_iter().collect()))
411 }
412
413 pub fn security(mut self, security: SecurityRequirement) -> Self {
415 if let Some(ref mut securities) = self.security {
416 securities.push(security);
417 } else {
418 self.security = Some(vec![security]);
419 }
420
421 self
422 }
423
424 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
426 set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
427 }
428
429 pub fn server(mut self, server: Server) -> Self {
431 if let Some(ref mut servers) = self.servers {
432 servers.push(server);
433 } else {
434 self.servers = Some(vec![server]);
435 }
436
437 self
438 }
439}
440
441builder! {
442 ParameterBuilder;
443
444 #[non_exhaustive]
448 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
449 #[cfg_attr(feature = "debug", derive(Debug))]
450 #[serde(rename_all = "camelCase")]
451 pub struct Parameter {
452 pub name: String,
457
458 #[serde(rename = "in")]
460 pub parameter_in: ParameterIn,
461
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub description: Option<String>,
465
466 pub required: Required,
470
471 #[serde(skip_serializing_if = "Option::is_none")]
473 pub deprecated: Option<Deprecated>,
474 #[serde(skip_serializing_if = "Option::is_none")]
477 pub schema: Option<RefOr<Schema>>,
478
479 #[serde(skip_serializing_if = "Option::is_none")]
482 pub style: Option<ParameterStyle>,
483
484 #[serde(skip_serializing_if = "Option::is_none")]
497 pub explode: Option<bool>,
498
499 #[serde(skip_serializing_if = "Option::is_none")]
503 pub allow_reserved: Option<bool>,
504
505 #[serde(skip_serializing_if = "Option::is_none")]
508 example: Option<Value>,
509 }
510}
511
512impl Parameter {
513 pub fn new<S: Into<String>>(name: S) -> Self {
515 Self {
516 name: name.into(),
517 required: Required::True,
518 ..Default::default()
519 }
520 }
521}
522
523impl ParameterBuilder {
524 pub fn name<I: Into<String>>(mut self, name: I) -> Self {
526 set_value!(self name name.into())
527 }
528
529 pub fn parameter_in(mut self, parameter_in: ParameterIn) -> Self {
531 set_value!(self parameter_in parameter_in)
532 }
533
534 pub fn required(mut self, required: Required) -> Self {
537 self.required = required;
538 if self.parameter_in == ParameterIn::Path {
540 self.required = Required::True;
541 }
542
543 self
544 }
545
546 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
548 set_value!(self description description.map(|description| description.into()))
549 }
550
551 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
553 set_value!(self deprecated deprecated)
554 }
555
556 pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: Option<I>) -> Self {
558 set_value!(self schema component.map(|component| component.into()))
559 }
560
561 pub fn style(mut self, style: Option<ParameterStyle>) -> Self {
563 set_value!(self style style)
564 }
565
566 pub fn explode(mut self, explode: Option<bool>) -> Self {
568 set_value!(self explode explode)
569 }
570
571 pub fn allow_reserved(mut self, allow_reserved: Option<bool>) -> Self {
573 set_value!(self allow_reserved allow_reserved)
574 }
575
576 pub fn example(mut self, example: Option<Value>) -> Self {
578 set_value!(self example example)
579 }
580}
581
582#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
584#[serde(rename_all = "lowercase")]
585#[cfg_attr(feature = "debug", derive(Debug))]
586pub enum ParameterIn {
587 Query,
589 Path,
591 Header,
593 Cookie,
595}
596
597impl Default for ParameterIn {
598 fn default() -> Self {
599 Self::Path
600 }
601}
602
603#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
605#[cfg_attr(feature = "debug", derive(Debug))]
606#[serde(rename_all = "camelCase")]
607pub enum ParameterStyle {
608 Matrix,
612 Label,
616 Form,
620 Simple,
623 SpaceDelimited,
626 PipeDelimited,
629 DeepObject,
632}
633
634#[cfg(test)]
635mod tests {
636 use super::{Operation, OperationBuilder};
637 use crate::openapi::{
638 security::SecurityRequirement, server::Server, PathItem, PathItemType, PathsBuilder,
639 };
640
641 #[test]
642 fn test_path_order() {
643 let paths_list = PathsBuilder::new()
644 .path(
645 "/todo",
646 PathItem::new(PathItemType::Get, OperationBuilder::new()),
647 )
648 .path(
649 "/todo",
650 PathItem::new(PathItemType::Post, OperationBuilder::new()),
651 )
652 .path(
653 "/todo/{id}",
654 PathItem::new(PathItemType::Delete, OperationBuilder::new()),
655 )
656 .path(
657 "/todo/{id}",
658 PathItem::new(PathItemType::Get, OperationBuilder::new()),
659 )
660 .path(
661 "/todo/{id}",
662 PathItem::new(PathItemType::Put, OperationBuilder::new()),
663 )
664 .path(
665 "/todo/search",
666 PathItem::new(PathItemType::Get, OperationBuilder::new()),
667 )
668 .build();
669
670 let actual_value = paths_list
671 .paths
672 .iter()
673 .flat_map(|(path, path_item)| {
674 path_item.operations.iter().fold(
675 Vec::<(&str, &PathItemType)>::with_capacity(paths_list.paths.len()),
676 |mut acc, (method, _)| {
677 acc.push((path.as_str(), method));
678 acc
679 },
680 )
681 })
682 .collect::<Vec<_>>();
683
684 let get = PathItemType::Get;
685 let post = PathItemType::Post;
686 let put = PathItemType::Put;
687 let delete = PathItemType::Delete;
688
689 #[cfg(not(feature = "preserve_path_order"))]
690 {
691 let expected_value = vec![
692 ("/todo", &get),
693 ("/todo", &post),
694 ("/todo/search", &get),
695 ("/todo/{id}", &get),
696 ("/todo/{id}", &put),
697 ("/todo/{id}", &delete),
698 ];
699 assert_eq!(actual_value, expected_value);
700 }
701
702 #[cfg(feature = "preserve_path_order")]
703 {
704 let expected_value = vec![
705 ("/todo", &get),
706 ("/todo", &post),
707 ("/todo/{id}", &delete),
708 ("/todo/{id}", &get),
709 ("/todo/{id}", &put),
710 ("/todo/search", &get),
711 ];
712 assert_eq!(actual_value, expected_value);
713 }
714 }
715
716 #[test]
717 fn operation_new() {
718 let operation = Operation::new();
719
720 assert!(operation.tags.is_none());
721 assert!(operation.summary.is_none());
722 assert!(operation.description.is_none());
723 assert!(operation.operation_id.is_none());
724 assert!(operation.external_docs.is_none());
725 assert!(operation.parameters.is_none());
726 assert!(operation.request_body.is_none());
727 assert!(operation.responses.responses.is_empty());
728 assert!(operation.callbacks.is_none());
729 assert!(operation.deprecated.is_none());
730 assert!(operation.security.is_none());
731 assert!(operation.servers.is_none());
732 }
733
734 #[test]
735 fn operation_builder_security() {
736 let security_requirement1 =
737 SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
738 let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
739 let operation = OperationBuilder::new()
740 .security(security_requirement1)
741 .security(security_requirement2)
742 .build();
743
744 assert!(operation.security.is_some());
745 }
746
747 #[test]
748 fn operation_builder_server() {
749 let server1 = Server::new("/api");
750 let server2 = Server::new("/admin");
751 let operation = OperationBuilder::new()
752 .server(server1)
753 .server(server2)
754 .build();
755
756 assert!(operation.servers.is_some());
757 }
758}