utoipa/openapi/
extensions.rs

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