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