salvo_oapi/openapi/schema/
object.rs1use indexmap::IndexSet;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::AdditionalProperties;
6use crate::{Array, Deprecated, PropMap, RefOr, Schema, SchemaFormat, SchemaType, Xml};
7
8#[non_exhaustive]
15#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
16#[serde(rename_all = "camelCase")]
17pub struct Object {
18    #[serde(rename = "type", skip_serializing_if = "SchemaType::is_any_value")]
21    pub schema_type: SchemaType,
22
23    #[serde(skip_serializing_if = "Option::is_none")]
25    pub name: Option<String>,
26
27    #[serde(skip_serializing_if = "Option::is_none")]
29    pub format: Option<SchemaFormat>,
30
31    #[serde(skip_serializing_if = "Option::is_none")]
33    pub description: Option<String>,
34
35    #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
37    pub default_value: Option<Value>,
38
39    #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
41    pub enum_values: Vec<Value>,
42
43    #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
45    pub required: IndexSet<String>,
46
47    #[serde(default, skip_serializing_if = "PropMap::is_empty")]
55    pub properties: PropMap<String, RefOr<Schema>>,
56
57    #[serde(skip_serializing_if = "Option::is_none")]
59    pub additional_properties: Option<Box<AdditionalProperties<Schema>>>,
60
61    #[serde(skip_serializing_if = "Option::is_none")]
63    pub deprecated: Option<Deprecated>,
64
65    #[serde(skip_serializing_if = "Vec::is_empty", default)]
67    pub examples: Vec<Value>,
68
69    #[serde(skip_serializing_if = "Option::is_none")]
71    pub write_only: Option<bool>,
72
73    #[serde(skip_serializing_if = "Option::is_none")]
75    pub read_only: Option<bool>,
76
77    #[serde(skip_serializing_if = "Option::is_none")]
79    pub xml: Option<Xml>,
80
81    #[serde(skip_serializing_if = "Option::is_none")]
84    pub multiple_of: Option<f64>,
85
86    #[serde(skip_serializing_if = "Option::is_none")]
89    pub maximum: Option<f64>,
90
91    #[serde(skip_serializing_if = "Option::is_none")]
94    pub minimum: Option<f64>,
95
96    #[serde(skip_serializing_if = "Option::is_none")]
99    pub exclusive_maximum: Option<f64>,
100
101    #[serde(skip_serializing_if = "Option::is_none")]
104    pub exclusive_minimum: Option<f64>,
105
106    #[serde(skip_serializing_if = "Option::is_none")]
109    pub max_length: Option<usize>,
110
111    #[serde(skip_serializing_if = "Option::is_none")]
115    pub min_length: Option<usize>,
116
117    #[serde(skip_serializing_if = "Option::is_none")]
120    pub pattern: Option<String>,
121
122    #[serde(skip_serializing_if = "Option::is_none")]
124    pub max_properties: Option<usize>,
125
126    #[serde(skip_serializing_if = "Option::is_none")]
129    pub min_properties: Option<usize>,
130
131    #[serde(default, skip_serializing_if = "PropMap::is_empty", flatten)]
133    pub extensions: PropMap<String, serde_json::Value>,
134
135    #[serde(skip_serializing_if = "String::is_empty", default)]
144    pub content_encoding: String,
145
146    #[serde(skip_serializing_if = "String::is_empty", default)]
151    pub content_media_type: String,
152}
153
154impl Object {
155    #[must_use]
158    pub fn new() -> Self {
159        Default::default()
160    }
161
162    #[must_use]
170    pub fn with_type<T: Into<SchemaType>>(schema_type: T) -> Self {
171        Self {
172            schema_type: schema_type.into(),
173            ..Default::default()
174        }
175    }
176
177    #[must_use]
180    pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
181        self.schema_type = schema_type.into();
182        self
183    }
184
185    #[must_use]
187    pub fn format(mut self, format: SchemaFormat) -> Self {
188        self.format = Some(format);
189        self
190    }
191
192    #[must_use]
196    pub fn property<S: Into<String>, I: Into<RefOr<Schema>>>(
197        mut self,
198        property_name: S,
199        component: I,
200    ) -> Self {
201        self.properties
202            .insert(property_name.into(), component.into());
203
204        self
205    }
206
207    #[must_use]
209    pub fn additional_properties<I: Into<AdditionalProperties<Schema>>>(
210        mut self,
211        additional_properties: I,
212    ) -> Self {
213        self.additional_properties = Some(Box::new(additional_properties.into()));
214        self
215    }
216
217    #[must_use]
219    pub fn required(mut self, required_field: impl Into<String>) -> Self {
220        self.required.insert(required_field.into());
221        self
222    }
223
224    #[must_use]
226    pub fn name(mut self, name: impl Into<String>) -> Self {
227        self.name = Some(name.into());
228        self
229    }
230
231    #[must_use]
233    pub fn description(mut self, description: impl Into<String>) -> Self {
234        self.description = Some(description.into());
235        self
236    }
237
238    #[must_use]
240    pub fn default_value(mut self, default: Value) -> Self {
241        self.default_value = Some(default);
242        self
243    }
244
245    #[must_use]
247    pub fn deprecated(mut self, deprecated: Deprecated) -> Self {
248        self.deprecated = Some(deprecated);
249        self
250    }
251
252    #[must_use]
254    pub fn enum_values<I, E>(mut self, enum_values: I) -> Self
255    where
256        I: IntoIterator<Item = E>,
257        E: Into<Value>,
258    {
259        self.enum_values = enum_values
260            .into_iter()
261            .map(|enum_value| enum_value.into())
262            .collect();
263        self
264    }
265
266    #[must_use]
268    pub fn example<V: Into<Value>>(mut self, example: V) -> Self {
269        self.examples.push(example.into());
270        self
271    }
272
273    #[must_use]
275    pub fn examples<I: IntoIterator<Item = V>, V: Into<Value>>(mut self, examples: I) -> Self {
276        self.examples = examples.into_iter().map(Into::into).collect();
277        self
278    }
279
280    #[must_use]
282    pub fn write_only(mut self, write_only: bool) -> Self {
283        self.write_only = Some(write_only);
284        self
285    }
286
287    #[must_use]
289    pub fn read_only(mut self, read_only: bool) -> Self {
290        self.read_only = Some(read_only);
291        self
292    }
293
294    #[must_use]
296    pub fn xml(mut self, xml: Xml) -> Self {
297        self.xml = Some(xml);
298        self
299    }
300
301    #[must_use]
303    pub fn multiple_of(mut self, multiple_of: f64) -> Self {
304        self.multiple_of = Some(multiple_of);
305        self
306    }
307
308    #[must_use]
310    pub fn maximum(mut self, maximum: f64) -> Self {
311        self.maximum = Some(maximum);
312        self
313    }
314
315    #[must_use]
317    pub fn minimum(mut self, minimum: f64) -> Self {
318        self.minimum = Some(minimum);
319        self
320    }
321
322    #[must_use]
324    pub fn exclusive_maximum(mut self, exclusive_maximum: f64) -> Self {
325        self.exclusive_maximum = Some(exclusive_maximum);
326        self
327    }
328
329    #[must_use]
331    pub fn exclusive_minimum(mut self, exclusive_minimum: f64) -> Self {
332        self.exclusive_minimum = Some(exclusive_minimum);
333        self
334    }
335
336    #[must_use]
338    pub fn max_length(mut self, max_length: usize) -> Self {
339        self.max_length = Some(max_length);
340        self
341    }
342
343    #[must_use]
345    pub fn min_length(mut self, min_length: usize) -> Self {
346        self.min_length = Some(min_length);
347        self
348    }
349
350    #[must_use]
352    pub fn pattern<I: Into<String>>(mut self, pattern: I) -> Self {
353        self.pattern = Some(pattern.into());
354        self
355    }
356
357    #[must_use]
359    pub fn max_properties(mut self, max_properties: usize) -> Self {
360        self.max_properties = Some(max_properties);
361        self
362    }
363
364    #[must_use]
366    pub fn min_properties(mut self, min_properties: usize) -> Self {
367        self.min_properties = Some(min_properties);
368        self
369    }
370
371    #[must_use]
373    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
374        self.extensions.insert(key.into(), value);
375        self
376    }
377
378    #[must_use]
381    pub fn content_encoding<S: Into<String>>(mut self, content_encoding: S) -> Self {
382        self.content_encoding = content_encoding.into();
383        self
384    }
385
386    #[must_use]
389    pub fn content_media_type<S: Into<String>>(mut self, content_media_type: S) -> Self {
390        self.content_media_type = content_media_type.into();
391        self
392    }
393
394    #[must_use]
396    pub fn to_array(self) -> Array {
397        Array::new().items(self)
398    }
399}
400
401impl From<Object> for Schema {
402    fn from(s: Object) -> Self {
403        Self::Object(Box::new(s))
404    }
405}
406
407impl From<Object> for RefOr<Schema> {
410    fn from(obj: Object) -> Self {
411        Self::Type(Schema::Object(Box::new(obj)))
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use assert_json_diff::assert_json_eq;
418    use serde_json::json;
419
420    use super::*;
421    use crate::BasicType;
422
423    #[test]
424    fn test_build_string_object() {
425        let object = Object::new()
426            .schema_type(BasicType::String)
427            .deprecated(Deprecated::True)
428            .write_only(false)
429            .read_only(true)
430            .xml(Xml::new())
431            .max_length(10)
432            .min_length(1)
433            .pattern(r"^[a-z]+$");
434
435        assert_json_eq!(
436            object,
437            json!({
438                "type": "string",
439                "deprecated": true,
440                "readOnly": true,
441                "writeOnly": false,
442                "xml": {},
443                "minLength": 1,
444                "maxLength": 10,
445                "pattern": "^[a-z]+$"
446            })
447        );
448    }
449
450    #[test]
451    fn test_build_number_object() {
452        let object = Object::new()
453            .schema_type(BasicType::Number)
454            .deprecated(Deprecated::True)
455            .write_only(false)
456            .read_only(true)
457            .xml(Xml::new())
458            .multiple_of(10.0)
459            .minimum(0.0)
460            .maximum(1000.0)
461            .exclusive_minimum(0.0)
462            .exclusive_maximum(1000.0);
463
464        assert_json_eq!(
465            object,
466            json!({
467                "type": "number",
468                "deprecated": true,
469                "readOnly": true,
470                "writeOnly": false,
471                "xml": {},
472                "multipleOf": 10.0,
473                "minimum": 0.0,
474                "maximum": 1000.0,
475                "exclusiveMinimum": 0.0,
476                "exclusiveMaximum": 1000.0
477            })
478        );
479    }
480
481    #[test]
482    fn test_build_object_object() {
483        let object = Object::new()
484            .schema_type(BasicType::Object)
485            .deprecated(Deprecated::True)
486            .write_only(false)
487            .read_only(true)
488            .xml(Xml::new())
489            .min_properties(1)
490            .max_properties(10);
491
492        assert_json_eq!(
493            object,
494            json!({
495                "type": "object",
496                "deprecated": true,
497                "readOnly": true,
498                "writeOnly": false,
499                "xml": {},
500                "minProperties": 1,
501                "maxProperties": 10
502            })
503        );
504    }
505
506    #[test]
507    fn test_object_with_extensions() {
508        let expected = json!("value");
509        let json_value = Object::new().add_extension("x-some-extension", expected.clone());
510
511        let value = serde_json::to_value(&json_value).unwrap();
512        assert_eq!(value.get("x-some-extension"), Some(&expected));
513    }
514}