salvo_oapi/openapi/schema/
any_of.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::SchemaType;
5use crate::{Discriminator, PropMap, RefOr, Schema};
6
7/// AnyOf [Composite Object][allof] component holds
8/// multiple components together where API endpoint will return a combination of all of them.
9///
10/// See [`Schema::AnyOf`] for more details.
11///
12/// [allof]: https://spec.openapis.org/oas/latest.html#components-object
13#[non_exhaustive]
14#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
15pub struct AnyOf {
16    /// Components of _AnyOf_ component.
17    #[serde(rename = "anyOf")]
18    pub items: Vec<RefOr<Schema>>,
19
20    /// Type of [`AnyOf`] e.g. `SchemaType::basic(BasicType::Object)` for `object`.
21    ///
22    /// By default this is [`SchemaType::AnyValue`] as the type is defined by items
23    /// themselves.
24    #[serde(
25        rename = "type",
26        default = "SchemaType::any",
27        skip_serializing_if = "SchemaType::is_any_value"
28    )]
29    pub schema_type: SchemaType,
30
31    /// Changes the [`AnyOf`] title.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub title: Option<String>,
34
35    /// Description of the [`AnyOf`]. Markdown syntax is supported.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub description: Option<String>,
38
39    /// Default value which is provided when user has not provided the input in Swagger UI.
40    #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
41    pub default_value: Option<Value>,
42
43    /// Examples shown in UI of the value for richer documentation.
44    #[serde(skip_serializing_if = "Vec::is_empty", default)]
45    pub examples: Vec<Value>,
46
47    /// Optional discriminator field can be used to aid deserialization, serialization and validation of a
48    /// specific schema.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub discriminator: Option<Discriminator>,
51
52    /// Optional extensions `x-something`.
53    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
54    pub extensions: PropMap<String, serde_json::Value>,
55}
56
57impl Default for AnyOf {
58    fn default() -> Self {
59        Self {
60            items: Default::default(),
61            schema_type: SchemaType::AnyValue,
62            title: None,
63            description: Default::default(),
64            default_value: Default::default(),
65            examples: Default::default(),
66            discriminator: Default::default(),
67            extensions: Default::default(),
68        }
69    }
70}
71
72impl AnyOf {
73    /// Construct a new empty [`AnyOf`]. This is effectively same as calling [`AnyOf::default`].
74    #[must_use]
75    pub fn new() -> Self {
76        Default::default()
77    }
78
79    /// Construct a new [`AnyOf`] component with given capacity.
80    ///
81    /// AnyOf component is then able to contain number of components without
82    /// reallocating.
83    ///
84    /// # Examples
85    ///
86    /// Create [`AnyOf`] component with initial capacity of 5.
87    /// ```
88    /// # use salvo_oapi::schema::AnyOf;
89    /// let one_of = AnyOf::with_capacity(5);
90    /// ```
91    #[must_use]
92    pub fn with_capacity(capacity: usize) -> Self {
93        Self {
94            items: Vec::with_capacity(capacity),
95            ..Default::default()
96        }
97    }
98    /// Adds a given [`Schema`] to [`AnyOf`] [Composite Object][composite]
99    ///
100    /// [composite]: https://spec.openapis.org/oas/latest.html#components-object
101    #[must_use]
102    pub fn item<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
103        self.items.push(component.into());
104        self
105    }
106
107    /// Add or change type of the object e.g. to change type to _`string`_
108    /// use value `SchemaType::Type(Type::String)`.
109    #[must_use]
110    pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
111        self.schema_type = schema_type.into();
112        self
113    }
114
115    /// Add or change the title of the [`AnyOf`].
116    #[must_use]
117    pub fn title(mut self, title: impl Into<String>) -> Self {
118        self.title = Some(title.into());
119        self
120    }
121
122    /// Add or change optional description for `AnyOf` component.
123    #[must_use]
124    pub fn description(mut self, description: impl Into<String>) -> Self {
125        self.description = Some(description.into());
126        self
127    }
128
129    /// Add or change default value for the object which is provided when user has not provided the input in Swagger UI.
130    #[must_use]
131    pub fn default_value(mut self, default: Value) -> Self {
132        self.default_value = Some(default);
133        self
134    }
135
136    /// Add or change example shown in UI of the value for richer documentation.
137    #[must_use]
138    pub fn add_example<V: Into<Value>>(mut self, example: V) -> Self {
139        self.examples.push(example.into());
140        self
141    }
142
143    /// Add or change discriminator field of the composite [`AnyOf`] type.
144    #[must_use]
145    pub fn discriminator(mut self, discriminator: Discriminator) -> Self {
146        self.discriminator = Some(discriminator);
147        self
148    }
149
150    /// Add openapi extension (`x-something`) for [`AnyOf`].
151    #[must_use]
152    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
153        self.extensions.insert(key.into(), value);
154        self
155    }
156}
157
158impl From<AnyOf> for Schema {
159    fn from(one_of: AnyOf) -> Self {
160        Self::AnyOf(one_of)
161    }
162}
163
164impl From<AnyOf> for RefOr<Schema> {
165    fn from(one_of: AnyOf) -> Self {
166        Self::Type(Schema::AnyOf(one_of))
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use assert_json_diff::assert_json_eq;
173    use serde_json::json;
174
175    use super::*;
176
177    #[test]
178    fn test_build_any_of() {
179        let any_of = AnyOf::with_capacity(5)
180            .title("title")
181            .description("description")
182            .default_value(Value::String("default".to_owned()))
183            .add_example(Value::String("example1".to_owned()))
184            .add_example(Value::String("example2".to_owned()))
185            .discriminator(Discriminator::new("discriminator".to_owned()));
186
187        assert_eq!(any_of.items.len(), 0);
188        assert_eq!(any_of.items.capacity(), 5);
189        assert_json_eq!(
190            any_of,
191            json!({
192                "anyOf": [],
193                "title": "title",
194                "description": "description",
195                "default": "default",
196                "examples": ["example1", "example2"],
197                "discriminator": {
198                    "propertyName": "discriminator"
199                }
200            })
201        )
202    }
203
204    #[test]
205    fn test_schema_from_any_of() {
206        let any_of = AnyOf::new();
207        let schema = Schema::from(any_of);
208        assert_json_eq!(
209            schema,
210            json!({
211                "anyOf": []
212            })
213        )
214    }
215
216    #[test]
217    fn test_refor_schema_from_any_of() {
218        let any_of = AnyOf::new();
219        let ref_or: RefOr<Schema> = RefOr::from(any_of);
220        assert_json_eq!(
221            ref_or,
222            json!({
223                "anyOf": []
224            })
225        )
226    }
227
228    #[test]
229    fn test_anyof_with_extensions() {
230        let expected = json!("value");
231        let json_value = AnyOf::new().add_extension("x-some-extension", expected.clone());
232
233        let value = serde_json::to_value(&json_value).unwrap();
234        assert_eq!(value.get("x-some-extension"), Some(&expected));
235    }
236}