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]
16#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
17#[serde(rename_all = "camelCase")]
18pub struct Object {
19 #[serde(rename = "type", skip_serializing_if = "SchemaType::is_any_value")]
22 pub schema_type: SchemaType,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub name: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub format: Option<SchemaFormat>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub description: Option<String>,
35
36 #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
38 pub default_value: Option<Value>,
39
40 #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
42 pub enum_values: Vec<Value>,
43
44 #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
46 pub required: IndexSet<String>,
47
48 #[serde(default, skip_serializing_if = "PropMap::is_empty")]
56 pub properties: PropMap<String, RefOr<Schema>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub additional_properties: Option<Box<AdditionalProperties<Schema>>>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub deprecated: Option<Deprecated>,
65
66 #[serde(skip_serializing_if = "Vec::is_empty", default)]
68 pub examples: Vec<Value>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub write_only: Option<bool>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub read_only: Option<bool>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub xml: Option<Xml>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
85 pub multiple_of: Option<f64>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
90 pub maximum: Option<f64>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
95 pub minimum: Option<f64>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
100 pub exclusive_maximum: Option<f64>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
105 pub exclusive_minimum: Option<f64>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
111 pub max_length: Option<usize>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
117 pub min_length: Option<usize>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
122 pub pattern: Option<String>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub max_properties: Option<usize>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
131 pub min_properties: Option<usize>,
132
133 #[serde(default, skip_serializing_if = "PropMap::is_empty", flatten)]
135 pub extensions: PropMap<String, serde_json::Value>,
136
137 #[serde(skip_serializing_if = "String::is_empty", default)]
146 pub content_encoding: String,
147
148 #[serde(skip_serializing_if = "String::is_empty", default)]
153 pub content_media_type: String,
154}
155
156impl Object {
157 #[must_use]
160 pub fn new() -> Self {
161 Default::default()
162 }
163
164 #[must_use]
172 pub fn with_type<T: Into<SchemaType>>(schema_type: T) -> Self {
173 Self {
174 schema_type: schema_type.into(),
175 ..Default::default()
176 }
177 }
178
179 #[must_use]
182 pub fn schema_type<T: Into<SchemaType>>(mut self, schema_type: T) -> Self {
183 self.schema_type = schema_type.into();
184 self
185 }
186
187 #[must_use]
189 pub fn format(mut self, format: SchemaFormat) -> Self {
190 self.format = Some(format);
191 self
192 }
193
194 #[must_use]
198 pub fn property<S: Into<String>, I: Into<RefOr<Schema>>>(
199 mut self,
200 property_name: S,
201 component: I,
202 ) -> Self {
203 self.properties
204 .insert(property_name.into(), component.into());
205
206 self
207 }
208
209 #[must_use]
211 pub fn additional_properties<I: Into<AdditionalProperties<Schema>>>(
212 mut self,
213 additional_properties: I,
214 ) -> Self {
215 self.additional_properties = Some(Box::new(additional_properties.into()));
216 self
217 }
218
219 #[must_use]
221 pub fn required(mut self, required_field: impl Into<String>) -> Self {
222 self.required.insert(required_field.into());
223 self
224 }
225
226 #[must_use]
228 pub fn name(mut self, name: impl Into<String>) -> Self {
229 self.name = Some(name.into());
230 self
231 }
232
233 #[must_use]
235 pub fn description(mut self, description: impl Into<String>) -> Self {
236 self.description = Some(description.into());
237 self
238 }
239
240 #[must_use]
243 pub fn default_value(mut self, default: Value) -> Self {
244 self.default_value = Some(default);
245 self
246 }
247
248 #[must_use]
250 pub fn deprecated(mut self, deprecated: Deprecated) -> Self {
251 self.deprecated = Some(deprecated);
252 self
253 }
254
255 #[must_use]
257 pub fn enum_values<I, E>(mut self, enum_values: I) -> Self
258 where
259 I: IntoIterator<Item = E>,
260 E: Into<Value>,
261 {
262 self.enum_values = enum_values
263 .into_iter()
264 .map(|enum_value| enum_value.into())
265 .collect();
266 self
267 }
268
269 #[must_use]
271 pub fn example<V: Into<Value>>(mut self, example: V) -> Self {
272 self.examples.push(example.into());
273 self
274 }
275
276 #[must_use]
278 pub fn examples<I: IntoIterator<Item = V>, V: Into<Value>>(mut self, examples: I) -> Self {
279 self.examples = examples.into_iter().map(Into::into).collect();
280 self
281 }
282
283 #[must_use]
285 pub fn write_only(mut self, write_only: bool) -> Self {
286 self.write_only = Some(write_only);
287 self
288 }
289
290 #[must_use]
292 pub fn read_only(mut self, read_only: bool) -> Self {
293 self.read_only = Some(read_only);
294 self
295 }
296
297 #[must_use]
299 pub fn xml(mut self, xml: Xml) -> Self {
300 self.xml = Some(xml);
301 self
302 }
303
304 #[must_use]
306 pub fn multiple_of(mut self, multiple_of: f64) -> Self {
307 self.multiple_of = Some(multiple_of);
308 self
309 }
310
311 #[must_use]
313 pub fn maximum(mut self, maximum: f64) -> Self {
314 self.maximum = Some(maximum);
315 self
316 }
317
318 #[must_use]
320 pub fn minimum(mut self, minimum: f64) -> Self {
321 self.minimum = Some(minimum);
322 self
323 }
324
325 #[must_use]
327 pub fn exclusive_maximum(mut self, exclusive_maximum: f64) -> Self {
328 self.exclusive_maximum = Some(exclusive_maximum);
329 self
330 }
331
332 #[must_use]
334 pub fn exclusive_minimum(mut self, exclusive_minimum: f64) -> Self {
335 self.exclusive_minimum = Some(exclusive_minimum);
336 self
337 }
338
339 #[must_use]
341 pub fn max_length(mut self, max_length: usize) -> Self {
342 self.max_length = Some(max_length);
343 self
344 }
345
346 #[must_use]
348 pub fn min_length(mut self, min_length: usize) -> Self {
349 self.min_length = Some(min_length);
350 self
351 }
352
353 #[must_use]
355 pub fn pattern<I: Into<String>>(mut self, pattern: I) -> Self {
356 self.pattern = Some(pattern.into());
357 self
358 }
359
360 #[must_use]
362 pub fn max_properties(mut self, max_properties: usize) -> Self {
363 self.max_properties = Some(max_properties);
364 self
365 }
366
367 #[must_use]
369 pub fn min_properties(mut self, min_properties: usize) -> Self {
370 self.min_properties = Some(min_properties);
371 self
372 }
373
374 #[must_use]
376 pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
377 self.extensions.insert(key.into(), value);
378 self
379 }
380
381 #[must_use]
384 pub fn content_encoding<S: Into<String>>(mut self, content_encoding: S) -> Self {
385 self.content_encoding = content_encoding.into();
386 self
387 }
388
389 #[must_use]
392 pub fn content_media_type<S: Into<String>>(mut self, content_media_type: S) -> Self {
393 self.content_media_type = content_media_type.into();
394 self
395 }
396
397 #[must_use]
399 pub fn to_array(self) -> Array {
400 Array::new().items(self)
401 }
402}
403
404impl From<Object> for Schema {
405 fn from(s: Object) -> Self {
406 Self::Object(Box::new(s))
407 }
408}
409
410impl From<Object> for RefOr<Schema> {
413 fn from(obj: Object) -> Self {
414 Self::Type(Schema::Object(Box::new(obj)))
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use assert_json_diff::assert_json_eq;
421 use serde_json::json;
422
423 use super::*;
424 use crate::BasicType;
425
426 #[test]
427 fn test_build_string_object() {
428 let object = Object::new()
429 .schema_type(BasicType::String)
430 .deprecated(Deprecated::True)
431 .write_only(false)
432 .read_only(true)
433 .xml(Xml::new())
434 .max_length(10)
435 .min_length(1)
436 .pattern(r"^[a-z]+$");
437
438 assert_json_eq!(
439 object,
440 json!({
441 "type": "string",
442 "deprecated": true,
443 "readOnly": true,
444 "writeOnly": false,
445 "xml": {},
446 "minLength": 1,
447 "maxLength": 10,
448 "pattern": "^[a-z]+$"
449 })
450 );
451 }
452
453 #[test]
454 fn test_build_number_object() {
455 let object = Object::new()
456 .schema_type(BasicType::Number)
457 .deprecated(Deprecated::True)
458 .write_only(false)
459 .read_only(true)
460 .xml(Xml::new())
461 .multiple_of(10.0)
462 .minimum(0.0)
463 .maximum(1000.0)
464 .exclusive_minimum(0.0)
465 .exclusive_maximum(1000.0);
466
467 assert_json_eq!(
468 object,
469 json!({
470 "type": "number",
471 "deprecated": true,
472 "readOnly": true,
473 "writeOnly": false,
474 "xml": {},
475 "multipleOf": 10.0,
476 "minimum": 0.0,
477 "maximum": 1000.0,
478 "exclusiveMinimum": 0.0,
479 "exclusiveMaximum": 1000.0
480 })
481 );
482 }
483
484 #[test]
485 fn test_build_object_object() {
486 let object = Object::new()
487 .schema_type(BasicType::Object)
488 .deprecated(Deprecated::True)
489 .write_only(false)
490 .read_only(true)
491 .xml(Xml::new())
492 .min_properties(1)
493 .max_properties(10);
494
495 assert_json_eq!(
496 object,
497 json!({
498 "type": "object",
499 "deprecated": true,
500 "readOnly": true,
501 "writeOnly": false,
502 "xml": {},
503 "minProperties": 1,
504 "maxProperties": 10
505 })
506 );
507 }
508
509 #[test]
510 fn test_object_with_extensions() {
511 let expected = json!("value");
512 let json_value = Object::new().add_extension("x-some-extension", expected.clone());
513
514 let value = serde_json::to_value(&json_value).unwrap();
515 assert_eq!(value.get("x-some-extension"), Some(&expected));
516 }
517}