1
- // Copyright 2023 Oxide Computer Company
1
+ // Copyright 2024 Oxide Computer Company
2
2
3
3
use std:: {
4
4
collections:: { BTreeMap , BTreeSet } ,
@@ -25,17 +25,19 @@ fn try_merge_all(schemas: &[Schema], defs: &BTreeMap<RefKey, Schema>) -> Result<
25
25
serde_json:: to_string_pretty( schemas) . unwrap( ) ,
26
26
) ;
27
27
28
- match schemas {
28
+ let merged_schema = match schemas {
29
29
[ ] => panic ! ( "we should not be trying to merge an empty array of schemas" ) ,
30
- [ only] => Ok ( only. clone ( ) ) ,
30
+ [ only] => only. clone ( ) ,
31
31
[ first, second, rest @ ..] => {
32
32
let mut out = try_merge_schema ( first, second, defs) ?;
33
33
for schema in rest {
34
34
out = try_merge_schema ( & out, schema, defs) ?;
35
35
}
36
- Ok ( out)
36
+ out
37
37
}
38
- }
38
+ } ;
39
+
40
+ Ok ( merged_schema)
39
41
}
40
42
41
43
/// Given two additionalItems schemas that might be None--which is equivalent
@@ -71,12 +73,16 @@ fn merge_additional_properties(
71
73
}
72
74
}
73
75
76
+ fn merge_schema ( a : & Schema , b : & Schema , defs : & BTreeMap < RefKey , Schema > ) -> Schema {
77
+ try_merge_schema ( a, b, defs) . unwrap_or ( Schema :: Bool ( false ) )
78
+ }
79
+
74
80
/// Merge two schemas returning the resulting schema. If the two schemas are
75
81
/// incompatible (i.e. if there is no data that can satisfy them both
76
82
/// simultaneously) then this returns Err.
77
83
fn try_merge_schema ( a : & Schema , b : & Schema , defs : & BTreeMap < RefKey , Schema > ) -> Result < Schema , ( ) > {
78
84
match ( a, b) {
79
- ( Schema :: Bool ( false ) , _) | ( _, Schema :: Bool ( false ) ) => Ok ( Schema :: Bool ( false ) ) ,
85
+ ( Schema :: Bool ( false ) , _) | ( _, Schema :: Bool ( false ) ) => Err ( ( ) ) ,
80
86
( Schema :: Bool ( true ) , other) | ( other, Schema :: Bool ( true ) ) => Ok ( other. clone ( ) ) ,
81
87
82
88
// If we have two references to the same schema, that's easy!
@@ -489,9 +495,11 @@ fn try_merge_schema_not(
489
495
if required. contains ( not_required) {
490
496
return Err ( ( ) ) ;
491
497
}
492
- // We don't care if a property we're saying is not
493
- // required was not present in the properties map.
494
- let _ = properties. remove ( not_required) ;
498
+ // Set the property's schema to false i.e. that the
499
+ // presence of any value would be invalid. We ignore
500
+ // the return value as it doesn't matter if the
501
+ // property was there previously or not.
502
+ let _ = properties. insert ( not_required. clone ( ) , Schema :: Bool ( false ) ) ;
495
503
}
496
504
}
497
505
}
@@ -947,6 +955,11 @@ fn merge_so_object(
947
955
match ( a, b) {
948
956
( None , other) | ( other, None ) => Ok ( other. cloned ( ) . map ( Box :: new) ) ,
949
957
( Some ( aa) , Some ( bb) ) => {
958
+ let required = aa
959
+ . required
960
+ . union ( & bb. required )
961
+ . cloned ( )
962
+ . collect :: < BTreeSet < _ > > ( ) ;
950
963
let additional_properties = merge_additional_properties (
951
964
aa. additional_properties . as_deref ( ) ,
952
965
bb. additional_properties . as_deref ( ) ,
@@ -984,29 +997,29 @@ fn merge_so_object(
984
997
let resolved_schema = match ab_schema {
985
998
AOrB :: A ( a_schema) => filter_prop ( name, a_schema, bb) ,
986
999
AOrB :: B ( b_schema) => filter_prop ( name, b_schema, aa) ,
987
- AOrB :: Both ( a_schema, b_schema) => {
988
- try_merge_schema ( a_schema, b_schema, defs)
989
- }
1000
+ AOrB :: Both ( a_schema, b_schema) => merge_schema ( a_schema, b_schema, defs) ,
990
1001
} ;
991
1002
match resolved_schema {
992
1003
// If a required field is incompatible with the
993
1004
// other schema, this object is unsatisfiable.
994
- Err ( ( ) ) if aa . required . contains ( name) => Some ( Err ( ( ) ) ) ,
1005
+ Schema :: Bool ( false ) if required. contains ( name) => Some ( Err ( ( ) ) ) ,
995
1006
996
1007
// For incompatible, non-required fields we need to
997
1008
// exclude the property from any values. If
998
1009
// `additionalProperties` is `false` (i.e. excludes all
999
1010
// other properties) then we can simply omit the
1000
- // property. Otherwise we include the optional property
1001
- // but with the `false` schema that means that no value
1002
- // will satisfy that property--the value would always
1003
- // be None and any serialization that included the
1004
- // named property would fail to deserialize.
1011
+ // property knowing that it (like all other unnamed
1012
+ // properties) will not be permitted. Otherwise we
1013
+ // include the optional property but with the `false`
1014
+ // schema that means that no value will satisfy that
1015
+ // property--the value would always be None and any
1016
+ // serialization that included the named property would
1017
+ // fail to deserialize.
1005
1018
//
1006
1019
// If we ever make use of `propertyNames`, it's
1007
1020
// conceivable that we might check it or modify it in
1008
1021
// this case, but that may be overly complex.
1009
- Err ( ( ) ) => {
1022
+ Schema :: Bool ( false ) => {
1010
1023
if let Some ( Schema :: Bool ( false ) ) = additional_properties {
1011
1024
None
1012
1025
} else {
@@ -1015,25 +1028,11 @@ fn merge_so_object(
1015
1028
}
1016
1029
1017
1030
// Compatible schema; proceed.
1018
- Ok ( schema) => Some ( Ok ( ( name. clone ( ) , schema) ) ) ,
1031
+ schema => Some ( Ok ( ( name. clone ( ) , schema) ) ) ,
1019
1032
}
1020
1033
} )
1021
1034
. collect :: < Result < schemars:: Map < _ , _ > , _ > > ( ) ?;
1022
1035
1023
- let additional_properties = additional_properties. map ( Box :: new) ;
1024
- let required = aa. required . union ( & bb. required ) . cloned ( ) . collect ( ) ;
1025
-
1026
- // So. We merged the properties and we merged the array of
1027
- // properties that are required. We use the absence of a property
1028
- // in the properties map to indicate that the property must *not*
1029
- // be present. This is imprecise, but allows us to make progress
1030
- // without a most significant conversion to a new representation.
1031
- for req_prop in & required {
1032
- if !properties. contains_key ( req_prop) {
1033
- return Err ( ( ) ) ;
1034
- }
1035
- }
1036
-
1037
1036
let max_properties = choose_value ( aa. max_properties , bb. max_properties , Ord :: min) ;
1038
1037
let min_properties = choose_value ( aa. min_properties , bb. min_properties , Ord :: max) ;
1039
1038
@@ -1046,7 +1045,7 @@ fn merge_so_object(
1046
1045
let object_validation = ObjectValidation {
1047
1046
required,
1048
1047
properties,
1049
- additional_properties,
1048
+ additional_properties : additional_properties . map ( Box :: new ) ,
1050
1049
max_properties,
1051
1050
min_properties,
1052
1051
pattern_properties : Default :: default ( ) , // TODO
@@ -1057,11 +1056,7 @@ fn merge_so_object(
1057
1056
}
1058
1057
}
1059
1058
1060
- fn filter_prop (
1061
- name : & str ,
1062
- prop_schema : & Schema ,
1063
- object_schema : & ObjectValidation ,
1064
- ) -> Result < Schema , ( ) > {
1059
+ fn filter_prop ( name : & str , prop_schema : & Schema , object_schema : & ObjectValidation ) -> Schema {
1065
1060
// We're only considering properties we *know* do not appear in the other
1066
1061
// object's schema.
1067
1062
assert ! ( !object_schema. properties. contains_key( name) ) ;
@@ -1077,6 +1072,7 @@ fn filter_prop(
1077
1072
assert ! ( object_schema. pattern_properties. is_empty( ) ) ;
1078
1073
1079
1074
merge_additional ( object_schema. additional_properties . as_deref ( ) , prop_schema)
1075
+ . unwrap_or ( Schema :: Bool ( false ) )
1080
1076
}
1081
1077
1082
1078
fn merge_additional ( additional : Option < & Schema > , prop_schema : & Schema ) -> Result < Schema , ( ) > {
0 commit comments