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 item.additional_operations
60 .append(&mut value.additional_operations);
61 })
62 .or_insert(value);
63 }
64 pub fn append(&mut self, other: &mut Self) {
71 let items = std::mem::take(&mut other.0);
72 for item in items {
73 self.insert(item.0, item.1);
74 }
75 }
76 pub fn extend<I, K, V>(&mut self, iter: I)
78 where
79 I: IntoIterator<Item = (K, V)>,
80 K: Into<String>,
81 V: Into<PathItem>,
82 {
83 for (k, v) in iter.into_iter() {
84 self.insert(k, v);
85 }
86 }
87}
88
89#[non_exhaustive]
94#[derive(Serialize, Default, Clone, PartialEq, Debug)]
95#[serde(rename_all = "camelCase")]
96pub struct PathItem {
97 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none", default)]
105 pub ref_location: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub summary: Option<String>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
114 pub description: Option<String>,
115
116 #[serde(skip_serializing_if = "Servers::is_empty", default)]
119 pub servers: Servers,
120
121 #[serde(skip_serializing_if = "Parameters::is_empty", default)]
125 pub parameters: Parameters,
126
127 #[serde(flatten, default)]
130 pub operations: Operations,
131
132 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
141 pub additional_operations: PropMap<String, Operation>,
142
143 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
145 pub extensions: PropMap<String, serde_json::Value>,
146}
147
148impl<'de> Deserialize<'de> for PathItem {
153 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154 where
155 D: serde::Deserializer<'de>,
156 {
157 fn field<T, E>(name: &str, value: serde_json::Value) -> Result<T, E>
158 where
159 T: serde::de::DeserializeOwned,
160 E: serde::de::Error,
161 {
162 serde_json::from_value(value)
163 .map_err(|e| E::custom(format!("invalid `{name}` in path item: {e}")))
164 }
165
166 let raw = PropMap::<String, serde_json::Value>::deserialize(deserializer)?;
167 let mut item = Self::default();
168 for (key, value) in raw {
169 match &*key {
170 "$ref" => item.ref_location = Some(field("$ref", value)?),
171 "summary" => item.summary = Some(field("summary", value)?),
172 "description" => item.description = Some(field("description", value)?),
173 "servers" => item.servers = field("servers", value)?,
174 "parameters" => item.parameters = field("parameters", value)?,
175 "additionalOperations" => {
176 item.additional_operations = field("additionalOperations", value)?;
177 }
178 _ => {
179 match serde_json::from_value::<PathItemType>(serde_json::Value::String(
182 key.clone(),
183 )) {
184 Ok(item_type) => {
185 item.operations
186 .insert(item_type, field::<Operation, _>(&key, value)?);
187 }
188 Err(_) => {
189 item.extensions.insert(key, value);
190 }
191 }
192 }
193 }
194 }
195 Ok(item)
196 }
197}
198
199impl PathItem {
200 pub fn new<O: Into<Operation>>(path_item_type: PathItemType, operation: O) -> Self {
202 let operations = PropMap::from_iter(iter::once((path_item_type, operation.into())));
203
204 Self {
205 operations: Operations(operations),
206 ..Default::default()
207 }
208 }
209
210 #[must_use]
218 pub fn from_ref<S: Into<String>>(ref_location: S) -> Self {
219 Self {
220 ref_location: Some(ref_location.into()),
221 ..Default::default()
222 }
223 }
224
225 #[must_use]
227 pub fn ref_location<S: Into<String>>(mut self, ref_location: S) -> Self {
228 self.ref_location = Some(ref_location.into());
229 self
230 }
231 pub fn append(&mut self, other: &mut Self) {
236 self.operations.append(&mut other.operations);
237 self.additional_operations
238 .append(&mut other.additional_operations);
239 self.servers.append(&mut other.servers);
240 self.parameters.append(&mut other.parameters);
241 if other.description.is_some() {
242 self.description = other.description.take();
243 }
244 if other.summary.is_some() {
245 self.summary = other.summary.take();
246 }
247 if other.ref_location.is_some() {
248 self.ref_location = other.ref_location.take();
249 }
250 other
251 .extensions
252 .retain(|name, _| !self.extensions.contains_key(name));
253 self.extensions.append(&mut other.extensions);
254 }
255
256 #[must_use]
259 pub fn add_operation<O: Into<Operation>>(
260 mut self,
261 path_item_type: PathItemType,
262 operation: O,
263 ) -> Self {
264 self.operations.insert(path_item_type, operation.into());
265 self
266 }
267
268 #[must_use]
279 pub fn add_additional_operation<M: Into<String>, O: Into<Operation>>(
280 mut self,
281 method: M,
282 operation: O,
283 ) -> Self {
284 self.additional_operations
285 .insert(method.into(), operation.into());
286 self
287 }
288
289 #[must_use]
291 pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
292 self.summary = Some(summary.into());
293 self
294 }
295
296 #[must_use]
299 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
300 self.description = Some(description.into());
301 self
302 }
303
304 #[must_use]
307 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: I) -> Self {
308 self.servers = Servers(servers.into_iter().collect());
309 self
310 }
311
312 #[must_use]
314 pub fn parameters<I: IntoIterator<Item = Parameter>>(mut self, parameters: I) -> Self {
315 self.parameters = Parameters(parameters.into_iter().collect());
316 self
317 }
318
319 #[must_use]
321 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
322 self.extensions = extensions;
323 self
324 }
325}
326
327#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy, Debug)]
336#[serde(rename_all = "lowercase")]
337pub enum PathItemType {
338 Get,
340 Post,
342 Put,
344 Delete,
346 Options,
348 Head,
350 Patch,
352 Trace,
354 Query,
359}
360
361#[cfg(test)]
362mod tests {
363 use assert_json_diff::assert_json_eq;
364 use serde_json::json;
365
366 use super::*;
367 use crate::oapi::response::Response;
368
369 #[test]
370 fn test_build_path_item() {
371 let path_item = PathItem::new(PathItemType::Get, Operation::new())
372 .summary("summary")
373 .description("description")
374 .servers(Servers::new())
375 .parameters(Parameters::new());
376
377 assert_json_eq!(
378 path_item,
379 json!({
380 "description": "description",
381 "summary": "summary",
382 "get": {
383 "responses": {}
384 }
385 })
386 )
387 }
388
389 #[test]
390 fn path_item_ref_serializes_as_dollar_ref() {
391 let item = PathItem::from_ref("#/components/pathItems/PingWebhook");
392 assert_json_eq!(
393 item,
394 json!({ "$ref": "#/components/pathItems/PingWebhook" })
395 );
396 }
397
398 #[test]
399 fn path_item_ref_round_trips_via_serde() {
400 let raw = json!({ "$ref": "#/components/pathItems/PingWebhook" });
401 let item: PathItem = serde_json::from_value(raw.clone()).expect("deserialize");
402 assert_eq!(
403 item.ref_location.as_deref(),
404 Some("#/components/pathItems/PingWebhook")
405 );
406 assert!(item.summary.is_none());
409 assert!(item.operations.is_empty());
410
411 let reserialized = serde_json::to_value(&item).expect("serialize");
412 assert_eq!(reserialized, raw);
413 }
414
415 #[test]
416 fn path_item_ref_setter_overrides_plain_construction() {
417 let item = PathItem::new(PathItemType::Get, Operation::new())
418 .ref_location("#/components/pathItems/Other");
419
420 let value = serde_json::to_value(&item).expect("serialize");
421 assert_eq!(
422 value["$ref"],
423 json!("#/components/pathItems/Other"),
424 "$ref should serialize when set"
425 );
426 assert!(value.get("get").is_some());
430 }
431
432 #[test]
433 fn path_item_parameters_serialize_as_named_field() {
434 use crate::Parameter;
435
436 let path_item = PathItem::new(PathItemType::Get, Operation::new())
437 .parameters([Parameter::new("id").location(crate::ParameterIn::Path)]);
438
439 assert_json_eq!(
440 path_item,
441 json!({
442 "parameters": [
443 {
444 "name": "id",
445 "in": "path",
446 "required": true
447 }
448 ],
449 "get": {
450 "responses": {}
451 }
452 })
453 );
454 }
455
456 #[test]
457 fn test_path_item_append() {
458 let mut path_item = PathItem::new(
459 PathItemType::Get,
460 Operation::new().add_response("200", Response::new("Get success")),
461 );
462 let mut other_path_item = PathItem::new(
463 PathItemType::Post,
464 Operation::new().add_response("200", Response::new("Post success")),
465 )
466 .description("description")
467 .summary("summary");
468 path_item.append(&mut other_path_item);
469
470 assert_json_eq!(
471 path_item,
472 json!({
473 "description": "description",
474 "summary": "summary",
475 "get": {
476 "responses": {
477 "200": {
478 "description": "Get success"
479 }
480 }
481 },
482 "post": {
483 "responses": {
484 "200": {
485 "description": "Post success"
486 }
487 }
488 }
489 })
490 )
491 }
492
493 #[test]
494 fn test_path_item_add_operation() {
495 let path_item = PathItem::new(
496 PathItemType::Get,
497 Operation::new().add_response("200", Response::new("Get success")),
498 )
499 .add_operation(
500 PathItemType::Post,
501 Operation::new().add_response("200", Response::new("Post success")),
502 );
503
504 assert_json_eq!(
505 path_item,
506 json!({
507 "get": {
508 "responses": {
509 "200": {
510 "description": "Get success"
511 }
512 }
513 },
514 "post": {
515 "responses": {
516 "200": {
517 "description": "Post success"
518 }
519 }
520 }
521 })
522 )
523 }
524
525 #[test]
526 fn test_paths_extend() {
527 let mut paths = Paths::new().path(
528 "/api/do_something",
529 PathItem::new(
530 PathItemType::Get,
531 Operation::new().add_response("200", Response::new("Get success")),
532 ),
533 );
534 paths.extend([(
535 "/api/do_something",
536 PathItem::new(
537 PathItemType::Post,
538 Operation::new().add_response("200", Response::new("Post success")),
539 )
540 .summary("summary")
541 .description("description"),
542 )]);
543
544 assert_json_eq!(
545 paths,
546 json!({
547 "/api/do_something": {
548 "description": "description",
549 "summary": "summary",
550 "get": {
551 "responses": {
552 "200": {
553 "description": "Get success"
554 }
555 }
556 },
557 "post": {
558 "responses": {
559 "200": {
560 "description": "Post success"
561 }
562 }
563 }
564 }
565 })
566 );
567 }
568
569 #[test]
570 fn path_item_query_operation_serializes_under_query_key() {
571 let path_item = PathItem::new(PathItemType::Query, Operation::new());
572 assert_json_eq!(path_item, json!({ "query": { "responses": {} } }));
573
574 let parsed: PathItem =
575 serde_json::from_value(json!({ "query": { "responses": {} } })).expect("deserialize");
576 assert!(parsed.operations.contains_key(&PathItemType::Query));
577 }
578
579 #[test]
580 fn path_item_additional_operations_round_trip() {
581 let path_item = PathItem::new(PathItemType::Get, Operation::new())
582 .add_additional_operation("PURGE", Operation::new());
583
584 let value = serde_json::to_value(&path_item).expect("serialize");
585 assert_json_eq!(
586 &value,
587 json!({
588 "get": { "responses": {} },
589 "additionalOperations": { "PURGE": { "responses": {} } }
590 })
591 );
592
593 let parsed: PathItem = serde_json::from_value(value).expect("deserialize");
594 assert!(parsed.operations.contains_key(&PathItemType::Get));
595 assert!(parsed.additional_operations.contains_key("PURGE"));
596 assert_eq!(parsed, path_item);
597 }
598
599 #[test]
600 fn path_item_deserialize_keeps_operations_out_of_extensions() {
601 let raw = json!({
602 "summary": "summary",
603 "get": { "responses": {} },
604 "additionalOperations": { "PURGE": { "responses": {} } },
605 "x-internal": true
606 });
607
608 let item: PathItem = serde_json::from_value(raw.clone()).expect("deserialize");
609 assert!(item.operations.contains_key(&PathItemType::Get));
610 assert!(item.additional_operations.contains_key("PURGE"));
611 assert_eq!(item.extensions.len(), 1);
612 assert_eq!(item.extensions.get("x-internal"), Some(&json!(true)));
613
614 assert_json_eq!(serde_json::to_value(&item).expect("serialize"), raw);
616 }
617
618 #[test]
619 fn path_item_append_merges_additional_operations() {
620 let mut path_item = PathItem::default().add_additional_operation("PURGE", Operation::new());
621 let mut other = PathItem::default().add_additional_operation("LINK", Operation::new());
622 path_item.append(&mut other);
623
624 assert!(path_item.additional_operations.contains_key("PURGE"));
625 assert!(path_item.additional_operations.contains_key("LINK"));
626 }
627
628 #[test]
629 fn test_paths_deref() {
630 let paths = Paths::new();
631 assert_eq!(0, paths.len());
632 }
633}