1use std::collections::{BTreeMap, BTreeSet, HashSet};
4
5use heck::ToPascalCase;
6use log::debug;
7use schemars::schema::{
8 ArrayValidation, InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SingleOrVec,
9 StringValidation, SubschemaValidation,
10};
11use unicode_ident::{is_xid_continue, is_xid_start};
12
13use crate::{validate::schema_value_validate, Error, Name, RefKey, Result, TypeSpace};
14
15pub(crate) fn metadata_description(metadata: &Option<Box<Metadata>>) -> Option<String> {
16 metadata
17 .as_ref()
18 .and_then(|metadata| metadata.description.as_ref().cloned())
19}
20
21pub(crate) fn metadata_title(metadata: &Option<Box<Metadata>>) -> Option<String> {
22 metadata
23 .as_ref()
24 .and_then(|metadata| metadata.title.as_ref().cloned())
25}
26
27pub(crate) fn metadata_title_and_description(metadata: &Option<Box<Metadata>>) -> Option<String> {
28 metadata
29 .as_ref()
30 .and_then(|metadata| match (&metadata.title, &metadata.description) {
31 (Some(t), Some(d)) => Some(format!("{}\n\n{}", t, d)),
32 (Some(t), None) => Some(t.clone()),
33 (None, Some(d)) => Some(d.clone()),
34 (None, None) => None,
35 })
36}
37
38pub(crate) fn all_mutually_exclusive(
47 subschemas: &[Schema],
48 definitions: &BTreeMap<RefKey, Schema>,
49) -> bool {
50 let len = subschemas.len();
51 if len < 2 {
55 return true;
56 }
57 (0..len - 1)
59 .flat_map(|ii| (ii + 1..len).map(move |jj| (ii, jj)))
60 .all(|(ii, jj)| {
61 let a = resolve(&subschemas[ii], definitions);
62 let b = resolve(&subschemas[jj], definitions);
63 schemas_mutually_exclusive(a, b, definitions)
64 })
65}
66
67fn schemas_mutually_exclusive(
70 a: &Schema,
71 b: &Schema,
72 definitions: &BTreeMap<RefKey, Schema>,
73) -> bool {
74 match (a, b) {
75 (Schema::Bool(false), _) => true,
77 (_, Schema::Bool(false)) => true,
78
79 (Schema::Bool(true), _) => false,
81 (_, Schema::Bool(true)) => false,
82
83 (
85 other,
86 Schema::Object(SchemaObject {
87 metadata: None,
88 instance_type: None,
89 format: None,
90 enum_values: None,
91 const_value: None,
92 subschemas: Some(subschemas),
93 number: None,
94 string: None,
95 array: None,
96 object: None,
97 reference: None,
98 extensions: _,
99 }),
100 )
101 | (
102 Schema::Object(SchemaObject {
103 metadata: None,
104 instance_type: None,
105 format: None,
106 enum_values: None,
107 const_value: None,
108 subschemas: Some(subschemas),
109 number: None,
110 string: None,
111 array: None,
112 object: None,
113 reference: None,
114 extensions: _,
115 }),
116 other,
117 ) => match subschemas.as_ref() {
118 SubschemaValidation {
121 all_of: Some(s),
122 any_of: None,
123 one_of: None,
124 not: None,
125 if_schema: None,
126 then_schema: None,
127 else_schema: None,
128 } => s
129 .iter()
130 .any(|sub| schemas_mutually_exclusive(sub, other, definitions)),
131
132 SubschemaValidation {
134 all_of: None,
135 any_of: Some(s),
136 one_of: None,
137 not: None,
138 if_schema: None,
139 then_schema: None,
140 else_schema: None,
141 }
142 | SubschemaValidation {
143 all_of: None,
144 any_of: None,
145 one_of: Some(s),
146 not: None,
147 if_schema: None,
148 then_schema: None,
149 else_schema: None,
150 } => s
151 .iter()
152 .all(|sub| schemas_mutually_exclusive(sub, other, definitions)),
153
154 SubschemaValidation {
156 all_of: None,
157 any_of: None,
158 one_of: None,
159 not: Some(sub),
160 if_schema: None,
161 then_schema: None,
162 else_schema: None,
163 } => !schemas_mutually_exclusive(sub, other, definitions),
164
165 _ => false,
168 },
169
170 (
176 schema,
177 Schema::Object(SchemaObject {
178 instance_type: None,
179 enum_values: Some(enum_values),
180 ..
181 }),
182 )
183 | (
184 Schema::Object(SchemaObject {
185 instance_type: None,
186 enum_values: Some(enum_values),
187 ..
188 }),
189 schema,
190 ) => enum_values
191 .iter()
192 .all(|value| schema_value_validate(schema, value, definitions).is_err()),
193
194 (
197 schema,
198 Schema::Object(SchemaObject {
199 const_value: Some(value),
200 ..
201 }),
202 )
203 | (
204 Schema::Object(SchemaObject {
205 const_value: Some(value),
206 ..
207 }),
208 schema,
209 ) => schema_value_validate(schema, value, definitions).is_err(),
210
211 (Schema::Object(a), Schema::Object(b)) => {
213 match (&a.instance_type, &b.instance_type) {
214 (None, _) => false,
217 (_, None) => false,
218
219 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
222 if a_single != b_single =>
223 {
224 true
225 }
226
227 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
231 if a_single == b_single && a_single.as_ref() == &InstanceType::Object =>
232 {
233 if let (
234 SchemaObject {
235 metadata: _,
236 instance_type: _,
237 format: None,
238 enum_values: None,
239 const_value: None,
240 subschemas: None,
241 number: None,
242 string: None,
243 array: None,
244 object: Some(a_validation),
245 reference: None,
246 extensions: _,
247 },
248 SchemaObject {
249 metadata: _,
250 instance_type: _,
251 format: None,
252 enum_values: None,
253 const_value: None,
254 subschemas: None,
255 number: None,
256 string: None,
257 array: None,
258 object: Some(b_validation),
259 reference: None,
260 extensions: _,
261 },
262 ) = (a, b)
263 {
264 object_schemas_mutually_exclusive(a_validation, b_validation)
265 } else {
266 false
268 }
269 }
270
271 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
275 if a_single == b_single && a_single.as_ref() == &InstanceType::Array =>
276 {
277 if let (
278 SchemaObject {
279 metadata: _,
280 instance_type: _,
281 format: None,
282 enum_values: None,
283 const_value: None,
284 subschemas: None,
285 number: None,
286 string: None,
287 array: Some(a_validation),
288 object: None,
289 reference: None,
290 extensions: _,
291 },
292 SchemaObject {
293 metadata: _,
294 instance_type: _,
295 format: None,
296 enum_values: None,
297 const_value: None,
298 subschemas: None,
299 number: None,
300 string: None,
301 array: Some(b_validation),
302 object: None,
303 reference: None,
304 extensions: _,
305 },
306 ) = (a, b)
307 {
308 array_schemas_mutually_exclusive(a_validation, b_validation, definitions)
309 } else {
310 false
312 }
313 }
314
315 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single))) => {
318 a_single != b_single
319 }
320
321 (Some(SingleOrVec::Vec(a_vec)), Some(SingleOrVec::Vec(b_vec))) => a_vec
324 .iter()
325 .all(|instance_type| !b_vec.contains(instance_type)),
326
327 (Some(SingleOrVec::Single(single)), Some(SingleOrVec::Vec(vec)))
331 | (Some(SingleOrVec::Vec(vec)), Some(SingleOrVec::Single(single))) => {
332 !vec.contains(single)
333 }
334 }
335 }
336 }
337}
338
339fn object_schemas_mutually_exclusive(
344 a_validation: &ObjectValidation,
345 b_validation: &ObjectValidation,
346) -> bool {
347 let ObjectValidation {
348 required: a_required,
349 properties: a_properties,
350 ..
351 } = a_validation;
352 let ObjectValidation {
353 required: b_required,
354 properties: b_properties,
355 ..
356 } = b_validation;
357
358 if a_properties.is_empty() || b_properties.is_empty() {
360 return false;
361 }
362
363 if !a_required.is_subset(&b_properties.keys().cloned().collect())
368 || !b_required.is_subset(&a_properties.keys().cloned().collect())
369 {
370 true
371 } else {
372 let aa = a_required
384 .iter()
385 .filter_map(|name| {
386 let t = a_properties.get(name).unwrap();
387 constant_string_value(t).map(|s| (name.clone(), s))
388 })
389 .collect::<HashSet<_>>();
390 let bb = b_required
391 .iter()
392 .filter_map(|name| {
393 let t = b_properties.get(name).unwrap();
394 constant_string_value(t).map(|s| (name.clone(), s))
395 })
396 .collect::<HashSet<_>>();
397
398 !aa.is_subset(&bb) && !bb.is_subset(&aa)
400 }
401}
402
403fn array_schemas_mutually_exclusive(
404 a_validation: &ArrayValidation,
405 b_validation: &ArrayValidation,
406 definitions: &BTreeMap<RefKey, Schema>,
407) -> bool {
408 match (a_validation, b_validation) {
409 (
415 ArrayValidation {
416 items: Some(SingleOrVec::Single(single)),
417 additional_items: None,
418 ..
419 },
420 ArrayValidation {
421 items: Some(SingleOrVec::Vec(vec)),
422 additional_items: None,
423 max_items: Some(max_items),
424 min_items: Some(min_items),
425 unique_items: None,
426 contains: None,
427 },
428 )
429 | (
430 ArrayValidation {
431 items: Some(SingleOrVec::Vec(vec)),
432 additional_items: None,
433 max_items: Some(max_items),
434 min_items: Some(min_items),
435 unique_items: None,
436 contains: None,
437 },
438 ArrayValidation {
439 items: Some(SingleOrVec::Single(single)),
440 additional_items: None,
441 ..
442 },
443 ) if max_items == min_items && *max_items as usize == vec.len() => vec
444 .iter()
445 .any(|schema| schemas_mutually_exclusive(schema, single, definitions)),
446
447 (aa, bb) => {
448 match (&aa.max_items, &bb.min_items) {
450 (Some(max), Some(min)) if min > max => return true,
451 _ => (),
452 }
453 match (&bb.max_items, &aa.min_items) {
454 (Some(max), Some(min)) if min > max => return true,
455 _ => (),
456 }
457
458 match (&aa.items, &aa.max_items, &bb.items, &bb.max_items) {
459 (Some(SingleOrVec::Single(a_items)), _, Some(SingleOrVec::Single(b_items)), _)
462 if schemas_mutually_exclusive(a_items, b_items, definitions) =>
463 {
464 return true;
465 }
466
467 _ => (),
468 }
469 debug!(
470 "giving up on mutual exclusivity check {} {}",
471 serde_json::to_string_pretty(aa).unwrap(),
472 serde_json::to_string_pretty(bb).unwrap(),
473 );
474 false
475 }
476 }
477}
478
479pub(crate) fn constant_string_value(schema: &Schema) -> Option<&str> {
482 match schema {
483 Schema::Object(SchemaObject {
485 metadata: _,
486 instance_type: Some(SingleOrVec::Single(single)),
487 format: None,
488 enum_values: Some(values),
489 const_value: None,
490 subschemas: None,
491 number: None,
492 string: None,
493 array: None,
494 object: None,
495 reference: None,
496 extensions: _,
497 }) if single.as_ref() == &InstanceType::String && values.len() == 1 => {
498 values.first().unwrap().as_str()
499 }
500
501 Schema::Object(SchemaObject {
503 metadata: _,
504 instance_type: None,
505 format: None,
506 enum_values: Some(values),
507 const_value: None,
508 subschemas: None,
509 number: None,
510 string: None,
511 array: None,
512 object: None,
513 reference: None,
514 extensions: _,
515 }) if values.len() == 1 => values.first().unwrap().as_str(),
516
517 Schema::Object(SchemaObject {
519 metadata: _,
520 instance_type: Some(SingleOrVec::Single(single)),
521 format: None,
522 enum_values: None,
523 const_value: Some(value),
524 subschemas: None,
525 number: None,
526 string: None,
527 array: None,
528 object: None,
529 reference: None,
530 extensions: _,
531 }) if single.as_ref() == &InstanceType::String => value.as_str(),
532
533 Schema::Object(SchemaObject {
535 metadata: _,
536 instance_type: None,
537 format: None,
538 enum_values: None,
539 const_value: Some(value),
540 subschemas: None,
541 number: None,
542 string: None,
543 array: None,
544 object: None,
545 reference: None,
546 extensions: _,
547 }) => value.as_str(),
548
549 _ => None,
550 }
551}
552
553fn decode_segment(segment: &str) -> String {
554 segment.replace("~1", "/").replace("~0", "~")
555}
556
557pub(crate) fn ref_key(ref_name: &str) -> RefKey {
558 if ref_name == "#" {
559 RefKey::Root
560 } else if let Some(idx) = ref_name.rfind('/') {
561 let decoded_segment = decode_segment(&ref_name[idx + 1..]);
562
563 RefKey::Def(decoded_segment)
564 } else {
565 panic!("expected a '/' in $ref: {}", ref_name)
566 }
567}
568
569fn resolve<'a>(
570 schema: &'a Schema,
571 definitions: &'a std::collections::BTreeMap<RefKey, Schema>,
572) -> &'a Schema {
573 match schema {
574 Schema::Bool(_) => schema,
575 Schema::Object(SchemaObject {
576 metadata: _,
577 instance_type: None,
578 format: None,
579 enum_values: None,
580 const_value: None,
581 subschemas: None,
582 number: None,
583 string: None,
584 array: None,
585 object: None,
586 reference: Some(ref_name),
587 extensions: _,
588 }) => definitions.get(&ref_key(ref_name)).unwrap(),
589 Schema::Object(SchemaObject {
590 reference: None, ..
591 }) => schema,
592 _ => todo!(),
594 }
595}
596
597pub(crate) fn schema_is_named(schema: &Schema) -> Option<String> {
599 let raw_name = match schema {
600 Schema::Object(SchemaObject {
601 metadata: _,
602 instance_type: None,
603 format: None,
604 enum_values: None,
605 const_value: None,
606 subschemas: None,
607 number: None,
608 string: None,
609 array: None,
610 object: None,
611 reference: Some(reference),
612 extensions: _,
613 }) => {
614 let idx = reference.rfind('/')?;
615 Some(reference[idx + 1..].to_string())
616 }
617
618 Schema::Object(SchemaObject {
619 metadata: Some(metadata),
620 ..
621 }) if metadata.as_ref().title.is_some() => Some(metadata.as_ref().title.as_ref()?.clone()),
622
623 Schema::Object(SchemaObject {
624 metadata: _,
625 instance_type: _,
626 format: None,
627 enum_values: None,
628 const_value: None,
629 subschemas: Some(subschemas),
630 number: None,
631 string: None,
632 array: None,
633 object: None,
634 reference: None,
635 extensions: _,
636 }) => singleton_subschema(subschemas).and_then(schema_is_named),
637
638 Schema::Object(SchemaObject {
640 instance_type: Some(SingleOrVec::Single(single)),
641 format,
642 ..
643 }) => match (**single, format.as_deref()) {
644 (_, Some(format)) => Some(format.to_pascal_case()),
645 (InstanceType::Boolean, _) => Some("Boolean".to_string()),
646 (InstanceType::Integer, _) => Some("Integer".to_string()),
647 (InstanceType::Number, _) => Some("Number".to_string()),
648 (InstanceType::String, _) => Some("String".to_string()),
649 (InstanceType::Array, _) => Some("Array".to_string()),
650 (InstanceType::Object, _) => Some("Object".to_string()),
651 (InstanceType::Null, _) => Some("Null".to_string()),
652 },
653
654 _ => None,
655 }?;
656
657 Some(sanitize(&raw_name, Case::Pascal))
658}
659
660pub(crate) fn get_object(schema: &Schema) -> Option<(&Option<Box<Metadata>>, &ObjectValidation)> {
663 match schema {
664 Schema::Object(SchemaObject {
666 metadata,
667 instance_type: Some(SingleOrVec::Single(single)),
668 format: None,
669 enum_values: None,
670 const_value: None,
671 subschemas: None,
672 number: _,
673 string: _,
674 array: _,
675 object: Some(validation),
676 reference: None,
677 extensions: _,
678 }) if single.as_ref() == &InstanceType::Object
679 && schema_none_or_false(&validation.additional_properties)
680 && validation.max_properties.is_none()
681 && validation.min_properties.is_none()
682 && validation.pattern_properties.is_empty()
683 && validation.property_names.is_none() =>
684 {
685 Some((metadata, validation.as_ref()))
686 }
687 Schema::Object(SchemaObject {
689 metadata,
690 instance_type: None,
691 format: None,
692 enum_values: None,
693 const_value: None,
694 subschemas: None,
695 number: None,
696 string: None,
697 array: None,
698 object: Some(validation),
699 reference: None,
700 extensions: _,
701 }) if schema_none_or_false(&validation.additional_properties)
702 && validation.max_properties.is_none()
703 && validation.min_properties.is_none()
704 && validation.pattern_properties.is_empty()
705 && validation.property_names.is_none() =>
706 {
707 Some((metadata, validation.as_ref()))
708 }
709
710 Schema::Object(SchemaObject {
712 metadata,
713 instance_type: _,
714 format: None,
715 enum_values: None,
716 const_value: None,
717 subschemas: Some(subschemas),
718 number: None,
719 string: None,
720 array: None,
721 object: None,
722 reference: None,
723 extensions: _,
724 }) => singleton_subschema(subschemas).and_then(|sub_schema| {
725 get_object(sub_schema).map(|(m, validation)| match m {
726 Some(_) => (metadata, validation),
727 None => (&None, validation),
728 })
729 }),
730
731 _ => None,
733 }
734}
735
736fn schema_none_or_false(additional_properties: &Option<Box<Schema>>) -> bool {
739 matches!(
740 additional_properties.as_ref().map(Box::as_ref),
741 None | Some(Schema::Bool(false))
742 )
743}
744
745pub(crate) fn singleton_subschema(subschemas: &SubschemaValidation) -> Option<&Schema> {
746 match subschemas {
747 SubschemaValidation {
748 all_of: Some(subschemas),
749 any_of: None,
750 one_of: None,
751 not: None,
752 if_schema: None,
753 then_schema: None,
754 else_schema: None,
755 }
756 | SubschemaValidation {
757 all_of: None,
758 any_of: Some(subschemas),
759 one_of: None,
760 not: None,
761 if_schema: None,
762 then_schema: None,
763 else_schema: None,
764 }
765 | SubschemaValidation {
766 all_of: None,
767 any_of: None,
768 one_of: Some(subschemas),
769 not: None,
770 if_schema: None,
771 then_schema: None,
772 else_schema: None,
773 } if subschemas.len() == 1 => subschemas.first(),
774 _ => None,
775 }
776}
777
778pub(crate) enum Case {
779 Pascal,
780 Snake,
781}
782
783pub(crate) fn sanitize(input: &str, case: Case) -> String {
784 use heck::{ToPascalCase, ToSnakeCase};
785 let to_case = match case {
786 Case::Pascal => str::to_pascal_case,
787 Case::Snake => str::to_snake_case,
788 };
789
790 let out = match input {
792 "+1" => "plus1".to_string(),
793 "-1" => "minus1".to_string(),
794 _ => to_case(&input.replace("'", "").replace(|c| !is_xid_continue(c), "-")),
795 };
796
797 let prefix = to_case("x");
798
799 let out = match out.chars().next() {
800 None => prefix,
801 Some(c) if is_xid_start(c) => out,
802 Some(_) => format!("{}{}", prefix, out),
803 };
804
805 if accept_as_ident(&out) {
807 out
808 } else {
809 format!("{}_", out)
810 }
811}
812
813pub fn accept_as_ident(ident: &str) -> bool {
818 match ident {
821 "_" |
822 "abstract" | "as" | "async" | "await" | "become" | "box" | "break" |
824 "const" | "continue" | "crate" | "do" | "dyn" | "else" | "enum" |
825 "extern" | "false" | "final" | "fn" | "for" | "gen" | "if" | "impl" |
826 "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" |
827 "override" | "priv" | "pub" | "ref" | "return" | "Self" | "self" |
828 "static" | "struct" | "super" | "trait" | "true" | "try" | "type" |
829 "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" |
830 "while" | "yield" => false,
831 _ => true,
832 }
833}
834
835pub(crate) fn recase(input: &str, case: Case) -> (String, Option<String>) {
836 let new = sanitize(input, case);
837 let rename = if new == input {
838 None
839 } else {
840 Some(input.to_string())
841 };
842 (new, rename)
843}
844
845pub(crate) fn unique<I, T>(items: I) -> bool
846where
847 I: IntoIterator<Item = T>,
848 T: Eq + std::hash::Hash,
849{
850 let mut unique = HashSet::new();
851 items.into_iter().all(|item| unique.insert(item))
852}
853
854pub(crate) fn get_type_name(type_name: &Name, metadata: &Option<Box<Metadata>>) -> Option<String> {
855 let name = match (type_name, metadata_title(metadata)) {
856 (Name::Required(name), _) => name.clone(),
857 (Name::Suggested(name), None) => name.clone(),
858 (_, Some(name)) => name,
859 (Name::Unknown, None) => None?,
860 };
861
862 Some(sanitize(&name, Case::Pascal))
863}
864
865pub(crate) struct TypePatch {
866 pub name: String,
867 pub derives: BTreeSet<String>,
868 pub attrs: BTreeSet<String>,
869}
870
871impl TypePatch {
872 pub fn new(type_space: &TypeSpace, type_name: String) -> Self {
874 match type_space.settings.patch.get(&type_name) {
875 None => Self {
876 name: type_name,
877 derives: Default::default(),
878 attrs: Default::default(),
879 },
880
881 Some(patch) => {
882 let name = patch.rename.clone().unwrap_or(type_name);
883 let derives = patch.derives.iter().cloned().collect();
884 let attrs = patch.attrs.iter().cloned().collect();
885
886 Self {
887 name,
888 derives,
889 attrs,
890 }
891 }
892 }
893 }
894}
895
896pub(crate) struct StringValidator {
897 max_length: Option<u32>,
898 min_length: Option<u32>,
899 pattern: Option<regress::Regex>,
900}
901
902impl StringValidator {
903 pub fn new(type_name: &Name, validation: Option<&StringValidation>) -> Result<Self> {
904 let (max_length, min_length, pattern) =
905 validation.map_or(Ok((None, None, None)), |validation| {
906 let max = validation.max_length;
907 let min = validation.min_length;
908 let pattern = validation
909 .pattern
910 .as_ref()
911 .map(|pattern| {
912 regress::Regex::new(pattern).map_err(|e| Error::InvalidSchema {
913 type_name: type_name.clone().into_option(),
914 reason: format!("invalid pattern '{}' {}", pattern, e),
915 })
916 })
917 .transpose()?;
918 Ok((max, min, pattern))
919 })?;
920 Ok(Self {
921 max_length,
922 min_length,
923 pattern,
924 })
925 }
926
927 pub fn is_valid<S: AsRef<str>>(&self, s: S) -> bool {
928 self.max_length
931 .as_ref()
932 .is_none_or(|max| s.as_ref().chars().count() as u32 <= *max)
933 && self
934 .min_length
935 .as_ref()
936 .is_none_or(|min| s.as_ref().chars().count() as u32 >= *min)
937 && self
938 .pattern
939 .as_ref()
940 .is_none_or(|pattern| pattern.find(s.as_ref()).is_some())
941 }
942}
943
944#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
950pub(crate) enum ReorderedInstanceType {
951 Null,
953
954 Boolean,
956
957 Integer,
959
960 Number,
962
963 String,
965
966 Array,
968
969 Object,
971}
972
973impl From<InstanceType> for ReorderedInstanceType {
974 fn from(instance_type: InstanceType) -> Self {
975 match instance_type {
976 InstanceType::Null => Self::Null,
977 InstanceType::Boolean => Self::Boolean,
978 InstanceType::Object => Self::Object,
979 InstanceType::Array => Self::Array,
980 InstanceType::Integer => Self::Integer,
981 InstanceType::Number => Self::Number,
982 InstanceType::String => Self::String,
983 }
984 }
985}
986
987impl From<ReorderedInstanceType> for InstanceType {
988 fn from(instance_type: ReorderedInstanceType) -> Self {
989 match instance_type {
990 ReorderedInstanceType::Null => Self::Null,
991 ReorderedInstanceType::Boolean => Self::Boolean,
992 ReorderedInstanceType::Object => Self::Object,
993 ReorderedInstanceType::Array => Self::Array,
994 ReorderedInstanceType::Integer => Self::Integer,
995 ReorderedInstanceType::Number => Self::Number,
996 ReorderedInstanceType::String => Self::String,
997 }
998 }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use std::collections::BTreeMap;
1004
1005 use schemars::{
1006 gen::{SchemaGenerator, SchemaSettings},
1007 schema::StringValidation,
1008 schema_for, JsonSchema,
1009 };
1010
1011 use crate::{
1012 util::{
1013 all_mutually_exclusive, decode_segment, sanitize, schemas_mutually_exclusive, Case,
1014 ReorderedInstanceType,
1015 },
1016 Name,
1017 };
1018
1019 use super::StringValidator;
1020
1021 #[test]
1022 fn test_non_exclusive_structs() {
1023 #![allow(dead_code)]
1024
1025 #[derive(JsonSchema)]
1026 struct A {
1027 a: Option<()>,
1028 b: (),
1029 }
1030
1031 #[derive(JsonSchema)]
1032 struct B {
1033 a: (),
1034 b: Option<()>,
1035 }
1036
1037 let a = schema_for!(A).schema.into();
1038 let b = schema_for!(B).schema.into();
1039
1040 assert!(!schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1041 assert!(!schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1042 }
1043
1044 #[test]
1045 fn test_non_exclusive_oneof_subschema() {
1046 #![allow(dead_code)]
1047
1048 #[derive(JsonSchema)]
1049 enum A {
1050 B(i32),
1051 C(i64),
1052 }
1053
1054 let mut settings = SchemaSettings::default();
1055 settings.inline_subschemas = true;
1056 let gen = SchemaGenerator::new(settings);
1057
1058 let a = gen.into_root_schema_for::<Vec<A>>().schema.into();
1059
1060 assert!(!schemas_mutually_exclusive(&a, &a, &BTreeMap::new()));
1061 }
1062
1063 #[test]
1064 fn test_unique_prop_structs() {
1065 #![allow(dead_code)]
1066
1067 #[derive(JsonSchema)]
1068 struct A {
1069 a: Option<()>,
1070 b: (),
1071 }
1072
1073 #[derive(JsonSchema)]
1074 struct B {
1075 a: (),
1076 b: Option<()>,
1077 c: (),
1078 }
1079
1080 let a = schema_for!(A).schema.into();
1081 let b = schema_for!(B).schema.into();
1082
1083 assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1084 assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1085 }
1086
1087 #[test]
1088 fn test_exclusive_structs() {
1089 #![allow(dead_code)]
1090
1091 #[derive(JsonSchema)]
1092 struct A {
1093 a: Option<()>,
1094 b: (),
1095 aa: (),
1096 }
1097
1098 #[derive(JsonSchema)]
1099 struct B {
1100 a: (),
1101 b: Option<()>,
1102 bb: (),
1103 }
1104
1105 let a = schema_for!(A).schema.into();
1106 let b = schema_for!(B).schema.into();
1107
1108 assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1109 assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1110 }
1111
1112 #[test]
1113 fn test_exclusive_simple_arrays() {
1114 let a = schema_for!(Vec<u32>).schema.into();
1115 let b = schema_for!(Vec<f32>).schema.into();
1116
1117 assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1118 assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1119 }
1120
1121 #[test]
1122 fn test_all_mutually_exclusive_empty() {
1123 assert!(all_mutually_exclusive(&[], &BTreeMap::new()));
1124 }
1125
1126 #[test]
1127 fn test_decode_segment() {
1128 assert_eq!(decode_segment("foo~1bar"), "foo/bar");
1129 assert_eq!(decode_segment("foo~0bar"), "foo~bar");
1130 }
1131
1132 #[test]
1133 fn test_sanitize() {
1134 assert_eq!(sanitize("type", Case::Snake), "type_");
1135 assert_eq!(sanitize("ref", Case::Snake), "ref_");
1136 assert_eq!(sanitize("gen", Case::Snake), "gen_");
1137 assert_eq!(sanitize("gen", Case::Pascal), "Gen");
1138 assert_eq!(sanitize("+1", Case::Snake), "plus1");
1139 assert_eq!(sanitize("-1", Case::Snake), "minus1");
1140 assert_eq!(sanitize("@timestamp", Case::Pascal), "Timestamp");
1141 assert_eq!(sanitize("won't and can't", Case::Pascal), "WontAndCant");
1142 assert_eq!(
1143 sanitize(
1144 "urn:ietf:params:scim:schemas:extension:gluu:2.0:user_",
1145 Case::Pascal
1146 ),
1147 "UrnIetfParamsScimSchemasExtensionGluu20User"
1148 );
1149 assert_eq!(sanitize("Ipv6Net", Case::Snake), "ipv6_net");
1150 assert_eq!(sanitize("V6", Case::Pascal), "V6");
1151 }
1152
1153 #[test]
1154 fn test_string_validation() {
1155 let permissive = StringValidator::new(&Name::Unknown, None).unwrap();
1156 assert!(permissive.is_valid("everything should be fine"));
1157 assert!(permissive.is_valid(""));
1158
1159 let also_permissive = StringValidator::new(
1160 &Name::Unknown,
1161 Some(&StringValidation {
1162 max_length: None,
1163 min_length: None,
1164 pattern: None,
1165 }),
1166 )
1167 .unwrap();
1168 assert!(also_permissive.is_valid("everything should be fine"));
1169 assert!(also_permissive.is_valid(""));
1170
1171 let eight = StringValidator::new(
1172 &Name::Unknown,
1173 Some(&StringValidation {
1174 max_length: Some(8),
1175 min_length: Some(8),
1176 pattern: None,
1177 }),
1178 )
1179 .unwrap();
1180 assert!(eight.is_valid("Shadrach"));
1181 assert!(!eight.is_valid("Meshach"));
1182 assert!(eight.is_valid("Abednego"));
1183
1184 let ach = StringValidator::new(
1185 &Name::Unknown,
1186 Some(&StringValidation {
1187 max_length: None,
1188 min_length: None,
1189 pattern: Some("ach$".to_string()),
1190 }),
1191 )
1192 .unwrap();
1193 assert!(ach.is_valid("Shadrach"));
1194 assert!(ach.is_valid("Meshach"));
1195 assert!(!ach.is_valid("Abednego"));
1196 }
1197
1198 #[test]
1199 fn test_string_validation_multi_byte() {
1200 let five = StringValidator::new(
1203 &Name::Unknown,
1204 Some(&StringValidation {
1205 max_length: Some(5),
1206 min_length: Some(5),
1207 pattern: None,
1208 }),
1209 )
1210 .unwrap();
1211 assert!(five.is_valid("héllo"));
1212 assert!(five.is_valid("hello"));
1213 assert_eq!("héllo".len(), 6);
1215 assert_eq!("héllo".chars().count(), 5);
1216
1217 let one = StringValidator::new(
1220 &Name::Unknown,
1221 Some(&StringValidation {
1222 max_length: Some(1),
1223 min_length: Some(1),
1224 pattern: None,
1225 }),
1226 )
1227 .unwrap();
1228 assert!(one.is_valid("🍔"));
1229 assert_eq!("🍔".len(), 4);
1230 assert_eq!("🍔".chars().count(), 1);
1231 assert!(!one.is_valid("🍔🍔"));
1233
1234 let eight = StringValidator::new(
1237 &Name::Unknown,
1238 Some(&StringValidation {
1239 max_length: Some(8),
1240 min_length: Some(8),
1241 pattern: None,
1242 }),
1243 )
1244 .unwrap();
1245 let emoji8 = "🍔".repeat(8);
1246 assert_eq!(emoji8.len(), 32);
1247 assert_eq!(emoji8.chars().count(), 8);
1248 assert!(eight.is_valid(&emoji8));
1249 assert!(!eight.is_valid("🍔".repeat(7)));
1250 assert!(!eight.is_valid("🍔".repeat(9)));
1251 }
1252
1253 #[test]
1254 fn test_instance_type_ordering() {
1255 let null = ReorderedInstanceType::Null;
1256 let boolean = ReorderedInstanceType::Boolean;
1257 let integer = ReorderedInstanceType::Integer;
1258 let number = ReorderedInstanceType::Number;
1259 let string = ReorderedInstanceType::String;
1260 let array = ReorderedInstanceType::Array;
1261 let object = ReorderedInstanceType::Object;
1262
1263 assert!(null < boolean);
1264 assert!(boolean < integer);
1265 assert!(integer < number);
1266 assert!(number < string);
1267 assert!(string < array);
1268 assert!(array < object);
1269 }
1270}