openapiv3_1/
extensions.rs

1//! Implements [OpenAPI Extensions][extensions].
2//!
3//! [extensions]: https://spec.openapis.org/oas/latest.html#specification-extensions
4use std::ops::{Deref, DerefMut};
5
6use indexmap::IndexMap;
7use is_empty::IsEmpty;
8
9const EXTENSION_PREFIX: &str = "x-";
10
11/// Additional [data for extending][extensions] the OpenAPI specification.
12///
13/// [extensions]: https://spec.openapis.org/oas/latest.html#specification-extensions
14#[derive(Default, serde_derive::Serialize, Clone, PartialEq, Eq, IsEmpty)]
15#[cfg_attr(feature = "debug", derive(Debug))]
16pub struct Extensions {
17    #[serde(flatten)]
18    #[is_empty(if = "IndexMap::is_empty")]
19    extensions: IndexMap<String, serde_json::Value>,
20}
21
22impl Extensions {
23    /// Create a new extension from an iterator
24    pub fn new<K: Into<String>, V: Into<serde_json::Value>>(items: impl IntoIterator<Item = (K, V)>) -> Self {
25        items.into_iter().fold(Self::default(), |this, (k, v)| this.add(k, v))
26    }
27
28    /// Merge other [`Extensions`] into _`self`_.
29    pub fn merge(&mut self, other: Extensions) {
30        self.extensions.extend(other.extensions);
31    }
32
33    /// Add an extension to the list
34    pub fn add(mut self, key: impl Into<String>, value: impl Into<serde_json::Value>) -> Self {
35        let mut key = key.into();
36        if !key.starts_with(EXTENSION_PREFIX) {
37            key = format!("{EXTENSION_PREFIX}{key}");
38        }
39        self.extensions.insert(key, value.into());
40        self
41    }
42}
43
44impl Deref for Extensions {
45    type Target = IndexMap<String, serde_json::Value>;
46
47    fn deref(&self) -> &Self::Target {
48        &self.extensions
49    }
50}
51
52impl DerefMut for Extensions {
53    fn deref_mut(&mut self) -> &mut Self::Target {
54        &mut self.extensions
55    }
56}
57
58impl<K, V> FromIterator<(K, V)> for Extensions
59where
60    K: Into<String>,
61    V: Into<serde_json::Value>,
62{
63    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
64        Self::new(iter)
65    }
66}
67
68impl From<Extensions> for IndexMap<String, serde_json::Value> {
69    fn from(value: Extensions) -> Self {
70        value.extensions
71    }
72}
73
74impl<'de> serde::de::Deserialize<'de> for Extensions {
75    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76    where
77        D: serde::Deserializer<'de>,
78    {
79        let extensions: IndexMap<String, _> = IndexMap::deserialize(deserializer)?;
80        let extensions = extensions
81            .into_iter()
82            .filter(|(k, _)| k.starts_with(EXTENSION_PREFIX))
83            .collect();
84        Ok(Self { extensions })
85    }
86}
87
88#[cfg(test)]
89#[cfg_attr(coverage_nightly, coverage(off))]
90mod tests {
91    use serde_json::json;
92
93    use super::*;
94
95    #[test]
96    fn extensions_builder() {
97        let expected = json!("value");
98        let extensions = Extensions::default()
99            .add("x-some-extension", expected.clone())
100            .add("another-extension", expected.clone());
101
102        let value = serde_json::to_value(&extensions).unwrap();
103        assert_eq!(value.get("x-some-extension"), Some(&expected));
104        assert_eq!(value.get("x-another-extension"), Some(&expected));
105    }
106
107    #[test]
108    fn extensions_from_iter() {
109        let expected = json!("value");
110        let extensions: Extensions = [
111            ("x-some-extension", expected.clone()),
112            ("another-extension", expected.clone()),
113        ]
114        .into_iter()
115        .collect();
116
117        assert_eq!(extensions.get("x-some-extension"), Some(&expected));
118        assert_eq!(extensions.get("x-another-extension"), Some(&expected));
119    }
120}