salvo_oapi/openapi/schema/
all_of.rs

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