salvo_oapi/openapi/schema/
object.rs1use indexmap::IndexSet;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::AdditionalProperties;
6use crate::{Deprecated, PropMap, RefOr, Schema, SchemaFormat, SchemaType, ToArray, 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
395impl From<Object> for Schema {
396 fn from(s: Object) -> Self {
397 Self::Object(Box::new(s))
398 }
399}
400
401impl ToArray for Object {}
402
403impl From<Object> for RefOr<Schema> {
404 fn from(obj: Object) -> Self {
405 Self::Type(Schema::Object(Box::new(obj)))
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use assert_json_diff::assert_json_eq;
412 use serde_json::json;
413
414 use super::*;
415 use crate::BasicType;
416
417 #[test]
418 fn test_build_string_object() {
419 let object = Object::new()
420 .schema_type(BasicType::String)
421 .deprecated(Deprecated::True)
422 .write_only(false)
423 .read_only(true)
424 .xml(Xml::new())
425 .max_length(10)
426 .min_length(1)
427 .pattern(r"^[a-z]+$");
428
429 assert_json_eq!(
430 object,
431 json!({
432 "type": "string",
433 "deprecated": true,
434 "readOnly": true,
435 "writeOnly": false,
436 "xml": {},
437 "minLength": 1,
438 "maxLength": 10,
439 "pattern": "^[a-z]+$"
440 })
441 );
442 }
443
444 #[test]
445 fn test_build_number_object() {
446 let object = Object::new()
447 .schema_type(BasicType::Number)
448 .deprecated(Deprecated::True)
449 .write_only(false)
450 .read_only(true)
451 .xml(Xml::new())
452 .multiple_of(10.0)
453 .minimum(0.0)
454 .maximum(1000.0)
455 .exclusive_minimum(0.0)
456 .exclusive_maximum(1000.0);
457
458 assert_json_eq!(
459 object,
460 json!({
461 "type": "number",
462 "deprecated": true,
463 "readOnly": true,
464 "writeOnly": false,
465 "xml": {},
466 "multipleOf": 10.0,
467 "minimum": 0.0,
468 "maximum": 1000.0,
469 "exclusiveMinimum": 0.0,
470 "exclusiveMaximum": 1000.0
471 })
472 );
473 }
474
475 #[test]
476 fn test_build_object_object() {
477 let object = Object::new()
478 .schema_type(BasicType::Object)
479 .deprecated(Deprecated::True)
480 .write_only(false)
481 .read_only(true)
482 .xml(Xml::new())
483 .min_properties(1)
484 .max_properties(10);
485
486 assert_json_eq!(
487 object,
488 json!({
489 "type": "object",
490 "deprecated": true,
491 "readOnly": true,
492 "writeOnly": false,
493 "xml": {},
494 "minProperties": 1,
495 "maxProperties": 10
496 })
497 );
498 }
499
500 #[test]
501 fn test_object_with_extensions() {
502 let expected = json!("value");
503 let json_value = Object::new().add_extension("x-some-extension", expected.clone());
504
505 let value = serde_json::to_value(&json_value).unwrap();
506 assert_eq!(value.get("x-some-extension"), Some(&expected));
507 }
508}