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}