salvo_oapi/openapi/schema/
object.rs1use indexmap::IndexSet;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::AdditionalProperties;
6use super::number::Number;
7use crate::{Array, Deprecated, PropMap, RefOr, Schema, SchemaFormat, SchemaType, Xml};
8
9#[non_exhaustive]
17#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
18#[serde(rename_all = "camelCase")]
19pub struct Object {
20 #[serde(rename = "type", skip_serializing_if = "SchemaType::is_any_value")]
23 pub schema_type: SchemaType,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub name: Option<String>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub format: Option<SchemaFormat>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description: Option<String>,
36
37 #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
39 pub default_value: Option<Value>,
40
41 #[serde(default, rename = "enum", skip_serializing_if = "Option::is_none")]
43 pub enum_values: Option<Vec<Value>>,
44
45 #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
47 pub required: IndexSet<String>,
48
49 #[serde(default, skip_serializing_if = "PropMap::is_empty")]
57 pub properties: PropMap<String, RefOr<Schema>>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub additional_properties: Option<Box<AdditionalProperties<Schema>>>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
66 pub property_names: Option<Box<RefOr<Schema>>>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub deprecated: Option<Deprecated>,
71
72 #[serde(skip_serializing_if = "Vec::is_empty", default)]
74 pub examples: Vec<Value>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub write_only: Option<bool>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub read_only: Option<bool>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub xml: Option<Xml>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
91 pub multiple_of: Option<Number>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
96 pub maximum: Option<Number>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
101 pub minimum: Option<Number>,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
106 pub exclusive_maximum: Option<Number>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
111 pub exclusive_minimum: Option<Number>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
117 pub max_length: Option<usize>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
123 pub min_length: Option<usize>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
128 pub pattern: Option<String>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub max_properties: Option<usize>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub min_properties: Option<usize>,
138
139 #[serde(default, skip_serializing_if = "PropMap::is_empty", flatten)]
141 pub extensions: PropMap<String, serde_json::Value>,
142
143 #[serde(skip_serializing_if = "String::is_empty", default)]
152 pub content_encoding: String,
153
154 #[serde(skip_serializing_if = "String::is_empty", default)]
159 pub content_media_type: String,
160}
161
162impl Object {
163 #[must_use]
166 pub fn new() -> Self {
167 Default::default()
168 }
169
170 #[must_use]
178 pub fn with_type<T: Into<SchemaType>>(schema_type: T) -> Self {
179 Self {
180 schema_type: schema_type.into(),
181 ..Default::default()
182 }
183 }
184
185 #[must_use]
188 pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
189 self.schema_type = schema_type.into();
190 self
191 }
192
193 #[must_use]
195 pub fn format(mut self, format: SchemaFormat) -> Self {
196 self.format = Some(format);
197 self
198 }
199
200 #[must_use]
204 pub fn property<S: Into<String>, I: Into<RefOr<Schema>>>(
205 mut self,
206 property_name: S,
207 component: I,
208 ) -> Self {
209 self.properties
210 .insert(property_name.into(), component.into());
211
212 self
213 }
214
215 #[must_use]
217 pub fn additional_properties<I: Into<AdditionalProperties<Schema>>>(
218 mut self,
219 additional_properties: I,
220 ) -> Self {
221 self.additional_properties = Some(Box::new(additional_properties.into()));
222 self
223 }
224
225 #[must_use]
228 pub fn property_names(mut self, property_names: impl Into<RefOr<Schema>>) -> Self {
229 self.property_names = Some(Box::new(property_names.into()));
230 self
231 }
232
233 #[must_use]
235 pub fn required(mut self, required_field: impl Into<String>) -> Self {
236 self.required.insert(required_field.into());
237 self
238 }
239
240 #[must_use]
242 pub fn name(mut self, name: impl Into<String>) -> Self {
243 self.name = Some(name.into());
244 self
245 }
246
247 #[must_use]
249 pub fn description(mut self, description: impl Into<String>) -> Self {
250 self.description = Some(description.into());
251 self
252 }
253
254 #[must_use]
257 pub fn default_value(mut self, default: Value) -> Self {
258 self.default_value = Some(default);
259 self
260 }
261
262 #[must_use]
264 pub fn deprecated(mut self, deprecated: Deprecated) -> Self {
265 self.deprecated = Some(deprecated);
266 self
267 }
268
269 #[must_use]
271 pub fn enum_values<I, E>(mut self, enum_values: I) -> Self
272 where
273 I: IntoIterator<Item = E>,
274 E: Into<Value>,
275 {
276 self.enum_values = Some(
277 enum_values
278 .into_iter()
279 .map(|enum_value| enum_value.into())
280 .collect(),
281 );
282 self
283 }
284
285 #[must_use]
287 pub fn example<V: Into<Value>>(mut self, example: V) -> Self {
288 self.examples.push(example.into());
289 self
290 }
291
292 #[must_use]
294 pub fn examples<I: IntoIterator<Item = V>, V: Into<Value>>(mut self, examples: I) -> Self {
295 self.examples = examples.into_iter().map(Into::into).collect();
296 self
297 }
298
299 #[must_use]
301 pub fn write_only(mut self, write_only: bool) -> Self {
302 self.write_only = Some(write_only);
303 self
304 }
305
306 #[must_use]
308 pub fn read_only(mut self, read_only: bool) -> Self {
309 self.read_only = Some(read_only);
310 self
311 }
312
313 #[must_use]
315 pub fn xml(mut self, xml: Xml) -> Self {
316 self.xml = Some(xml);
317 self
318 }
319
320 #[must_use]
322 pub fn multiple_of<N: Into<Number>>(mut self, multiple_of: N) -> Self {
323 self.multiple_of = Some(multiple_of.into());
324 self
325 }
326
327 #[must_use]
329 pub fn maximum<N: Into<Number>>(mut self, maximum: N) -> Self {
330 self.maximum = Some(maximum.into());
331 self
332 }
333
334 #[must_use]
336 pub fn minimum<N: Into<Number>>(mut self, minimum: N) -> Self {
337 self.minimum = Some(minimum.into());
338 self
339 }
340
341 #[must_use]
343 pub fn exclusive_maximum<N: Into<Number>>(mut self, exclusive_maximum: N) -> Self {
344 self.exclusive_maximum = Some(exclusive_maximum.into());
345 self
346 }
347
348 #[must_use]
350 pub fn exclusive_minimum<N: Into<Number>>(mut self, exclusive_minimum: N) -> Self {
351 self.exclusive_minimum = Some(exclusive_minimum.into());
352 self
353 }
354
355 #[must_use]
357 pub fn max_length(mut self, max_length: usize) -> Self {
358 self.max_length = Some(max_length);
359 self
360 }
361
362 #[must_use]
364 pub fn min_length(mut self, min_length: usize) -> Self {
365 self.min_length = Some(min_length);
366 self
367 }
368
369 #[must_use]
371 pub fn pattern<I: Into<String>>(mut self, pattern: I) -> Self {
372 self.pattern = Some(pattern.into());
373 self
374 }
375
376 #[must_use]
378 pub fn max_properties(mut self, max_properties: usize) -> Self {
379 self.max_properties = Some(max_properties);
380 self
381 }
382
383 #[must_use]
385 pub fn min_properties(mut self, min_properties: usize) -> Self {
386 self.min_properties = Some(min_properties);
387 self
388 }
389
390 #[must_use]
392 pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
393 self.extensions.insert(key.into(), value);
394 self
395 }
396
397 #[must_use]
400 pub fn content_encoding<S: Into<String>>(mut self, content_encoding: S) -> Self {
401 self.content_encoding = content_encoding.into();
402 self
403 }
404
405 #[must_use]
408 pub fn content_media_type<S: Into<String>>(mut self, content_media_type: S) -> Self {
409 self.content_media_type = content_media_type.into();
410 self
411 }
412
413 #[must_use]
415 pub fn to_array(self) -> Array {
416 Array::new().items(self)
417 }
418}
419
420impl From<Object> for Schema {
421 fn from(s: Object) -> Self {
422 Self::Object(Box::new(s))
423 }
424}
425
426impl From<Object> for RefOr<Schema> {
429 fn from(obj: Object) -> Self {
430 Self::Type(Schema::Object(Box::new(obj)))
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use assert_json_diff::assert_json_eq;
437 use serde_json::json;
438
439 use super::*;
440 use crate::BasicType;
441
442 #[test]
443 fn test_build_string_object() {
444 let object = Object::new()
445 .schema_type(BasicType::String)
446 .deprecated(Deprecated::True)
447 .write_only(false)
448 .read_only(true)
449 .xml(Xml::new())
450 .max_length(10)
451 .min_length(1)
452 .pattern(r"^[a-z]+$");
453
454 assert_json_eq!(
455 object,
456 json!({
457 "type": "string",
458 "deprecated": true,
459 "readOnly": true,
460 "writeOnly": false,
461 "xml": {},
462 "minLength": 1,
463 "maxLength": 10,
464 "pattern": "^[a-z]+$"
465 })
466 );
467 }
468
469 #[test]
470 fn test_build_number_object() {
471 let object = Object::new()
472 .schema_type(BasicType::Number)
473 .deprecated(Deprecated::True)
474 .write_only(false)
475 .read_only(true)
476 .xml(Xml::new())
477 .multiple_of(10.0)
478 .minimum(0.0)
479 .maximum(1000.0)
480 .exclusive_minimum(0.0)
481 .exclusive_maximum(1000.0);
482
483 assert_json_eq!(
484 object,
485 json!({
486 "type": "number",
487 "deprecated": true,
488 "readOnly": true,
489 "writeOnly": false,
490 "xml": {},
491 "multipleOf": 10,
492 "minimum": 0,
493 "maximum": 1000,
494 "exclusiveMinimum": 0,
495 "exclusiveMaximum": 1000
496 })
497 );
498 }
499
500 #[test]
501 fn test_build_object_object() {
502 let object = Object::new()
503 .schema_type(BasicType::Object)
504 .deprecated(Deprecated::True)
505 .write_only(false)
506 .read_only(true)
507 .xml(Xml::new())
508 .min_properties(1)
509 .max_properties(10);
510
511 assert_json_eq!(
512 object,
513 json!({
514 "type": "object",
515 "deprecated": true,
516 "readOnly": true,
517 "writeOnly": false,
518 "xml": {},
519 "minProperties": 1,
520 "maxProperties": 10
521 })
522 );
523 }
524
525 #[test]
526 fn test_object_with_extensions() {
527 let expected = json!("value");
528 let json_value = Object::new().add_extension("x-some-extension", expected.clone());
529
530 let value = serde_json::to_value(&json_value).unwrap();
531 assert_eq!(value.get("x-some-extension"), Some(&expected));
532 }
533}