Skip to main content

kynos_openapi/model/paths/
mod.rs

1//! The Paths, Path Item and Operation Objects, and path templating.
2
3pub mod item;
4pub mod method;
5pub mod operation;
6pub mod template;
7
8use std::fmt;
9
10use serde::{
11    Deserialize, Deserializer, Serialize, Serializer,
12    de::{MapAccess, Visitor},
13    ser::SerializeMap,
14};
15
16use crate::{
17    Map,
18    model::{
19        extensions::Extensions,
20        paths::{item::PathItem, template::PathTemplate},
21    },
22};
23
24/// The available paths and the operations on each.
25///
26/// The specification lets this object carry extensions alongside its path
27/// keys, so it is not a bare map: a `#[serde(transparent)]` newtype made an
28/// `x-` member whose value was not a Path Item fail to parse outright. The
29/// shape is [`Responses`](crate::Responses)' — patterned keys, extensions, and
30/// a hand-written (de)serializer to tell them apart.
31#[derive(Clone, Debug, Default, PartialEq)]
32pub struct Paths {
33    /// The Path Items, keyed by path template.
34    pub items: Map<PathItem>,
35
36    /// Specification extensions.
37    pub extensions: Extensions,
38}
39
40impl Paths {
41    /// Creates an empty path map.
42    #[must_use]
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Inserts a path item, replacing any existing entry for that template.
48    pub fn insert(&mut self, template: &PathTemplate, item: PathItem) -> Option<PathItem> {
49        self.items.insert(template.as_str().to_owned(), item)
50    }
51
52    /// Looks up the path item for a template.
53    #[must_use]
54    pub fn get(&self, template: &PathTemplate) -> Option<&PathItem> {
55        self.items.get(template.as_str())
56    }
57
58    /// Returns `true` when nothing at all is declared.
59    ///
60    /// Extensions count, for the reason
61    /// [`Responses::is_empty`](crate::Responses::is_empty)'s do: a Paths
62    /// Object carrying only `x-` fields still has something to write down.
63    #[must_use]
64    pub fn is_empty(&self) -> bool {
65        self.items.is_empty() && self.extensions.is_empty()
66    }
67
68    /// Returns `true` when a path is declared.
69    #[must_use]
70    pub fn declares_a_path(&self) -> bool {
71        !self.items.is_empty()
72    }
73}
74
75impl Serialize for Paths {
76    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
77        let mut map = serializer.serialize_map(Some(self.items.len() + self.extensions.0.len()))?;
78        for (key, item) in &self.items {
79            map.serialize_entry(key, item)?;
80        }
81        for (key, value) in &self.extensions.0 {
82            map.serialize_entry(key, value)?;
83        }
84        map.end()
85    }
86}
87
88impl<'de> Deserialize<'de> for Paths {
89    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
90        struct PathsVisitor;
91
92        impl<'de> Visitor<'de> for PathsVisitor {
93            type Value = Paths;
94
95            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96                f.write_str("a map of path templates to path items")
97            }
98
99            fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<Paths, A::Error> {
100                let mut paths = Paths::new();
101                while let Some(key) = access.next_key::<String>()? {
102                    if key.starts_with(crate::model::extensions::EXTENSION_PREFIX) {
103                        paths.extensions.0.insert(key, access.next_value()?);
104                    } else {
105                        paths.items.insert(key, access.next_value()?);
106                    }
107                }
108                Ok(paths)
109            }
110        }
111
112        deserializer.deserialize_map(PathsVisitor)
113    }
114}
115
116#[cfg(test)]
117mod tests;