1use std::iter;
5use std::ops::{Deref, DerefMut};
6
7use serde::{Deserialize, Serialize};
8
9use super::{Operation, Operations, Parameter, Parameters, PathMap, PropMap, Server, Servers};
10
11#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
15pub struct Paths(PathMap<String, PathItem>);
16impl Deref for Paths {
17 type Target = PathMap<String, PathItem>;
18
19 fn deref(&self) -> &Self::Target {
20 &self.0
21 }
22}
23impl DerefMut for Paths {
24 fn deref_mut(&mut self) -> &mut Self::Target {
25 &mut self.0
26 }
27}
28impl Paths {
29 #[must_use]
31 pub fn new() -> Self {
32 Default::default()
33 }
34 #[must_use]
36 pub fn path<K: Into<String>, V: Into<PathItem>>(mut self, key: K, value: V) -> Self {
37 self.insert(key, value);
38 self
39 }
40 pub fn insert<K: Into<String>, V: Into<PathItem>>(&mut self, key: K, value: V) {
42 let key = key.into();
43 let mut value = value.into();
44 self.0
45 .entry(key)
46 .and_modify(|item| {
47 if value.ref_location.is_some() {
48 item.ref_location = value.ref_location.take();
49 }
50 if value.summary.is_some() {
51 item.summary = value.summary.take();
52 }
53 if value.description.is_some() {
54 item.description = value.description.take();
55 }
56 item.servers.append(&mut value.servers);
57 item.parameters.append(&mut value.parameters);
58 item.operations.append(&mut value.operations);
59 })
60 .or_insert(value);
61 }
62 pub fn append(&mut self, other: &mut Self) {
69 let items = std::mem::take(&mut other.0);
70 for item in items {
71 self.insert(item.0, item.1);
72 }
73 }
74 pub fn extend<I, K, V>(&mut self, iter: I)
76 where
77 I: IntoIterator<Item = (K, V)>,
78 K: Into<String>,
79 V: Into<PathItem>,
80 {
81 for (k, v) in iter.into_iter() {
82 self.insert(k, v);
83 }
84 }
85}
86
87#[non_exhaustive]
92#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
93#[serde(rename_all = "camelCase")]
94pub struct PathItem {
95 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none", default)]
103 pub ref_location: Option<String>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub summary: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
112 pub description: Option<String>,
113
114 #[serde(skip_serializing_if = "Servers::is_empty", default)]
117 pub servers: Servers,
118
119 #[serde(skip_serializing_if = "Parameters::is_empty", default)]
123 pub parameters: Parameters,
124
125 #[serde(flatten, default)]
128 pub operations: Operations,
129
130 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
132 pub extensions: PropMap<String, serde_json::Value>,
133}
134
135impl PathItem {
136 pub fn new<O: Into<Operation>>(path_item_type: PathItemType, operation: O) -> Self {
138 let operations = PropMap::from_iter(iter::once((path_item_type, operation.into())));
139
140 Self {
141 operations: Operations(operations),
142 ..Default::default()
143 }
144 }
145
146 #[must_use]
154 pub fn from_ref<S: Into<String>>(ref_location: S) -> Self {
155 Self {
156 ref_location: Some(ref_location.into()),
157 ..Default::default()
158 }
159 }
160
161 #[must_use]
163 pub fn ref_location<S: Into<String>>(mut self, ref_location: S) -> Self {
164 self.ref_location = Some(ref_location.into());
165 self
166 }
167 pub fn append(&mut self, other: &mut Self) {
172 self.operations.append(&mut other.operations);
173 self.servers.append(&mut other.servers);
174 self.parameters.append(&mut other.parameters);
175 if other.description.is_some() {
176 self.description = other.description.take();
177 }
178 if other.summary.is_some() {
179 self.summary = other.summary.take();
180 }
181 if other.ref_location.is_some() {
182 self.ref_location = other.ref_location.take();
183 }
184 other
185 .extensions
186 .retain(|name, _| !self.extensions.contains_key(name));
187 self.extensions.append(&mut other.extensions);
188 }
189
190 #[must_use]
193 pub fn add_operation<O: Into<Operation>>(
194 mut self,
195 path_item_type: PathItemType,
196 operation: O,
197 ) -> Self {
198 self.operations.insert(path_item_type, operation.into());
199 self
200 }
201
202 #[must_use]
204 pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
205 self.summary = Some(summary.into());
206 self
207 }
208
209 #[must_use]
212 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
213 self.description = Some(description.into());
214 self
215 }
216
217 #[must_use]
220 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
221 self.servers = Servers(servers.into_iter().collect());
222 self
223 }
224
225 #[must_use]
227 pub fn parameters<I: IntoIterator<Item = Parameter>>(mut self, parameters: I) -> Self {
228 self.parameters = Parameters(parameters.into_iter().collect());
229 self
230 }
231
232 #[must_use]
234 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
235 self.extensions = extensions;
236 self
237 }
238}
239
240#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy, Debug)]
248#[serde(rename_all = "lowercase")]
249pub enum PathItemType {
250 Get,
252 Post,
254 Put,
256 Delete,
258 Options,
260 Head,
262 Patch,
264 Trace,
266}
267
268#[cfg(test)]
269mod tests {
270 use assert_json_diff::assert_json_eq;
271 use serde_json::json;
272
273 use super::*;
274 use crate::oapi::response::Response;
275
276 #[test]
277 fn test_build_path_item() {
278 let path_item = PathItem::new(PathItemType::Get, Operation::new())
279 .summary("summary")
280 .description("description")
281 .servers(Servers::new())
282 .parameters(Parameters::new());
283
284 assert_json_eq!(
285 path_item,
286 json!({
287 "description": "description",
288 "summary": "summary",
289 "get": {
290 "responses": {}
291 }
292 })
293 )
294 }
295
296 #[test]
297 fn path_item_ref_serializes_as_dollar_ref() {
298 let item = PathItem::from_ref("#/components/pathItems/PingWebhook");
299 assert_json_eq!(
300 item,
301 json!({ "$ref": "#/components/pathItems/PingWebhook" })
302 );
303 }
304
305 #[test]
306 fn path_item_ref_round_trips_via_serde() {
307 let raw = json!({ "$ref": "#/components/pathItems/PingWebhook" });
308 let item: PathItem = serde_json::from_value(raw.clone()).expect("deserialize");
309 assert_eq!(
310 item.ref_location.as_deref(),
311 Some("#/components/pathItems/PingWebhook")
312 );
313 assert!(item.summary.is_none());
316 assert!(item.operations.is_empty());
317
318 let reserialized = serde_json::to_value(&item).expect("serialize");
319 assert_eq!(reserialized, raw);
320 }
321
322 #[test]
323 fn path_item_ref_setter_overrides_plain_construction() {
324 let item = PathItem::new(PathItemType::Get, Operation::new())
325 .ref_location("#/components/pathItems/Other");
326
327 let value = serde_json::to_value(&item).expect("serialize");
328 assert_eq!(
329 value["$ref"],
330 json!("#/components/pathItems/Other"),
331 "$ref should serialize when set"
332 );
333 assert!(value.get("get").is_some());
337 }
338
339 #[test]
340 fn path_item_parameters_serialize_as_named_field() {
341 use crate::Parameter;
342
343 let path_item = PathItem::new(PathItemType::Get, Operation::new())
344 .parameters([Parameter::new("id").location(crate::ParameterIn::Path)]);
345
346 assert_json_eq!(
347 path_item,
348 json!({
349 "parameters": [
350 {
351 "name": "id",
352 "in": "path",
353 "required": true
354 }
355 ],
356 "get": {
357 "responses": {}
358 }
359 })
360 );
361 }
362
363 #[test]
364 fn test_path_item_append() {
365 let mut path_item = PathItem::new(
366 PathItemType::Get,
367 Operation::new().add_response("200", Response::new("Get success")),
368 );
369 let mut other_path_item = PathItem::new(
370 PathItemType::Post,
371 Operation::new().add_response("200", Response::new("Post success")),
372 )
373 .description("description")
374 .summary("summary");
375 path_item.append(&mut other_path_item);
376
377 assert_json_eq!(
378 path_item,
379 json!({
380 "description": "description",
381 "summary": "summary",
382 "get": {
383 "responses": {
384 "200": {
385 "description": "Get success"
386 }
387 }
388 },
389 "post": {
390 "responses": {
391 "200": {
392 "description": "Post success"
393 }
394 }
395 }
396 })
397 )
398 }
399
400 #[test]
401 fn test_path_item_add_operation() {
402 let path_item = PathItem::new(
403 PathItemType::Get,
404 Operation::new().add_response("200", Response::new("Get success")),
405 )
406 .add_operation(
407 PathItemType::Post,
408 Operation::new().add_response("200", Response::new("Post success")),
409 );
410
411 assert_json_eq!(
412 path_item,
413 json!({
414 "get": {
415 "responses": {
416 "200": {
417 "description": "Get success"
418 }
419 }
420 },
421 "post": {
422 "responses": {
423 "200": {
424 "description": "Post success"
425 }
426 }
427 }
428 })
429 )
430 }
431
432 #[test]
433 fn test_paths_extend() {
434 let mut paths = Paths::new().path(
435 "/api/do_something",
436 PathItem::new(
437 PathItemType::Get,
438 Operation::new().add_response("200", Response::new("Get success")),
439 ),
440 );
441 paths.extend([(
442 "/api/do_something",
443 PathItem::new(
444 PathItemType::Post,
445 Operation::new().add_response("200", Response::new("Post success")),
446 )
447 .summary("summary")
448 .description("description"),
449 )]);
450
451 assert_json_eq!(
452 paths,
453 json!({
454 "/api/do_something": {
455 "description": "description",
456 "summary": "summary",
457 "get": {
458 "responses": {
459 "200": {
460 "description": "Get success"
461 }
462 }
463 },
464 "post": {
465 "responses": {
466 "200": {
467 "description": "Post success"
468 }
469 }
470 }
471 }
472 })
473 );
474 }
475
476 #[test]
477 fn test_paths_deref() {
478 let paths = Paths::new();
479 assert_eq!(0, paths.len());
480 }
481}