aide/openapi/
paths.rs

1use std::marker::PhantomData;
2
3use crate::{openapi::*, util::*};
4use indexmap::IndexMap;
5use serde::{Deserialize, Deserializer, Serialize};
6use tracing::warn;
7
8/// Describes the operations available on a single path.
9/// A Path Item MAY be empty, due to ACL constraints.
10/// The path itself is still exposed to the documentation
11/// viewer but they will not know which operations and
12/// parameters are available.
13#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, schemars::JsonSchema)]
14pub struct PathItem {
15    /// Allows for a referenced definition of this path item. The referenced
16    /// structure MUST be in the form of a Path Item Object.  In case a Path
17    /// Item Object field appears both in the defined object and the referenced
18    /// object, the behavior is undefined. See the rules for resolving Relative
19    /// References.
20    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
21    pub reference: Option<String>,
22    /// An optional, string summary, intended to apply to all operations in
23    /// this path.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub summary: Option<String>,
26    /// An optional, string description, intended to apply to all operations in
27    /// this path. CommonMark syntax MAY be used for rich text representation.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub description: Option<String>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub get: Option<Operation>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub put: Option<Operation>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub post: Option<Operation>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub delete: Option<Operation>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub options: Option<Operation>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub head: Option<Operation>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub patch: Option<Operation>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub trace: Option<Operation>,
46    /// An alternative server array to service all operations in this path.
47    #[serde(default, skip_serializing_if = "Vec::is_empty")]
48    pub servers: Vec<Server>,
49    /// A list of parameters that are applicable for all the
50    /// operations described under this path. These parameters
51    /// can be overridden at the operation level, but cannot be
52    /// removed there. The list MUST NOT include duplicated parameters.
53    /// A unique parameter is defined by a combination of a name and location.
54    /// The list can use the Reference Object to link to parameters that
55    /// are defined at the OpenAPI Object's components/parameters.
56    #[serde(default)]
57    #[serde(skip_serializing_if = "Vec::is_empty")]
58    pub parameters: Vec<ReferenceOr<Parameter>>,
59    /// Inline extensions to this object.
60    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
61    pub extensions: IndexMap<String, serde_json::Value>,
62}
63
64impl PathItem {
65    /// Returns an iterator of references to the [Operation]s in the [PathItem].
66    pub fn iter(&self) -> impl Iterator<Item = (&str, &'_ Operation)> {
67        vec![
68            ("get", &self.get),
69            ("put", &self.put),
70            ("post", &self.post),
71            ("delete", &self.delete),
72            ("options", &self.options),
73            ("head", &self.head),
74            ("patch", &self.patch),
75            ("trace", &self.trace),
76        ]
77        .into_iter()
78        .filter_map(|(method, maybe_op)| maybe_op.as_ref().map(|op| (method, op)))
79    }
80
81    /// Merges two path items as well as it can. Global settings will likely conflict. Conflicts always favor self.
82    pub fn merge(mut self, other: Self) -> Self {
83        self.merge_with(other);
84        self
85    }
86
87    /// Merges self with other path item as well as it can. Global settings will likely conflict. Conflicts always favor self.
88    pub fn merge_with(&mut self, mut other: Self) {
89        self.servers.append(&mut other.servers);
90
91        self.parameters.append(&mut other.parameters);
92
93        for (k, ext) in other.extensions {
94            self.extensions
95                .entry(k.clone())
96                .and_modify(|_| warn!("Conflict on merging extension {}", k))
97                .or_insert(ext);
98        }
99        macro_rules! merge {
100            ($id:ident) => {
101                if let Some($id) = other.$id {
102                    if self.$id.is_some() {
103                        warn!(
104                            "Conflict on merging {}, ignoring duplicate",
105                            stringify!($id)
106                        );
107                    } else {
108                        let _ = self.$id.insert($id);
109                    }
110                }
111            };
112        }
113        merge!(reference);
114        merge!(summary);
115        merge!(description);
116        merge!(get);
117        merge!(put);
118        merge!(post);
119        merge!(delete);
120        merge!(options);
121        merge!(head);
122        merge!(patch);
123        merge!(trace);
124    }
125}
126
127impl IntoIterator for PathItem {
128    type Item = (&'static str, Operation);
129
130    type IntoIter = std::vec::IntoIter<Self::Item>;
131
132    /// Returns an iterator of the [Operation]s in the [PathItem].
133    fn into_iter(self) -> Self::IntoIter {
134        vec![
135            ("get", self.get),
136            ("put", self.put),
137            ("post", self.post),
138            ("delete", self.delete),
139            ("options", self.options),
140            ("head", self.head),
141            ("patch", self.patch),
142            ("trace", self.trace),
143        ]
144        .into_iter()
145        .filter_map(|(method, maybe_op)| maybe_op.map(|op| (method, op)))
146        .collect::<Vec<_>>()
147        .into_iter()
148    }
149}
150
151/// Holds the relative paths to the individual endpoints and
152/// their operations. The path is appended to the URL from the
153/// Server Object in order to construct the full URL. The Paths
154/// MAY be empty, due to Access Control List (ACL) constraints.
155#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, schemars::JsonSchema)]
156pub struct Paths {
157    /// A map of PathItems or references to them.
158    #[serde(flatten, deserialize_with = "deserialize_paths")]
159    pub paths: IndexMap<String, ReferenceOr<PathItem>>,
160    /// Inline extensions to this object.
161    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
162    pub extensions: IndexMap<String, serde_json::Value>,
163}
164
165impl Paths {
166    /// Iterate over path items.
167    pub fn iter(&self) -> indexmap::map::Iter<'_, String, ReferenceOr<PathItem>> {
168        self.paths.iter()
169    }
170}
171
172impl IntoIterator for Paths {
173    type Item = (String, ReferenceOr<PathItem>);
174
175    type IntoIter = indexmap::map::IntoIter<String, ReferenceOr<PathItem>>;
176
177    fn into_iter(self) -> Self::IntoIter {
178        self.paths.into_iter()
179    }
180}
181
182fn deserialize_paths<'de, D>(
183    deserializer: D,
184) -> Result<IndexMap<String, ReferenceOr<PathItem>>, D::Error>
185where
186    D: Deserializer<'de>,
187{
188    deserializer.deserialize_map(PredicateVisitor(
189        |key: &String| key.starts_with('/'),
190        PhantomData,
191    ))
192}