salvo_oapi/openapi/schema/
array.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::schema::BasicType;
5use crate::{Deprecated, PropMap, RefOr, Schema, SchemaType, Xml};
6
7/// Array represents [`Vec`] or [`slice`] type  of items.
8///
9/// See [`Schema::Array`] for more details.
10#[non_exhaustive]
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
12#[serde(rename_all = "camelCase")]
13pub struct Array {
14    /// Type will always be [`SchemaType::Array`]
15    #[serde(rename = "type")]
16    pub schema_type: SchemaType,
17
18    /// Changes the [`Array`] title.
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub title: Option<String>,
21
22    /// Schema representing the array items type.
23    pub items: Box<RefOr<Schema>>,
24
25    /// Description of the [`Array`]. Markdown syntax is supported.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub description: Option<String>,
28
29    /// Marks the [`Array`] deprecated.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub deprecated: Option<Deprecated>,
32
33    /// Examples shown in UI of the value for richer documentation.
34    #[serde(skip_serializing_if = "Vec::is_empty", default)]
35    pub examples: Vec<Value>,
36
37    /// Default value which is provided when user has not provided the input in Swagger UI.
38    #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
39    pub default_value: Option<Value>,
40
41    /// Max length of the array.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub max_items: Option<usize>,
44
45    /// Min length of the array.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub min_items: Option<usize>,
48
49    /// Setting this to `true` will validate successfully if all elements of this [`Array`] are
50    /// unique.
51    #[serde(default, skip_serializing_if = "super::is_false")]
52    pub unique_items: bool,
53
54    /// Xml format of the array.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub xml: Option<Xml>,
57
58    /// Optional extensions `x-something`.
59    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
60    pub extensions: PropMap<String, serde_json::Value>,
61}
62
63impl Default for Array {
64    fn default() -> Self {
65        Self {
66            title: Default::default(),
67            schema_type: BasicType::Array.into(),
68            unique_items: bool::default(),
69            items: Default::default(),
70            description: Default::default(),
71            deprecated: Default::default(),
72            examples: Default::default(),
73            default_value: Default::default(),
74            max_items: Default::default(),
75            min_items: Default::default(),
76            xml: Default::default(),
77            extensions: Default::default(),
78        }
79    }
80}
81
82impl Array {
83    /// Construct a new [`Array`] component from given [`Schema`].
84    ///
85    /// # Examples
86    ///
87    /// _**Create a `String` array component.**_
88    /// ```
89    /// # use salvo_oapi::schema::{Schema, Array, SchemaType, BasicType, Object};
90    /// let string_array = Array::new().items(Object::with_type(BasicType::String));
91    /// ```
92    pub fn new() -> Self {
93        Self::default()
94    }
95    /// Set [`Schema`] type for the [`Array`].
96    pub fn items<I: Into<RefOr<Schema>>>(mut self, items: I) -> Self {
97        self.items = Box::new(items.into());
98        self
99    }
100
101    /// Change type of the array e.g. to change type to _`string`_
102    /// use value `SchemaType::Type(Type::String)`.
103    ///
104    /// # Examples
105    ///
106    /// _**Make nullable string array.**_
107    /// ```rust
108    /// # use salvo_oapi::schema::{Array, BasicType, SchemaType, Object};
109    /// let _ = Array::new()
110    ///     .schema_type(SchemaType::from_iter([BasicType::Array, BasicType::Null]))
111    ///     .items(Object::with_type(BasicType::String));
112    /// ```
113    pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
114        self.schema_type = schema_type.into();
115        self
116    }
117
118    /// Add or change the title of the [`Array`].
119    pub fn title(mut self, title: impl Into<String>) -> Self {
120        self.title = Some(title.into());
121        self
122    }
123
124    /// Add or change description of the property. Markdown syntax is supported.
125    pub fn description(mut self, description: impl Into<String>) -> Self {
126        self.description = Some(description.into());
127        self
128    }
129
130    /// Add or change deprecated status for [`Array`].
131    pub fn deprecated(mut self, deprecated: Deprecated) -> Self {
132        self.deprecated = Some(deprecated);
133        self
134    }
135
136    /// Add or change example shown in UI of the value for richer documentation.
137    pub fn example<V: Into<Value>>(mut self, example: V) -> Self {
138        self.examples.push(example.into());
139        self
140    }
141
142    /// Add or change example shown in UI of the value for richer documentation.
143    pub fn examples<I: IntoIterator<Item = V>, V: Into<Value>>(mut self, examples: I) -> Self {
144        self.examples = examples.into_iter().map(Into::into).collect();
145        self
146    }
147
148    /// Add or change default value for the object which is provided when user has not provided the input in Swagger UI.
149    pub fn default_value(mut self, default: Value) -> Self {
150        self.default_value = Some(default);
151        self
152    }
153
154    /// Set maximum allowed length for [`Array`].
155    pub fn max_items(mut self, max_items: usize) -> Self {
156        self.max_items = Some(max_items);
157        self
158    }
159
160    /// Set minimum allowed length for [`Array`].
161    pub fn min_items(mut self, min_items: usize) -> Self {
162        self.min_items = Some(min_items);
163        self
164    }
165
166    /// Set or change whether [`Array`] should enforce all items to be unique.
167    pub fn unique_items(mut self, unique_items: bool) -> Self {
168        self.unique_items = unique_items;
169        self
170    }
171
172    /// Set [`Xml`] formatting for [`Array`].
173    pub fn xml(mut self, xml: Xml) -> Self {
174        self.xml = Some(xml);
175        self
176    }
177
178    /// Add openapi extension (`x-something`) for [`Array`].
179    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
180        self.extensions.insert(key.into(), value);
181        self
182    }
183}
184
185impl From<Array> for Schema {
186    fn from(array: Array) -> Self {
187        Self::Array(array)
188    }
189}
190
191impl From<Array> for RefOr<Schema> {
192    fn from(array: Array) -> Self {
193        Self::Type(Schema::Array(array))
194    }
195}
196
197impl ToArray for Array {}
198
199/// Trait for converting a type to [`Array`].
200pub trait ToArray
201where
202    RefOr<Schema>: From<Self>,
203    Self: Sized,
204{
205    /// Convert a type to [`Array`].
206    fn to_array(self) -> Array {
207        Array::new().items(self)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use assert_json_diff::assert_json_eq;
214    use serde_json::json;
215
216    use super::*;
217    use crate::Object;
218
219    #[test]
220    fn test_build_array() {
221        let array = Array::new()
222            .items(Object::with_type(BasicType::String))
223            .title("title")
224            .description("description")
225            .deprecated(Deprecated::False)
226            .examples([
227                Value::String("example1".to_string()),
228                Value::String("example2".to_string()),
229            ])
230            .default_value(Value::String("default".to_string()))
231            .max_items(10)
232            .min_items(1)
233            .unique_items(true)
234            .xml(Xml::new());
235
236        assert_json_eq!(
237            array,
238            json!({
239                "type": "array",
240                "items": {
241                    "type": "string"
242                },
243                "title": "title",
244                "description": "description",
245                "deprecated": false,
246                "examples": ["example1", "example2"],
247                "default": "default",
248                "maxItems": 10,
249                "minItems": 1,
250                "uniqueItems": true,
251                "xml": {},
252            })
253        )
254    }
255
256    #[test]
257    fn test_schema_from_array() {
258        let array = Array::default();
259        let schema = Schema::from(array);
260        assert_json_eq!(
261            schema,
262            json!({
263                "type": "array",
264                "items": {
265                    "type": "object"
266                }
267            })
268        )
269    }
270
271    #[test]
272    fn test_array_with_extensions() {
273        let expected = json!("value");
274        let json_value = Array::default().add_extension("x-some-extension", expected.clone());
275
276        let value = serde_json::to_value(&json_value).unwrap();
277        assert_eq!(value.get("x-some-extension"), Some(&expected));
278    }
279}