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 (0..len - 1)
53 .flat_map(|ii| (ii + 1..len).map(move |jj| (ii, jj)))
54 .all(|(ii, jj)| {
55 let a = resolve(&subschemas[ii], definitions);
56 let b = resolve(&subschemas[jj], definitions);
57 schemas_mutually_exclusive(a, b, definitions)
58 })
59}
60
61fn schemas_mutually_exclusive(
64 a: &Schema,
65 b: &Schema,
66 definitions: &BTreeMap<RefKey, Schema>,
67) -> bool {
68 match (a, b) {
69 (Schema::Bool(false), _) => true,
71 (_, Schema::Bool(false)) => true,
72
73 (Schema::Bool(true), _) => false,
75 (_, Schema::Bool(true)) => false,
76
77 (
79 other,
80 Schema::Object(SchemaObject {
81 metadata: None,
82 instance_type: None,
83 format: None,
84 enum_values: None,
85 const_value: None,
86 subschemas: Some(subschemas),
87 number: None,
88 string: None,
89 array: None,
90 object: None,
91 reference: None,
92 extensions: _,
93 }),
94 )
95 | (
96 Schema::Object(SchemaObject {
97 metadata: None,
98 instance_type: None,
99 format: None,
100 enum_values: None,
101 const_value: None,
102 subschemas: Some(subschemas),
103 number: None,
104 string: None,
105 array: None,
106 object: None,
107 reference: None,
108 extensions: _,
109 }),
110 other,
111 ) => match subschemas.as_ref() {
112 SubschemaValidation {
115 all_of: Some(s),
116 any_of: None,
117 one_of: None,
118 not: None,
119 if_schema: None,
120 then_schema: None,
121 else_schema: None,
122 } => s
123 .iter()
124 .any(|sub| schemas_mutually_exclusive(sub, other, definitions)),
125
126 SubschemaValidation {
128 all_of: None,
129 any_of: Some(s),
130 one_of: None,
131 not: None,
132 if_schema: None,
133 then_schema: None,
134 else_schema: None,
135 }
136 | SubschemaValidation {
137 all_of: None,
138 any_of: None,
139 one_of: Some(s),
140 not: None,
141 if_schema: None,
142 then_schema: None,
143 else_schema: None,
144 } => s
145 .iter()
146 .all(|sub| schemas_mutually_exclusive(sub, other, definitions)),
147
148 SubschemaValidation {
150 all_of: None,
151 any_of: None,
152 one_of: None,
153 not: Some(sub),
154 if_schema: None,
155 then_schema: None,
156 else_schema: None,
157 } => !schemas_mutually_exclusive(sub, other, definitions),
158
159 _ => false,
162 },
163
164 (
170 schema,
171 Schema::Object(SchemaObject {
172 instance_type: None,
173 enum_values: Some(enum_values),
174 ..
175 }),
176 )
177 | (
178 Schema::Object(SchemaObject {
179 instance_type: None,
180 enum_values: Some(enum_values),
181 ..
182 }),
183 schema,
184 ) => enum_values
185 .iter()
186 .all(|value| schema_value_validate(schema, value, definitions).is_err()),
187
188 (
191 schema,
192 Schema::Object(SchemaObject {
193 const_value: Some(value),
194 ..
195 }),
196 )
197 | (
198 Schema::Object(SchemaObject {
199 const_value: Some(value),
200 ..
201 }),
202 schema,
203 ) => schema_value_validate(schema, value, definitions).is_err(),
204
205 (Schema::Object(a), Schema::Object(b)) => {
207 match (&a.instance_type, &b.instance_type) {
208 (None, _) => false,
211 (_, None) => false,
212
213 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
216 if a_single != b_single =>
217 {
218 true
219 }
220
221 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
225 if a_single == b_single && a_single.as_ref() == &InstanceType::Object =>
226 {
227 if let (
228 SchemaObject {
229 metadata: _,
230 instance_type: _,
231 format: None,
232 enum_values: None,
233 const_value: None,
234 subschemas: None,
235 number: None,
236 string: None,
237 array: None,
238 object: Some(a_validation),
239 reference: None,
240 extensions: _,
241 },
242 SchemaObject {
243 metadata: _,
244 instance_type: _,
245 format: None,
246 enum_values: None,
247 const_value: None,
248 subschemas: None,
249 number: None,
250 string: None,
251 array: None,
252 object: Some(b_validation),
253 reference: None,
254 extensions: _,
255 },
256 ) = (a, b)
257 {
258 object_schemas_mutually_exclusive(a_validation, b_validation)
259 } else {
260 false
262 }
263 }
264
265 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single)))
269 if a_single == b_single && a_single.as_ref() == &InstanceType::Array =>
270 {
271 if let (
272 SchemaObject {
273 metadata: _,
274 instance_type: _,
275 format: None,
276 enum_values: None,
277 const_value: None,
278 subschemas: None,
279 number: None,
280 string: None,
281 array: Some(a_validation),
282 object: None,
283 reference: None,
284 extensions: _,
285 },
286 SchemaObject {
287 metadata: _,
288 instance_type: _,
289 format: None,
290 enum_values: None,
291 const_value: None,
292 subschemas: None,
293 number: None,
294 string: None,
295 array: Some(b_validation),
296 object: None,
297 reference: None,
298 extensions: _,
299 },
300 ) = (a, b)
301 {
302 array_schemas_mutually_exclusive(a_validation, b_validation, definitions)
303 } else {
304 false
306 }
307 }
308
309 (Some(SingleOrVec::Single(a_single)), Some(SingleOrVec::Single(b_single))) => {
312 a_single != b_single
313 }
314
315 (Some(SingleOrVec::Vec(a_vec)), Some(SingleOrVec::Vec(b_vec))) => a_vec
318 .iter()
319 .all(|instance_type| !b_vec.contains(instance_type)),
320
321 (Some(SingleOrVec::Single(single)), Some(SingleOrVec::Vec(vec)))
325 | (Some(SingleOrVec::Vec(vec)), Some(SingleOrVec::Single(single))) => {
326 !vec.contains(single)
327 }
328 }
329 }
330 }
331}
332
333fn object_schemas_mutually_exclusive(
338 a_validation: &ObjectValidation,
339 b_validation: &ObjectValidation,
340) -> bool {
341 let ObjectValidation {
342 required: a_required,
343 properties: a_properties,
344 ..
345 } = a_validation;
346 let ObjectValidation {
347 required: b_required,
348 properties: b_properties,
349 ..
350 } = b_validation;
351
352 if a_properties.is_empty() || b_properties.is_empty() {
354 return false;
355 }
356
357 if !a_required.is_subset(&b_properties.keys().cloned().collect())
362 || !b_required.is_subset(&a_properties.keys().cloned().collect())
363 {
364 true
365 } else {
366 let aa = a_required
378 .iter()
379 .filter_map(|name| {
380 let t = a_properties.get(name).unwrap();
381 constant_string_value(t).map(|s| (name.clone(), s))
382 })
383 .collect::<HashSet<_>>();
384 let bb = b_required
385 .iter()
386 .filter_map(|name| {
387 let t = b_properties.get(name).unwrap();
388 constant_string_value(t).map(|s| (name.clone(), s))
389 })
390 .collect::<HashSet<_>>();
391
392 !aa.is_subset(&bb) && !bb.is_subset(&aa)
394 }
395}
396
397fn array_schemas_mutually_exclusive(
398 a_validation: &ArrayValidation,
399 b_validation: &ArrayValidation,
400 definitions: &BTreeMap<RefKey, Schema>,
401) -> bool {
402 match (a_validation, b_validation) {
403 (
409 ArrayValidation {
410 items: Some(SingleOrVec::Single(single)),
411 additional_items: None,
412 ..
413 },
414 ArrayValidation {
415 items: Some(SingleOrVec::Vec(vec)),
416 additional_items: None,
417 max_items: Some(max_items),
418 min_items: Some(min_items),
419 unique_items: None,
420 contains: None,
421 },
422 )
423 | (
424 ArrayValidation {
425 items: Some(SingleOrVec::Vec(vec)),
426 additional_items: None,
427 max_items: Some(max_items),
428 min_items: Some(min_items),
429 unique_items: None,
430 contains: None,
431 },
432 ArrayValidation {
433 items: Some(SingleOrVec::Single(single)),
434 additional_items: None,
435 ..
436 },
437 ) if max_items == min_items && *max_items as usize == vec.len() => vec
438 .iter()
439 .any(|schema| schemas_mutually_exclusive(schema, single, definitions)),
440
441 (aa, bb) => {
442 match (&aa.max_items, &bb.min_items) {
444 (Some(max), Some(min)) if min > max => return true,
445 _ => (),
446 }
447 match (&bb.max_items, &aa.min_items) {
448 (Some(max), Some(min)) if min > max => return true,
449 _ => (),
450 }
451
452 match (&aa.items, &aa.max_items, &bb.items, &bb.max_items) {
453 (Some(SingleOrVec::Single(a_items)), _, Some(SingleOrVec::Single(b_items)), _)
456 if schemas_mutually_exclusive(a_items, b_items, definitions) =>
457 {
458 return true;
459 }
460
461 _ => (),
462 }
463 debug!(
464 "giving up on mutual exclusivity check {} {}",
465 serde_json::to_string_pretty(aa).unwrap(),
466 serde_json::to_string_pretty(bb).unwrap(),
467 );
468 false
469 }
470 }
471}
472
473pub(crate) fn constant_string_value(schema: &Schema) -> Option<&str> {
476 match schema {
477 Schema::Object(SchemaObject {
479 metadata: _,
480 instance_type: Some(SingleOrVec::Single(single)),
481 format: None,
482 enum_values: Some(values),
483 const_value: None,
484 subschemas: None,
485 number: None,
486 string: None,
487 array: None,
488 object: None,
489 reference: None,
490 extensions: _,
491 }) if single.as_ref() == &InstanceType::String && values.len() == 1 => {
492 values.first().unwrap().as_str()
493 }
494
495 Schema::Object(SchemaObject {
497 metadata: _,
498 instance_type: None,
499 format: None,
500 enum_values: Some(values),
501 const_value: None,
502 subschemas: None,
503 number: None,
504 string: None,
505 array: None,
506 object: None,
507 reference: None,
508 extensions: _,
509 }) if values.len() == 1 => values.first().unwrap().as_str(),
510
511 Schema::Object(SchemaObject {
513 metadata: _,
514 instance_type: Some(SingleOrVec::Single(single)),
515 format: None,
516 enum_values: None,
517 const_value: Some(value),
518 subschemas: None,
519 number: None,
520 string: None,
521 array: None,
522 object: None,
523 reference: None,
524 extensions: _,
525 }) if single.as_ref() == &InstanceType::String => value.as_str(),
526
527 Schema::Object(SchemaObject {
529 metadata: _,
530 instance_type: None,
531 format: None,
532 enum_values: None,
533 const_value: Some(value),
534 subschemas: None,
535 number: None,
536 string: None,
537 array: None,
538 object: None,
539 reference: None,
540 extensions: _,
541 }) => value.as_str(),
542
543 _ => None,
544 }
545}
546
547fn decode_segment(segment: &str) -> String {
548 segment.replace("~1", "/").replace("~0", "~")
549}
550
551pub(crate) fn ref_key(ref_name: &str) -> RefKey {
552 if ref_name == "#" {
553 RefKey::Root
554 } else if let Some(idx) = ref_name.rfind('/') {
555 let decoded_segment = decode_segment(&ref_name[idx + 1..]);
556
557 RefKey::Def(decoded_segment)
558 } else {
559 panic!("expected a '/' in $ref: {}", ref_name)
560 }
561}
562
563fn resolve<'a>(
564 schema: &'a Schema,
565 definitions: &'a std::collections::BTreeMap<RefKey, Schema>,
566) -> &'a Schema {
567 match schema {
568 Schema::Bool(_) => schema,
569 Schema::Object(SchemaObject {
570 metadata: _,
571 instance_type: None,
572 format: None,
573 enum_values: None,
574 const_value: None,
575 subschemas: None,
576 number: None,
577 string: None,
578 array: None,
579 object: None,
580 reference: Some(ref_name),
581 extensions: _,
582 }) => definitions.get(&ref_key(ref_name)).unwrap(),
583 Schema::Object(SchemaObject {
584 reference: None, ..
585 }) => schema,
586 _ => todo!(),
588 }
589}
590
591pub(crate) fn schema_is_named(schema: &Schema) -> Option<String> {
593 let raw_name = match schema {
594 Schema::Object(SchemaObject {
595 metadata: _,
596 instance_type: None,
597 format: None,
598 enum_values: None,
599 const_value: None,
600 subschemas: None,
601 number: None,
602 string: None,
603 array: None,
604 object: None,
605 reference: Some(reference),
606 extensions: _,
607 }) => {
608 let idx = reference.rfind('/')?;
609 Some(reference[idx + 1..].to_string())
610 }
611
612 Schema::Object(SchemaObject {
613 metadata: Some(metadata),
614 ..
615 }) if metadata.as_ref().title.is_some() => Some(metadata.as_ref().title.as_ref()?.clone()),
616
617 Schema::Object(SchemaObject {
618 metadata: _,
619 instance_type: _,
620 format: None,
621 enum_values: None,
622 const_value: None,
623 subschemas: Some(subschemas),
624 number: None,
625 string: None,
626 array: None,
627 object: None,
628 reference: None,
629 extensions: _,
630 }) => singleton_subschema(subschemas).and_then(schema_is_named),
631
632 Schema::Object(SchemaObject {
634 instance_type: Some(SingleOrVec::Single(single)),
635 format,
636 ..
637 }) => match (**single, format.as_deref()) {
638 (_, Some(format)) => Some(format.to_pascal_case()),
639 (InstanceType::Boolean, _) => Some("Boolean".to_string()),
640 (InstanceType::Integer, _) => Some("Integer".to_string()),
641 (InstanceType::Number, _) => Some("Number".to_string()),
642 (InstanceType::String, _) => Some("String".to_string()),
643 (InstanceType::Array, _) => Some("Array".to_string()),
644 (InstanceType::Object, _) => Some("Object".to_string()),
645 (InstanceType::Null, _) => Some("Null".to_string()),
646 },
647
648 _ => None,
649 }?;
650
651 Some(sanitize(&raw_name, Case::Pascal))
652}
653
654pub(crate) fn get_object(schema: &Schema) -> Option<(&Option<Box<Metadata>>, &ObjectValidation)> {
657 match schema {
658 Schema::Object(SchemaObject {
660 metadata,
661 instance_type: Some(SingleOrVec::Single(single)),
662 format: None,
663 enum_values: None,
664 const_value: None,
665 subschemas: None,
666 number: _,
667 string: _,
668 array: _,
669 object: Some(validation),
670 reference: None,
671 extensions: _,
672 }) if single.as_ref() == &InstanceType::Object
673 && schema_none_or_false(&validation.additional_properties)
674 && validation.max_properties.is_none()
675 && validation.min_properties.is_none()
676 && validation.pattern_properties.is_empty()
677 && validation.property_names.is_none() =>
678 {
679 Some((metadata, validation.as_ref()))
680 }
681 Schema::Object(SchemaObject {
683 metadata,
684 instance_type: None,
685 format: None,
686 enum_values: None,
687 const_value: None,
688 subschemas: None,
689 number: None,
690 string: None,
691 array: None,
692 object: Some(validation),
693 reference: None,
694 extensions: _,
695 }) if schema_none_or_false(&validation.additional_properties)
696 && validation.max_properties.is_none()
697 && validation.min_properties.is_none()
698 && validation.pattern_properties.is_empty()
699 && validation.property_names.is_none() =>
700 {
701 Some((metadata, validation.as_ref()))
702 }
703
704 Schema::Object(SchemaObject {
706 metadata,
707 instance_type: _,
708 format: None,
709 enum_values: None,
710 const_value: None,
711 subschemas: Some(subschemas),
712 number: None,
713 string: None,
714 array: None,
715 object: None,
716 reference: None,
717 extensions: _,
718 }) => singleton_subschema(subschemas).and_then(|sub_schema| {
719 get_object(sub_schema).map(|(m, validation)| match m {
720 Some(_) => (metadata, validation),
721 None => (&None, validation),
722 })
723 }),
724
725 _ => None,
727 }
728}
729
730fn schema_none_or_false(additional_properties: &Option<Box<Schema>>) -> bool {
733 matches!(
734 additional_properties.as_ref().map(Box::as_ref),
735 None | Some(Schema::Bool(false))
736 )
737}
738
739pub(crate) fn singleton_subschema(subschemas: &SubschemaValidation) -> Option<&Schema> {
740 match subschemas {
741 SubschemaValidation {
742 all_of: Some(subschemas),
743 any_of: None,
744 one_of: None,
745 not: None,
746 if_schema: None,
747 then_schema: None,
748 else_schema: None,
749 }
750 | SubschemaValidation {
751 all_of: None,
752 any_of: Some(subschemas),
753 one_of: None,
754 not: None,
755 if_schema: None,
756 then_schema: None,
757 else_schema: None,
758 }
759 | SubschemaValidation {
760 all_of: None,
761 any_of: None,
762 one_of: Some(subschemas),
763 not: None,
764 if_schema: None,
765 then_schema: None,
766 else_schema: None,
767 } if subschemas.len() == 1 => subschemas.first(),
768 _ => None,
769 }
770}
771
772pub(crate) enum Case {
773 Pascal,
774 Snake,
775}
776
777pub(crate) fn sanitize(input: &str, case: Case) -> String {
778 use heck::{ToPascalCase, ToSnakeCase};
779 let to_case = match case {
780 Case::Pascal => str::to_pascal_case,
781 Case::Snake => str::to_snake_case,
782 };
783
784 let out = match input {
786 "+1" => "plus1".to_string(),
787 "-1" => "minus1".to_string(),
788 _ => to_case(&input.replace("'", "").replace(|c| !is_xid_continue(c), "-")),
789 };
790
791 let prefix = to_case("x");
792
793 let out = match out.chars().next() {
794 None => prefix,
795 Some(c) if is_xid_start(c) => out,
796 Some(_) => format!("{}{}", prefix, out),
797 };
798
799 if accept_as_ident(&out) {
801 out
802 } else {
803 format!("{}_", out)
804 }
805}
806
807pub fn accept_as_ident(ident: &str) -> bool {
812 match ident {
815 "_" |
816 "abstract" | "as" | "async" | "await" | "become" | "box" | "break" |
818 "const" | "continue" | "crate" | "do" | "dyn" | "else" | "enum" |
819 "extern" | "false" | "final" | "fn" | "for" | "gen" | "if" | "impl" |
820 "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" |
821 "override" | "priv" | "pub" | "ref" | "return" | "Self" | "self" |
822 "static" | "struct" | "super" | "trait" | "true" | "try" | "type" |
823 "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" |
824 "while" | "yield" => false,
825 _ => true,
826 }
827}
828
829pub(crate) fn recase(input: &str, case: Case) -> (String, Option<String>) {
830 let new = sanitize(input, case);
831 let rename = if new == input {
832 None
833 } else {
834 Some(input.to_string())
835 };
836 (new, rename)
837}
838
839pub(crate) fn unique<I, T>(items: I) -> bool
840where
841 I: IntoIterator<Item = T>,
842 T: Eq + std::hash::Hash,
843{
844 let mut unique = HashSet::new();
845 items.into_iter().all(|item| unique.insert(item))
846}
847
848pub(crate) fn get_type_name(type_name: &Name, metadata: &Option<Box<Metadata>>) -> Option<String> {
849 let name = match (type_name, metadata_title(metadata)) {
850 (Name::Required(name), _) => name.clone(),
851 (Name::Suggested(name), None) => name.clone(),
852 (_, Some(name)) => name,
853 (Name::Unknown, None) => None?,
854 };
855
856 Some(sanitize(&name, Case::Pascal))
857}
858
859pub(crate) struct TypePatch {
860 pub name: String,
861 pub derives: BTreeSet<String>,
862 pub attrs: BTreeSet<String>,
863}
864
865impl TypePatch {
866 pub fn new(type_space: &TypeSpace, type_name: String) -> Self {
868 match type_space.settings.patch.get(&type_name) {
869 None => Self {
870 name: type_name,
871 derives: Default::default(),
872 attrs: Default::default(),
873 },
874
875 Some(patch) => {
876 let name = patch.rename.clone().unwrap_or(type_name);
877 let derives = patch.derives.iter().cloned().collect();
878 let attrs = patch.attrs.iter().cloned().collect();
879
880 Self {
881 name,
882 derives,
883 attrs,
884 }
885 }
886 }
887 }
888}
889
890pub(crate) struct StringValidator {
891 max_length: Option<u32>,
892 min_length: Option<u32>,
893 pattern: Option<regress::Regex>,
894}
895
896impl StringValidator {
897 pub fn new(type_name: &Name, validation: Option<&StringValidation>) -> Result<Self> {
898 let (max_length, min_length, pattern) =
899 validation.map_or(Ok((None, None, None)), |validation| {
900 let max = validation.max_length;
901 let min = validation.min_length;
902 let pattern = validation
903 .pattern
904 .as_ref()
905 .map(|pattern| {
906 regress::Regex::new(pattern).map_err(|e| Error::InvalidSchema {
907 type_name: type_name.clone().into_option(),
908 reason: format!("invalid pattern '{}' {}", pattern, e),
909 })
910 })
911 .transpose()?;
912 Ok((max, min, pattern))
913 })?;
914 Ok(Self {
915 max_length,
916 min_length,
917 pattern,
918 })
919 }
920
921 pub fn is_valid<S: AsRef<str>>(&self, s: S) -> bool {
922 self.max_length
923 .as_ref()
924 .is_none_or(|max| s.as_ref().len() as u32 <= *max)
925 && self
926 .min_length
927 .as_ref()
928 .is_none_or(|min| s.as_ref().len() as u32 >= *min)
929 && self
930 .pattern
931 .as_ref()
932 .is_none_or(|pattern| pattern.find(s.as_ref()).is_some())
933 }
934}
935
936#[cfg(test)]
937mod tests {
938 use std::collections::BTreeMap;
939
940 use schemars::{
941 gen::{SchemaGenerator, SchemaSettings},
942 schema::StringValidation,
943 schema_for, JsonSchema,
944 };
945
946 use crate::{
947 util::{decode_segment, sanitize, schemas_mutually_exclusive, Case},
948 Name,
949 };
950
951 use super::StringValidator;
952
953 #[test]
954 fn test_non_exclusive_structs() {
955 #![allow(dead_code)]
956
957 #[derive(JsonSchema)]
958 struct A {
959 a: Option<()>,
960 b: (),
961 }
962
963 #[derive(JsonSchema)]
964 struct B {
965 a: (),
966 b: Option<()>,
967 }
968
969 let a = schema_for!(A).schema.into();
970 let b = schema_for!(B).schema.into();
971
972 assert!(!schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
973 assert!(!schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
974 }
975
976 #[test]
977 fn test_non_exclusive_oneof_subschema() {
978 #![allow(dead_code)]
979
980 #[derive(JsonSchema)]
981 enum A {
982 B(i32),
983 C(i64),
984 }
985
986 let mut settings = SchemaSettings::default();
987 settings.inline_subschemas = true;
988 let gen = SchemaGenerator::new(settings);
989
990 let a = gen.into_root_schema_for::<Vec<A>>().schema.into();
991
992 assert!(!schemas_mutually_exclusive(&a, &a, &BTreeMap::new()));
993 }
994
995 #[test]
996 fn test_unique_prop_structs() {
997 #![allow(dead_code)]
998
999 #[derive(JsonSchema)]
1000 struct A {
1001 a: Option<()>,
1002 b: (),
1003 }
1004
1005 #[derive(JsonSchema)]
1006 struct B {
1007 a: (),
1008 b: Option<()>,
1009 c: (),
1010 }
1011
1012 let a = schema_for!(A).schema.into();
1013 let b = schema_for!(B).schema.into();
1014
1015 assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1016 assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1017 }
1018
1019 #[test]
1020 fn test_exclusive_structs() {
1021 #![allow(dead_code)]
1022
1023 #[derive(JsonSchema)]
1024 struct A {
1025 a: Option<()>,
1026 b: (),
1027 aa: (),
1028 }
1029
1030 #[derive(JsonSchema)]
1031 struct B {
1032 a: (),
1033 b: Option<()>,
1034 bb: (),
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_exclusive_simple_arrays() {
1046 let a = schema_for!(Vec<u32>).schema.into();
1047 let b = schema_for!(Vec<f32>).schema.into();
1048
1049 assert!(schemas_mutually_exclusive(&a, &b, &BTreeMap::new()));
1050 assert!(schemas_mutually_exclusive(&b, &a, &BTreeMap::new()));
1051 }
1052
1053 #[test]
1054 fn test_decode_segment() {
1055 assert_eq!(decode_segment("foo~1bar"), "foo/bar");
1056 assert_eq!(decode_segment("foo~0bar"), "foo~bar");
1057 }
1058
1059 #[test]
1060 fn test_sanitize() {
1061 assert_eq!(sanitize("type", Case::Snake), "type_");
1062 assert_eq!(sanitize("ref", Case::Snake), "ref_");
1063 assert_eq!(sanitize("gen", Case::Snake), "gen_");
1064 assert_eq!(sanitize("gen", Case::Pascal), "Gen");
1065 assert_eq!(sanitize("+1", Case::Snake), "plus1");
1066 assert_eq!(sanitize("-1", Case::Snake), "minus1");
1067 assert_eq!(sanitize("@timestamp", Case::Pascal), "Timestamp");
1068 assert_eq!(sanitize("won't and can't", Case::Pascal), "WontAndCant");
1069 assert_eq!(
1070 sanitize(
1071 "urn:ietf:params:scim:schemas:extension:gluu:2.0:user_",
1072 Case::Pascal
1073 ),
1074 "UrnIetfParamsScimSchemasExtensionGluu20User"
1075 );
1076 assert_eq!(sanitize("Ipv6Net", Case::Snake), "ipv6_net");
1077 assert_eq!(sanitize("V6", Case::Pascal), "V6");
1078 }
1079
1080 #[test]
1081 fn test_string_validation() {
1082 let permissive = StringValidator::new(&Name::Unknown, None).unwrap();
1083 assert!(permissive.is_valid("everything should be fine"));
1084 assert!(permissive.is_valid(""));
1085
1086 let also_permissive = StringValidator::new(
1087 &Name::Unknown,
1088 Some(&StringValidation {
1089 max_length: None,
1090 min_length: None,
1091 pattern: None,
1092 }),
1093 )
1094 .unwrap();
1095 assert!(also_permissive.is_valid("everything should be fine"));
1096 assert!(also_permissive.is_valid(""));
1097
1098 let eight = StringValidator::new(
1099 &Name::Unknown,
1100 Some(&StringValidation {
1101 max_length: Some(8),
1102 min_length: Some(8),
1103 pattern: None,
1104 }),
1105 )
1106 .unwrap();
1107 assert!(eight.is_valid("Shadrach"));
1108 assert!(!eight.is_valid("Meshach"));
1109 assert!(eight.is_valid("Abednego"));
1110
1111 let ach = StringValidator::new(
1112 &Name::Unknown,
1113 Some(&StringValidation {
1114 max_length: None,
1115 min_length: None,
1116 pattern: Some("ach$".to_string()),
1117 }),
1118 )
1119 .unwrap();
1120 assert!(ach.is_valid("Shadrach"));
1121 assert!(ach.is_valid("Meshach"));
1122 assert!(!ach.is_valid("Abednego"));
1123 }
1124}