Skip to main content

salvo_oapi/openapi/schema/
array.rs

1use serde::de::Visitor;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use crate::schema::{AllOf, AnyOf, BasicType, Object, OneOf, Ref};
6use crate::{Deprecated, PropMap, RefOr, Schema, SchemaType, Xml};
7
8/// Represents [`Array`] items in [JSON Schema Array][json_schema_array].
9///
10/// [json_schema_array]: <https://json-schema.org/understanding-json-schema/reference/array#items>
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
12#[serde(untagged)]
13pub enum ArrayItems {
14    /// Defines [`Array::items`] as [`RefOr::T(Schema)`]. This is the default for [`Array`].
15    RefOrSchema(Box<RefOr<Schema>>),
16    /// Defines [`Array::items`] as `false` indicating that no extra items are allowed to the
17    /// [`Array`]. This can be used together with [`Array::prefix_items`] to disallow [additional
18    /// items][additional_items] in [`Array`].
19    ///
20    /// [additional_items]: <https://json-schema.org/understanding-json-schema/reference/array#additionalitems>
21    #[serde(with = "array_items_false")]
22    False,
23}
24
25impl Default for ArrayItems {
26    fn default() -> Self {
27        Self::RefOrSchema(Box::new(Object::with_type(BasicType::Object).into()))
28    }
29}
30
31impl From<RefOr<Schema>> for ArrayItems {
32    fn from(value: RefOr<Schema>) -> Self {
33        Self::RefOrSchema(Box::new(value))
34    }
35}
36
37impl From<Schema> for ArrayItems {
38    fn from(value: Schema) -> Self {
39        Self::RefOrSchema(Box::new(RefOr::Type(value)))
40    }
41}
42
43impl From<Object> for ArrayItems {
44    fn from(value: Object) -> Self {
45        Self::RefOrSchema(Box::new(value.into()))
46    }
47}
48
49impl From<Ref> for ArrayItems {
50    fn from(value: Ref) -> Self {
51        Self::RefOrSchema(Box::new(value.into()))
52    }
53}
54
55impl From<AllOf> for ArrayItems {
56    fn from(value: AllOf) -> Self {
57        Self::RefOrSchema(Box::new(value.into()))
58    }
59}
60
61impl From<AnyOf> for ArrayItems {
62    fn from(value: AnyOf) -> Self {
63        Self::RefOrSchema(Box::new(value.into()))
64    }
65}
66
67impl From<OneOf> for ArrayItems {
68    fn from(value: OneOf) -> Self {
69        Self::RefOrSchema(Box::new(value.into()))
70    }
71}
72
73impl From<Array> for ArrayItems {
74    fn from(value: Array) -> Self {
75        Self::RefOrSchema(Box::new(value.into()))
76    }
77}
78
79mod array_items_false {
80    use super::*;
81
82    pub(super) fn serialize<S: serde::Serializer>(serializer: S) -> Result<S::Ok, S::Error> {
83        serializer.serialize_bool(false)
84    }
85
86    pub(super) fn deserialize<'de, D: serde::Deserializer<'de>>(
87        deserializer: D,
88    ) -> Result<(), D::Error> {
89        struct ItemsFalseVisitor;
90
91        impl<'de> Visitor<'de> for ItemsFalseVisitor {
92            type Value = ();
93            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
94            where
95                E: serde::de::Error,
96            {
97                if !v {
98                    Ok(())
99                } else {
100                    Err(serde::de::Error::custom(format!(
101                        "invalid boolean value: {v}, expected false"
102                    )))
103                }
104            }
105
106            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
107                formatter.write_str("expected boolean false")
108            }
109        }
110
111        deserializer.deserialize_bool(ItemsFalseVisitor)
112    }
113}
114
115/// Array represents [`Vec`] or [`slice`] type  of items.
116///
117/// See [`Schema::Array`] for more details.
118#[non_exhaustive]
119#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
120#[serde(rename_all = "camelCase")]
121pub struct Array {
122    /// Type will always be [`SchemaType::Array`]
123    #[serde(rename = "type")]
124    pub schema_type: SchemaType,
125
126    /// Changes the [`Array`] title.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub title: Option<String>,
129
130    /// Schema representing the array items type.
131    pub items: ArrayItems,
132
133    /// Prefix items of [`Array`] is used to define item validation of tuples according to
134    /// [JSON schema item validation][item_validation].
135    ///
136    /// [item_validation]: <https://json-schema.org/understanding-json-schema/reference/array#tupleValidation>
137    #[serde(skip_serializing_if = "Vec::is_empty", default)]
138    pub prefix_items: Vec<RefOr<Schema>>,
139
140    /// Description of the [`Array`]. Markdown syntax is supported.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub description: Option<String>,
143
144    /// Marks the [`Array`] deprecated.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub deprecated: Option<Deprecated>,
147
148    /// Examples shown in UI of the value for richer documentation.
149    #[serde(skip_serializing_if = "Vec::is_empty", default)]
150    pub examples: Vec<Value>,
151
152    /// Default value which is provided when user has not provided the input in Swagger UI.
153    #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
154    pub default_value: Option<Value>,
155
156    /// Max length of the array.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub max_items: Option<usize>,
159
160    /// Min length of the array.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub min_items: Option<usize>,
163
164    /// Setting this to `true` will validate successfully if all elements of this [`Array`] are
165    /// unique.
166    #[serde(default, skip_serializing_if = "super::is_false")]
167    pub unique_items: bool,
168
169    /// Xml format of the array.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub xml: Option<Xml>,
172
173    /// The `content_encoding` keyword specifies the encoding used to store the contents, as
174    /// specified in [RFC 2054, part 6.1](https://tools.ietf.org/html/rfc2045) and [RFC 4648](RFC 2054, part 6.1).
175    ///
176    /// See more details at <https://json-schema.org/understanding-json-schema/reference/non_json_data#contentencoding>
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub content_encoding: Option<String>,
179
180    /// The _`content_media_type`_ keyword specifies the MIME type of the contents of a string,
181    /// as described in [RFC 2046](https://tools.ietf.org/html/rfc2046).
182    ///
183    /// See more details at <https://json-schema.org/understanding-json-schema/reference/non_json_data#contentmediatype>
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub content_media_type: Option<String>,
186
187    /// Optional extensions `x-something`.
188    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
189    pub extensions: PropMap<String, serde_json::Value>,
190}
191
192impl Default for Array {
193    fn default() -> Self {
194        Self {
195            title: Default::default(),
196            schema_type: BasicType::Array.into(),
197            unique_items: bool::default(),
198            items: Default::default(),
199            prefix_items: Vec::default(),
200            description: Default::default(),
201            deprecated: Default::default(),
202            examples: Default::default(),
203            default_value: Default::default(),
204            max_items: Default::default(),
205            min_items: Default::default(),
206            xml: Default::default(),
207            content_encoding: Default::default(),
208            content_media_type: Default::default(),
209            extensions: Default::default(),
210        }
211    }
212}
213
214impl Array {
215    /// Construct a new [`Array`] component from given [`Schema`].
216    ///
217    /// # Examples
218    ///
219    /// _**Create a `String` array component.**_
220    /// ```
221    /// # use salvo_oapi::schema::{Schema, Array, SchemaType, BasicType, Object};
222    /// let string_array = Array::new().items(Object::with_type(BasicType::String));
223    /// ```
224    #[must_use]
225    pub fn new() -> Self {
226        Self::default()
227    }
228    /// Set [`Schema`] type for the [`Array`].
229    #[must_use]
230    pub fn items<I: Into<ArrayItems>>(mut self, items: I) -> Self {
231        self.items = items.into();
232        self
233    }
234
235    /// Add prefix items of [`Array`] to define item validation of tuples according to
236    /// [JSON schema item validation][item_validation].
237    ///
238    /// [item_validation]: <https://json-schema.org/understanding-json-schema/reference/array#tupleValidation>
239    #[must_use]
240    pub fn prefix_items<I: IntoIterator<Item = S>, S: Into<RefOr<Schema>>>(
241        mut self,
242        items: I,
243    ) -> Self {
244        self.prefix_items = items
245            .into_iter()
246            .map(|item| item.into())
247            .collect::<Vec<_>>();
248        self
249    }
250
251    /// Change type of the array e.g. to change type to _`string`_
252    /// use value `SchemaType::Type(Type::String)`.
253    ///
254    /// # Examples
255    ///
256    /// _**Make nullable string array.**_
257    /// ```rust
258    /// # use salvo_oapi::schema::{Array, BasicType, SchemaType, Object};
259    /// let _ = Array::new()
260    ///     .schema_type(SchemaType::from_iter([BasicType::Array, BasicType::Null]))
261    ///     .items(Object::with_type(BasicType::String));
262    /// ```
263    #[must_use]
264    pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
265        self.schema_type = schema_type.into();
266        self
267    }
268
269    /// Add or change the title of the [`Array`].
270    #[must_use]
271    pub fn title(mut self, title: impl Into<String>) -> Self {
272        self.title = Some(title.into());
273        self
274    }
275
276    /// Add or change description of the property. Markdown syntax is supported.
277    #[must_use]
278    pub fn description(mut self, description: impl Into<String>) -> Self {
279        self.description = Some(description.into());
280        self
281    }
282
283    /// Add or change deprecated status for [`Array`].
284    #[must_use]
285    pub fn deprecated(mut self, deprecated: Deprecated) -> Self {
286        self.deprecated = Some(deprecated);
287        self
288    }
289
290    /// Add or change example shown in UI of the value for richer documentation.
291    #[must_use]
292    pub fn example<V: Into<Value>>(mut self, example: V) -> Self {
293        self.examples.push(example.into());
294        self
295    }
296
297    /// Add or change example shown in UI of the value for richer documentation.
298    #[must_use]
299    pub fn examples<I: IntoIterator<Item = V>, V: Into<Value>>(mut self, examples: I) -> Self {
300        self.examples = examples.into_iter().map(Into::into).collect();
301        self
302    }
303
304    /// Add or change default value for the object which is provided when user has not provided the
305    /// input in Swagger UI.
306    #[must_use]
307    pub fn default_value(mut self, default: Value) -> Self {
308        self.default_value = Some(default);
309        self
310    }
311
312    /// Set maximum allowed length for [`Array`].
313    #[must_use]
314    pub fn max_items(mut self, max_items: usize) -> Self {
315        self.max_items = Some(max_items);
316        self
317    }
318
319    /// Set minimum allowed length for [`Array`].
320    #[must_use]
321    pub fn min_items(mut self, min_items: usize) -> Self {
322        self.min_items = Some(min_items);
323        self
324    }
325
326    /// Set or change whether [`Array`] should enforce all items to be unique.
327    #[must_use]
328    pub fn unique_items(mut self, unique_items: bool) -> Self {
329        self.unique_items = unique_items;
330        self
331    }
332
333    /// Set [`Xml`] formatting for [`Array`].
334    #[must_use]
335    pub fn xml(mut self, xml: Xml) -> Self {
336        self.xml = Some(xml);
337        self
338    }
339
340    /// Set or change [`Array::content_encoding`]. Typically left empty but could be `base64` for
341    /// example.
342    #[must_use]
343    pub fn content_encoding<S: Into<String>>(mut self, content_encoding: S) -> Self {
344        self.content_encoding = Some(content_encoding.into());
345        self
346    }
347
348    /// Set or change [`Array::content_media_type`]. Value must be valid MIME type e.g.
349    /// `application/json`.
350    #[must_use]
351    pub fn content_media_type<S: Into<String>>(mut self, content_media_type: S) -> Self {
352        self.content_media_type = Some(content_media_type.into());
353        self
354    }
355
356    /// Add openapi extension (`x-something`) for [`Array`].
357    #[must_use]
358    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
359        self.extensions.insert(key.into(), value);
360        self
361    }
362}
363
364impl From<Array> for Schema {
365    fn from(array: Array) -> Self {
366        Self::Array(array)
367    }
368}
369
370impl From<Array> for RefOr<Schema> {
371    fn from(array: Array) -> Self {
372        Self::Type(Schema::Array(array))
373    }
374}
375
376// impl ToArray for Array {}
377
378// /// Trait for converting a type to [`Array`].
379// pub trait ToArray
380// where
381//     RefOr<Schema>: From<Self>,
382//     Self: Sized,
383// {
384//     /// Convert a type to [`Array`].
385//     pub fn to_array(self) -> Array {
386//         Array::new().items(self)
387//     }
388// }
389
390#[cfg(test)]
391mod tests {
392    use assert_json_diff::assert_json_eq;
393    use serde_json::json;
394
395    use super::*;
396    use crate::Object;
397
398    #[test]
399    fn test_build_array() {
400        let array = Array::new()
401            .items(Object::with_type(BasicType::String))
402            .title("title")
403            .description("description")
404            .deprecated(Deprecated::False)
405            .examples([
406                Value::String("example1".to_owned()),
407                Value::String("example2".to_owned()),
408            ])
409            .default_value(Value::String("default".to_owned()))
410            .max_items(10)
411            .min_items(1)
412            .unique_items(true)
413            .xml(Xml::new());
414
415        assert_json_eq!(
416            array,
417            json!({
418                "type": "array",
419                "items": {
420                    "type": "string"
421                },
422                "title": "title",
423                "description": "description",
424                "deprecated": false,
425                "examples": ["example1", "example2"],
426                "default": "default",
427                "maxItems": 10,
428                "minItems": 1,
429                "uniqueItems": true,
430                "xml": {},
431            })
432        )
433    }
434
435    #[test]
436    fn test_schema_from_array() {
437        let array = Array::default();
438        let schema = Schema::from(array);
439        assert_json_eq!(
440            schema,
441            json!({
442                "type": "array",
443                "items": {
444                    "type": "object"
445                }
446            })
447        )
448    }
449
450    #[test]
451    fn test_array_with_extensions() {
452        let expected = json!("value");
453        let json_value = Array::default().add_extension("x-some-extension", expected.clone());
454
455        let value = serde_json::to_value(&json_value).unwrap();
456        assert_eq!(value.get("x-some-extension"), Some(&expected));
457    }
458
459    #[test]
460    fn test_array_with_prefix_items() {
461        let array = Array::new().items(ArrayItems::False).prefix_items([
462            Object::with_type(BasicType::String),
463            Object::with_type(BasicType::Number),
464        ]);
465
466        assert_json_eq!(
467            array,
468            json!({
469                "type": "array",
470                "items": false,
471                "prefixItems": [
472                    { "type": "string" },
473                    { "type": "number" }
474                ]
475            })
476        )
477    }
478
479    #[test]
480    fn test_array_items_false_deserialize() {
481        let json = json!({
482            "type": "array",
483            "items": false,
484            "prefixItems": [
485                { "type": "string" }
486            ]
487        });
488        let array: Array = serde_json::from_value(json).unwrap();
489        assert_eq!(array.items, ArrayItems::False);
490        assert_eq!(array.prefix_items.len(), 1);
491    }
492}