@@ -415,13 +415,35 @@ fn try_merge_with_each_subschema(
415415 joined_schemas
416416}
417417
418+ fn merge_schema_not (
419+ schema : & Schema ,
420+ not_schema : & Schema ,
421+ defs : & BTreeMap < RefKey , Schema > ,
422+ ) -> Schema {
423+ match ( schema, not_schema) {
424+ ( _, Schema :: Bool ( true ) ) | ( Schema :: Bool ( false ) , _) => Schema :: Bool ( false ) ,
425+
426+ ( any, Schema :: Bool ( false ) ) => any. clone ( ) ,
427+
428+ // TODO I don't know how to subtract something from nothing...
429+ ( Schema :: Bool ( true ) , Schema :: Object ( _) ) => todo ! ( ) ,
430+
431+ ( Schema :: Object ( schema_object) , any_not) => {
432+ match try_merge_schema_not ( schema_object. clone ( ) , any_not, defs) {
433+ Ok ( schema_obj) => Schema :: Object ( schema_obj) ,
434+ Err ( _) => Schema :: Bool ( false ) ,
435+ }
436+ }
437+ }
438+ }
439+
418440/// "Subtract" the "not" schema from the schema object.
419441///
420442/// TODO Exactly where and how we handle not constructions is... tricky! As we
421443/// find and support more and more useful uses of not we will likely move some
422444/// of this into the conversion methods.
423445fn try_merge_schema_not (
424- mut schema_object : SchemaObject ,
446+ schema_object : SchemaObject ,
425447 not_schema : & Schema ,
426448 defs : & BTreeMap < RefKey , Schema > ,
427449) -> Result < SchemaObject , ( ) > {
@@ -435,77 +457,8 @@ fn try_merge_schema_not(
435457 Schema :: Bool ( true ) => Err ( ( ) ) ,
436458 // ... whereas subtracting nothing leaves everything.
437459 Schema :: Bool ( false ) => Ok ( schema_object) ,
438-
439- Schema :: Object ( SchemaObject {
440- // I don't think there's any significance to the schema metadata
441- // with respect to the types we might generate.
442- metadata : _,
443- // TODO we should should check instance_type and then walk through
444- // validation of each type based on the specific validation.
445- instance_type : _,
446- format : _,
447- enum_values : _,
448- const_value : _,
449- subschemas,
450- number : _,
451- string : _,
452- array : _,
453- object,
454- // TODO we might want to chase these references but need to take
455- // care to handle circular references.
456- reference : _,
457- extensions : _,
458- } ) => {
459- if let Some ( not_object) = object {
460- // TODO this is incomplete, but seems sufficient for the
461- // schemas we've seen in the wild.
462- if let Some ( ObjectValidation {
463- required,
464- properties,
465- ..
466- } ) = schema_object. object . as_deref_mut ( )
467- {
468- // TODO This is completely wrong for arrays of len > 1.
469- // We need to treat required: [x, y] like it's:
470- // not:
471- // allOf:
472- // required: [x]
473- // required: [y]
474- // Then we can transform them into:
475- // anyOf:
476- // not:
477- // required: [x]
478- // not:
479- // required: [y]
480- // Which in turn can become:
481- // oneOf:
482- // not:
483- // required: [x]
484- // not:
485- // required: [y]
486- // not:
487- // required: [x, y]
488- for not_required in & not_object. required {
489- // A property can't be both required and not required
490- // therefore this schema is unsatisfiable.
491- if required. contains ( not_required) {
492- return Err ( ( ) ) ;
493- }
494- // Set the property's schema to false i.e. that the
495- // presence of any value would be invalid. We ignore
496- // the return value as it doesn't matter if the
497- // property was there previously or not.
498- let _ = properties. insert ( not_required. clone ( ) , Schema :: Bool ( false ) ) ;
499- }
500- }
501- }
502-
503- if let Some ( not_subschemas) = subschemas {
504- schema_object = try_merge_with_subschemas_not ( schema_object, not_subschemas, defs) ?;
505- }
506-
507- Ok ( schema_object)
508- }
460+ // Do the real work.
461+ Schema :: Object ( not_object) => try_merge_schema_object_not ( schema_object, not_object, defs) ,
509462 }
510463}
511464
@@ -604,6 +557,84 @@ fn try_merge_with_subschemas_not(
604557 }
605558}
606559
560+ fn try_merge_schema_object_not (
561+ mut schema_object : SchemaObject ,
562+ not_object : & SchemaObject ,
563+ defs : & BTreeMap < RefKey , Schema > ,
564+ ) -> Result < SchemaObject , ( ) > {
565+ // Examine enum values
566+ match ( & mut schema_object. enum_values , & not_object. enum_values ) {
567+ // Nothing to do.
568+ ( _, None ) => { }
569+ // TODO not sure quite what to do, so we'll ignore for now.
570+ ( None , Some ( _) ) => { }
571+ ( Some ( values) , Some ( not_values) ) => {
572+ values. retain ( |value| !not_values. contains ( value) ) ;
573+ if values. is_empty ( ) {
574+ return Err ( ( ) ) ;
575+ }
576+ }
577+ }
578+
579+ match ( & mut schema_object. object , & not_object. object ) {
580+ // Nothing to do.
581+ ( _, None ) => { }
582+
583+ // TODO Not sure how to enforce the inverse here...
584+ ( None , Some ( _) ) => { }
585+
586+ // In the interesting case, we need to "subtract" object attributes.
587+ ( Some ( obj) , Some ( not_obj) ) => {
588+ for ( prop_name, prop_schema) in & mut obj. properties {
589+ if let Some ( not_prop_schema) = not_obj. properties . get ( prop_name) {
590+ // For properties in both, we merge those schemas. Note
591+ // that if such a merging is unsatisfiable *and* the
592+ // property is required, we'll take the appropriate action
593+ // later.
594+ * prop_schema = merge_schema_not ( prop_schema, not_prop_schema, defs) ;
595+ }
596+ }
597+
598+ for prop_name in not_obj. properties . keys ( ) {
599+ if !obj. properties . contains_key ( prop_name) {
600+ // There's a property in the "not" that isn't in the
601+ // object. Most precisely we would say "this property may
602+ // have any value as long as it doesn't match this schema".
603+ // That's a little tricky right now, so instead we'll say
604+ // "you may not have a property with this name".
605+ let _ = obj
606+ . properties
607+ . insert ( prop_name. clone ( ) , Schema :: Bool ( false ) ) ;
608+ }
609+ }
610+
611+ for not_required in & not_obj. required {
612+ if !not_obj. properties . contains_key ( not_required) {
613+ // No value is permissible
614+ let _ = obj
615+ . properties
616+ . insert ( not_required. clone ( ) , Schema :: Bool ( false ) ) ;
617+ }
618+ }
619+
620+ // If any of the previous steps resulted in a required property
621+ // being invalid, we note that here and identify the full schema as
622+ // invalid.
623+ for required in & obj. required {
624+ if let Some ( Schema :: Bool ( false ) ) = obj. properties . get ( required) {
625+ return Err ( ( ) ) ;
626+ }
627+ }
628+ }
629+ }
630+
631+ if let Some ( not_subschemas) = & not_object. subschemas {
632+ schema_object = try_merge_with_subschemas_not ( schema_object, not_subschemas, defs) ?;
633+ }
634+
635+ Ok ( schema_object)
636+ }
637+
607638/// Merge instance types which could be None (meaning type is valid), a
608639/// singleton type, or an array of types. An error result indicates that the
609640/// types were non-overlappin and therefore incompatible.
0 commit comments