1use crate::*;
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
7#[serde(rename_all = "camelCase")]
8pub struct SchemaData {
9 #[serde(default, skip_serializing_if = "is_false")]
10 pub nullable: bool,
11 #[serde(default, skip_serializing_if = "is_false")]
12 pub read_only: bool,
13 #[serde(default, skip_serializing_if = "is_false")]
14 pub write_only: bool,
15 #[serde(default, skip_serializing_if = "is_false")]
16 pub deprecated: bool,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub external_docs: Option<ExternalDocumentation>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub example: Option<serde_json::Value>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub title: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub description: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub discriminator: Option<Discriminator>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub default: Option<serde_json::Value>,
29 #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
35 pub extensions: IndexMap<String, serde_json::Value>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39pub struct Schema {
40 #[serde(flatten)]
41 pub data: SchemaData,
42 #[serde(flatten)]
43 pub kind: SchemaKind,
44}
45
46impl std::ops::Deref for Schema {
47 type Target = SchemaData;
48
49 fn deref(&self) -> &Self::Target {
50 &self.data
51 }
52}
53
54impl std::ops::DerefMut for Schema {
55 fn deref_mut(&mut self) -> &mut Self::Target {
56 &mut self.data
57 }
58}
59
60#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)]
61#[serde(untagged)]
62pub enum SchemaKind {
63 Type(Type),
64 OneOf {
65 #[serde(rename = "oneOf")]
66 one_of: Vec<RefOr<Schema>>,
67 },
68 AllOf {
69 #[serde(rename = "allOf")]
70 all_of: Vec<RefOr<Schema>>,
71 },
72 AnyOf {
73 #[serde(rename = "anyOf")]
74 any_of: Vec<RefOr<Schema>>,
75 },
76 Not {
77 not: Box<RefOr<Schema>>,
78 },
79 Any(AnySchema),
80}
81
82
83impl Schema {
84 fn new_kind(kind: SchemaKind) -> Self {
85 Self { data: SchemaData::default(), kind }
86 }
87
88 pub fn new_number() -> Self {
89 Self::new_kind(SchemaKind::Type(Type::Number(NumberType::default())))
90 }
91
92 pub fn new_integer() -> Self {
93 Self::new_kind(SchemaKind::Type(Type::Integer(IntegerType::default())))
94 }
95
96 pub fn new_bool() -> Self {
97 Self::new_kind(SchemaKind::Type(Type::Boolean {}))
98 }
99
100 pub fn new_str_enum(enumeration: Vec<String>) -> Self {
101 Self::new_kind(SchemaKind::Type(Type::String(StringType {
102 enumeration,
103 ..StringType::default()
104 })))
105 }
106
107 pub fn new_string() -> Self {
108 Self::new_kind(SchemaKind::Type(Type::String(StringType::default())))
109 }
110
111 pub fn new_object() -> Self {
113 Self::new_kind(SchemaKind::Type(Type::Object(ObjectType::default())))
114 }
115
116 pub fn new_map(inner: impl Into<RefOr<Schema>>) -> Self {
118 let inner = inner.into().boxed();
119 Self::new_kind(SchemaKind::Type(Type::Object(ObjectType {
120 additional_properties: Some(AdditionalProperties::Schema(inner)),
121 ..ObjectType::default()
122 })))
123 }
124
125 pub fn new_map_any() -> Self {
127 Self::new_kind(SchemaKind::Type(Type::Object(ObjectType {
128 additional_properties: Some(AdditionalProperties::Any(true)),
129 ..ObjectType::default()
130 })))
131 }
132
133 pub fn new_array_any() -> Self {
135 Self::new_kind(SchemaKind::Type(Type::Array(ArrayType::default())))
136 }
137
138 pub fn new_array(inner: impl Into<RefOr<Schema>>) -> Self {
140 let inner = inner.into().boxed();
141 Self::new_kind(SchemaKind::Type(Type::Array(ArrayType {
142 items: Some(inner),
143 ..ArrayType::default()
144 })))
145 }
146
147 pub fn new_one_of(one_of: Vec<RefOr<Schema>>) -> Self {
148 Self::new_kind(SchemaKind::OneOf { one_of })
149 }
150
151 pub fn new_all_of(all_of: Vec<RefOr<Schema>>) -> Self {
152 Self::new_kind(SchemaKind::AllOf { all_of })
153 }
154
155 pub fn new_any_of(any_of: Vec<RefOr<Schema>>) -> Self {
156 Self::new_kind(SchemaKind::AnyOf { any_of })
157 }
158
159 pub fn new_any() -> Self {
161 Self {
162 data: SchemaData::default(),
163 kind: SchemaKind::Any(AnySchema::default()),
164 }
165 }
166
167 pub fn with_format(mut self, format: &str) -> Self {
168 if let SchemaKind::Type(Type::String(s)) = &mut self.kind {
169 s.format = serde_json::from_value(Value::String(format.to_string())).unwrap();
170 }
171 self
172 }
173
174 pub fn is_empty(&self) -> bool {
175 match &self.kind {
176 SchemaKind::Type(Type::Object(o)) => {
177 o.properties.is_empty() && o.additional_properties.is_none()
178 }
179 _ => false,
180 }
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185#[serde(tag = "type", rename_all = "lowercase")]
186pub enum Type {
187 String(StringType),
188 Number(NumberType),
189 Integer(IntegerType),
190 Object(ObjectType),
191 Array(ArrayType),
192 Boolean {},
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
196#[serde(untagged)]
197pub enum AdditionalProperties {
198 Any(bool),
199 Schema(Box<RefOr<Schema>>),
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
205#[serde(rename_all = "camelCase")]
206pub struct AnySchema {
207 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
208 pub typ: Option<String>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub pattern: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub multiple_of: Option<f64>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub exclusive_minimum: Option<bool>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub exclusive_maximum: Option<bool>,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub minimum: Option<f64>,
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub maximum: Option<f64>,
221 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
222 pub properties: RefOrMap<Schema>,
223 #[serde(default, skip_serializing_if = "Vec::is_empty")]
224 pub required: Vec<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub additional_properties: Option<AdditionalProperties>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub min_properties: Option<usize>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub max_properties: Option<usize>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub items: Option<Box<RefOr<Schema>>>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub min_items: Option<usize>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub max_items: Option<usize>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub unique_items: Option<bool>,
239 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
240 pub enumeration: Vec<serde_json::Value>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub format: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub min_length: Option<usize>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub max_length: Option<usize>,
247 #[serde(default, skip_serializing_if = "Vec::is_empty")]
248 pub one_of: Vec<RefOr<Schema>>,
249 #[serde(default, skip_serializing_if = "Vec::is_empty")]
250 pub all_of: Vec<RefOr<Schema>>,
251 #[serde(default, skip_serializing_if = "Vec::is_empty")]
252 pub any_of: Vec<RefOr<Schema>>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub not: Option<Box<RefOr<Schema>>>,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
258#[serde(rename_all = "camelCase")]
259pub struct StringType {
260 #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
261 pub format: VariantOrUnknownOrEmpty<StringFormat>,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub pattern: Option<String>,
264 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
265 pub enumeration: Vec<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub min_length: Option<usize>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub max_length: Option<usize>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
273#[serde(rename_all = "camelCase")]
274pub struct NumberType {
275 #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
276 pub format: VariantOrUnknownOrEmpty<NumberFormat>,
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub multiple_of: Option<f64>,
279 #[serde(default, skip_serializing_if = "is_false")]
280 pub exclusive_minimum: bool,
281 #[serde(default, skip_serializing_if = "is_false")]
282 pub exclusive_maximum: bool,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub minimum: Option<f64>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub maximum: Option<f64>,
287 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
288 pub enumeration: Vec<Option<f64>>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub struct IntegerType {
294 #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
295 pub format: VariantOrUnknownOrEmpty<IntegerFormat>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub multiple_of: Option<i64>,
298 #[serde(default, skip_serializing_if = "is_false")]
299 pub exclusive_minimum: bool,
300 #[serde(default, skip_serializing_if = "is_false")]
301 pub exclusive_maximum: bool,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub minimum: Option<i64>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub maximum: Option<i64>,
306 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
307 pub enumeration: Vec<Option<i64>>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
311#[serde(rename_all = "camelCase")]
312pub struct ObjectType {
313 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
314 pub properties: RefOrMap<Schema>,
315 #[serde(default, skip_serializing_if = "Vec::is_empty")]
316 pub required: Vec<String>,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 pub additional_properties: Option<AdditionalProperties>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub min_properties: Option<usize>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub max_properties: Option<usize>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
326#[serde(rename_all = "camelCase")]
327pub struct ArrayType {
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub items: Option<Box<RefOr<Schema>>>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub min_items: Option<usize>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub max_items: Option<usize>,
334 #[serde(default, skip_serializing_if = "is_false")]
335 pub unique_items: bool,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
339#[serde(rename_all = "lowercase")]
340pub enum NumberFormat {
341 Float,
342 Double,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
346#[serde(rename_all = "lowercase")]
347pub enum IntegerFormat {
348 Int32,
349 Int64,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
353#[serde(rename_all = "lowercase")]
354pub enum StringFormat {
355 Date,
356 #[serde(rename = "date-time")]
357 DateTime,
358 Password,
359 Byte,
360 Binary,
361}
362
363impl VariantOrUnknownOrEmpty<StringFormat> {
364 pub fn as_str(&self) -> &str {
365 match self {
366 VariantOrUnknownOrEmpty::Item(StringFormat::Date) => "date",
367 VariantOrUnknownOrEmpty::Item(StringFormat::DateTime) => "date-time",
368 VariantOrUnknownOrEmpty::Item(StringFormat::Password) => "password",
369 VariantOrUnknownOrEmpty::Item(StringFormat::Byte) => "byte",
370 VariantOrUnknownOrEmpty::Item(StringFormat::Binary) => "binary",
371 VariantOrUnknownOrEmpty::Unknown(s) => s.as_str(),
372 VariantOrUnknownOrEmpty::Empty => "",
373 }
374 }
375}
376
377
378impl Schema {
379 pub fn properties(&self) -> &RefOrMap<Schema> {
380 self.get_properties().expect("Schema is not an object")
381 }
382
383 pub fn get_properties(&self) -> Option<&RefOrMap<Schema>> {
384 match &self.kind {
385 SchemaKind::Type(Type::Object(o)) => Some(&o.properties),
386 SchemaKind::Any(AnySchema { properties, .. }) => Some(properties),
387 _ => None,
388 }
389 }
390
391 pub fn get_properties_mut(&mut self) -> Option<&mut RefOrMap<Schema>> {
392 match &mut self.kind {
393 SchemaKind::Type(Type::Object(ref mut o)) => Some(&mut o.properties),
394 SchemaKind::Any(AnySchema { ref mut properties, .. }) => Some(properties),
395 _ => None,
396 }
397 }
398
399 pub fn properties_mut(&mut self) -> &mut RefOrMap<Schema> {
400 self.get_properties_mut().expect("Schema is not an object")
401 }
402
403 pub fn properties_iter<'a>(&'a self, spec: &'a OpenAPI) -> Box<dyn Iterator<Item=(&'a String, &'a RefOr<Schema>)> + 'a> {
404 match &self.kind {
405 SchemaKind::Type(Type::Object(o)) => Box::new(o.properties.iter()),
406 SchemaKind::Any(AnySchema { properties, .. }) => Box::new(properties.iter()),
407 SchemaKind::AllOf { all_of } => Box::new(all_of
408 .iter()
409 .map(move |schema| schema.resolve(spec).properties_iter(spec))
410 .flatten()),
411 _ => Box::new(std::iter::empty())
412 }
413 }
414
415 pub fn is_required(&self, field: &str) -> bool {
416 match &self.kind {
417 SchemaKind::Type(Type::Object(o)) => o.required.iter().any(|s| s == field),
418 SchemaKind::Any(AnySchema { required, .. }) => required.iter().any(|s| s == field),
419 _ => true,
420 }
421 }
422
423 pub fn get_required(&self) -> Option<&Vec<String>> {
424 match &self.kind {
425 SchemaKind::Type(Type::Object(o)) => Some(&o.required),
426 SchemaKind::Any(AnySchema { required, .. }) => Some(required),
427 _ => None,
428 }
429 }
430
431 pub fn required(&self) -> &Vec<String> {
432 self.get_required().expect("Schema is not an object")
433 }
434
435 pub fn get_required_mut(&mut self) -> Option<&mut Vec<String>> {
436 match &mut self.kind {
437 SchemaKind::Type(Type::Object(ref mut o)) => Some(&mut o.required),
438 SchemaKind::Any(AnySchema { ref mut required, .. }) => Some(required),
439 _ => None,
440 }
441 }
442
443 pub fn required_mut(&mut self) -> &mut Vec<String> {
444 self.get_required_mut().expect("Schema is not an object")
445 }
446
447 pub fn add_required(&mut self, field: &str) {
448 if let Some(req) = self.get_required_mut() {
449 if req.iter().any(|f| f == field) {
450 return;
451 }
452 req.push(field.to_string());
453 }
454 }
455
456 pub fn remove_required(&mut self, field: &str) {
457 if let Some(req) = self.get_required_mut() {
458 req.retain(|f| f != field);
459 }
460 }
461
462 pub fn is_anonymous_object(&self) -> bool {
463 match &self.kind {
464 SchemaKind::Type(Type::Object(o)) => o.properties.is_empty(),
465 _ => false,
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use assert_matches::assert_matches;
473 use serde_json::json;
474
475 use crate::{AnySchema, Schema, SchemaData, SchemaKind};
476
477 #[test]
478 fn test_schema_with_extensions() {
479 let schema = serde_json::from_str::<Schema>(
480 r#"{
481 "type": "boolean",
482 "x-foo": "bar"
483 }"#,
484 )
485 .unwrap();
486
487 assert_eq!(
488 schema.data.extensions.get("x-foo"),
489 Some(&json!("bar"))
490 );
491 }
492
493 #[test]
494 fn test_any() {
495 let value = json! { {} };
496 serde_json::from_value::<AnySchema>(value).unwrap();
497 }
498
499 #[test]
500 fn test_not() {
501 let value = json! {
502 {
503 "not": {}
504 }
505 };
506
507 let schema = serde_json::from_value::<Schema>(value).unwrap();
508 assert!(matches!(schema.kind, SchemaKind::Not { not: _ }));
509 }
510
511 #[test]
512 fn test_null() {
513 let value = json! {
514 {
515 "nullable": true,
516 "enum": [ null ],
517 }
518 };
519
520 let schema = serde_json::from_value::<Schema>(value).unwrap();
521 assert!(matches!(
522 &schema.data,
523 SchemaData { nullable: true, .. }
524 ));
525 assert!(matches!(
526 &schema.kind,
527 SchemaKind::Any(AnySchema { enumeration, .. }) if enumeration[0] == json!(null)));
528 }
529
530 #[test]
531 fn test_default_to_object() {
532 let s = r##"
533required:
534 - definition
535properties:
536 definition:
537 type: string
538 description: >
539 Serialized definition of the version. This should be an OpenAPI 2.x, 3.x or AsyncAPI 2.x file
540 serialized as a string, in YAML or JSON.
541 example: |
542 {asyncapi: "2.0", "info": { "title: … }}
543 references:
544 type: array
545 description: Import external references used by `definition`. It's usually resources not accessible by Bump servers, like local files or internal URLs.
546 items:
547 $ref: "#/components/schemas/Reference"
548"##.trim();
549 let s = serde_yaml::from_str::<Schema>(s).unwrap();
550 assert!(matches!(s.kind, SchemaKind::Any(crate::AnySchema{ ref properties, ..}) if properties.len() == 2), "Schema kind was not expected {:?}", s.kind);
552 }
553
554 #[test]
555 fn test_all_of() {
556 let s = r##"
557allOf:
558 - $ref: "#/components/schemas/DocumentationRequest"
559 - $ref: "#/components/schemas/PreviewRequest"
560 "##.trim();
561 let s = serde_yaml::from_str::<Schema>(s).unwrap();
562 match &s.kind {
563 SchemaKind::AllOf { all_of } => {
564 assert_eq!(all_of.len(), 2);
565 assert!(matches!(all_of[0].as_ref_str(), Some("#/components/schemas/DocumentationRequest")));
566 assert!(matches!(all_of[1].as_ref_str(), Some("#/components/schemas/PreviewRequest")));
567 }
568 _ => panic!("Schema kind was not expected {:?}", s.kind)
569 }
570 }
571
572 #[test]
573 fn test_with_format() {
574 use crate::variant_or::VariantOrUnknownOrEmpty;
575 let s = Schema::new_string().with_format("date-time");
576 let SchemaKind::Type(crate::Type::String(s)) = s.kind else { panic!() };
577 assert_matches!(s.format, VariantOrUnknownOrEmpty::Item(crate::StringFormat::DateTime));
578
579 let s = Schema::new_string().with_format("uuid");
580 let SchemaKind::Type(crate::Type::String(s)) = s.kind else { panic!() };
581 assert_matches!(s.format, VariantOrUnknownOrEmpty::Unknown(s) if s == "uuid");
582 }
583}
584