@@ -47,6 +47,34 @@ pub enum ChangeKind {
4747 /// The name of the added property.
4848 removed : String ,
4949 } ,
50+ /// An array-type item has been changed from tuple validation to array validation.
51+ ///
52+ /// See https://json-schema.org/understanding-json-schema/reference/array.html
53+ ///
54+ /// Changes will still be emitted for inner items.
55+ TupleToArray {
56+ /// The length of the (old) tuple
57+ old_length : usize ,
58+ } ,
59+ /// An array-type item has been changed from array validation to tuple validation.
60+ ///
61+ /// See https://json-schema.org/understanding-json-schema/reference/array.html
62+ ///
63+ /// Changes will still be emitted for inner items.
64+ ArrayToTuple {
65+ /// The length of the (new) tuple
66+ new_length : usize ,
67+ } ,
68+ /// An array-type item with tuple validation has changed its length ("items" array got longer
69+ /// or shorter.
70+ ///
71+ /// See https://json-schema.org/understanding-json-schema/reference/array.html
72+ ///
73+ /// Changes will still be emitted for inner items.
74+ TupleChange {
75+ /// The new length of the tuple
76+ new_length : usize ,
77+ } ,
5078}
5179
5280impl ChangeKind {
@@ -69,6 +97,9 @@ impl ChangeKind {
6997 lhs_additional_properties,
7098 ..
7199 } => !* lhs_additional_properties,
100+ Self :: TupleToArray { .. } => false ,
101+ Self :: ArrayToTuple { .. } => true ,
102+ Self :: TupleChange { .. } => true ,
72103 }
73104 }
74105}
@@ -156,12 +187,14 @@ impl JsonSchemaType {
156187 }
157188}
158189
159- #[ derive( Default , Eq , PartialEq ) ]
190+ #[ derive( Default , Eq , PartialEq , Debug ) ]
160191struct JsonSchema {
161192 ty : Option < JsonSchemaType > ,
162193 constant : Option < Value > ,
163194 additional_properties : Option < Box < JsonSchema > > ,
164195 properties : BTreeMap < String , JsonSchema > ,
196+ any_of : Vec < JsonSchema > ,
197+ items : Option < JsonSchemaItems > ,
165198}
166199
167200impl JsonSchema {
@@ -194,8 +227,16 @@ impl<'de> Deserialize<'de> for JsonSchema {
194227 }
195228}
196229
230+ #[ derive( Deserialize , Eq , PartialEq , Debug ) ]
231+ #[ serde( untagged) ]
232+ enum JsonSchemaItems {
233+ ExactItems ( Vec < JsonSchema > ) ,
234+ Items ( Box < JsonSchema > ) ,
235+ }
236+
197237#[ derive( Deserialize , Default ) ]
198238#[ serde( rename_all = "camelCase" ) ]
239+ #[ cfg_attr( test, serde( deny_unknown_fields) ) ]
199240#[ serde( default ) ]
200241struct JsonSchemaRaw {
201242 #[ serde( rename = "type" ) ]
@@ -204,6 +245,8 @@ struct JsonSchemaRaw {
204245 constant : Option < Value > ,
205246 additional_properties : Option < Box < JsonSchema > > ,
206247 properties : BTreeMap < String , JsonSchema > ,
248+ any_of : Vec < JsonSchema > ,
249+ items : Option < JsonSchemaItems > ,
207250}
208251
209252impl From < JsonSchemaRaw > for JsonSchema {
@@ -213,12 +256,16 @@ impl From<JsonSchemaRaw> for JsonSchema {
213256 constant,
214257 additional_properties,
215258 properties,
259+ any_of,
260+ items,
216261 } = raw;
217262 JsonSchema {
218263 ty,
219264 constant,
220265 additional_properties,
221266 properties,
267+ any_of,
268+ items,
222269 }
223270 }
224271}
@@ -275,6 +322,18 @@ fn diff_inner(
275322 lhs : & JsonSchema ,
276323 rhs : & JsonSchema ,
277324) -> Result < ( ) , Error > {
325+ #[ cfg( test) ]
326+ {
327+ dbg ! ( & json_path) ;
328+ dbg ! ( & rhs) ;
329+ dbg ! ( & lhs) ;
330+ }
331+
332+ for ( i, ( lhs_inner, rhs_inner) ) in lhs. any_of . iter ( ) . zip ( rhs. any_of . iter ( ) ) . enumerate ( ) {
333+ let new_path = format ! ( "{json_path}.<anyOf:{i}>" ) ;
334+ diff_inner ( rv, new_path, lhs_inner, rhs_inner) ?;
335+ }
336+
278337 let lhs_ty = lhs. effective_type ( ) . into_set ( ) ;
279338 let rhs_ty = rhs. effective_type ( ) . into_set ( ) ;
280339
@@ -329,19 +388,15 @@ fn diff_inner(
329388 let lhs_child = lhs. properties . get ( common. as_str ( ) ) . unwrap ( ) ;
330389 let rhs_child = rhs. properties . get ( common. as_str ( ) ) . unwrap ( ) ;
331390
332- let mut new_path = json_path. clone ( ) ;
333- new_path. push ( '.' ) ;
334- new_path. push_str ( common) ;
335-
391+ let new_path = format ! ( "{json_path}.{common}" ) ;
336392 diff_inner ( rv, new_path, lhs_child, rhs_child) ?;
337393 }
338394
339395 if let ( Some ( ref lhs_additional_properties) , Some ( ref rhs_additional_properties) ) =
340396 ( & lhs. additional_properties , & rhs. additional_properties )
341397 {
342398 if rhs_additional_properties != lhs_additional_properties {
343- let mut new_path = json_path;
344- new_path. push_str ( ".<additional properties>" ) ;
399+ let new_path = format ! ( "{json_path}.<additionalProperties>" ) ;
345400
346401 diff_inner (
347402 rv,
@@ -352,6 +407,62 @@ fn diff_inner(
352407 }
353408 }
354409
410+ match ( & lhs. items , & rhs. items ) {
411+ (
412+ Some ( JsonSchemaItems :: ExactItems ( lhs_items) ) ,
413+ Some ( JsonSchemaItems :: ExactItems ( rhs_items) ) ,
414+ ) => {
415+ if lhs_items. len ( ) != rhs_items. len ( ) {
416+ rv. push ( Change {
417+ path : json_path. clone ( ) ,
418+ change : ChangeKind :: TupleChange {
419+ new_length : rhs_items. len ( ) ,
420+ } ,
421+ } ) ;
422+ }
423+
424+ for ( i, ( lhs_inner, rhs_inner) ) in lhs_items. iter ( ) . zip ( rhs_items. iter ( ) ) . enumerate ( ) {
425+ let new_path = format ! ( "{json_path}.{i}" ) ;
426+ diff_inner ( rv, new_path, lhs_inner, rhs_inner) ?;
427+ }
428+ }
429+ ( Some ( JsonSchemaItems :: Items ( lhs_inner) ) , Some ( JsonSchemaItems :: Items ( rhs_inner) ) ) => {
430+ let new_path = format ! ( "{json_path}.?" ) ;
431+ diff_inner ( rv, new_path, lhs_inner, rhs_inner) ?;
432+ }
433+ ( Some ( JsonSchemaItems :: Items ( lhs_inner) ) , Some ( JsonSchemaItems :: ExactItems ( rhs_items) ) ) => {
434+ rv. push ( Change {
435+ path : json_path. clone ( ) ,
436+ change : ChangeKind :: ArrayToTuple {
437+ new_length : rhs_items. len ( ) ,
438+ } ,
439+ } ) ;
440+
441+ for ( i, rhs_inner) in rhs_items. iter ( ) . enumerate ( ) {
442+ let new_path = format ! ( "{json_path}.{i}" ) ;
443+ diff_inner ( rv, new_path, lhs_inner, rhs_inner) ?;
444+ }
445+ }
446+ ( Some ( JsonSchemaItems :: ExactItems ( lhs_items) ) , Some ( JsonSchemaItems :: Items ( rhs_inner) ) ) => {
447+ rv. push ( Change {
448+ path : json_path. clone ( ) ,
449+ change : ChangeKind :: TupleToArray {
450+ old_length : lhs_items. len ( ) ,
451+ } ,
452+ } ) ;
453+
454+ for ( i, lhs_inner) in lhs_items. iter ( ) . enumerate ( ) {
455+ let new_path = format ! ( "{json_path}.{i}" ) ;
456+ diff_inner ( rv, new_path, lhs_inner, rhs_inner) ?;
457+ }
458+ }
459+ ( None , None ) => ( ) ,
460+ _ => {
461+ #[ cfg( test) ]
462+ todo ! ( "{:?} {:?}" , lhs. items, rhs. items)
463+ }
464+ }
465+
355466 Ok ( ( ) )
356467}
357468
@@ -478,43 +589,43 @@ mod tests {
478589 @r###"
479590 [
480591 Change {
481- path: ".<additional properties >",
592+ path: ".<additionalProperties >",
482593 change: TypeAdd {
483594 added: String,
484595 },
485596 },
486597 Change {
487- path: ".<additional properties >",
598+ path: ".<additionalProperties >",
488599 change: TypeAdd {
489600 added: Number,
490601 },
491602 },
492603 Change {
493- path: ".<additional properties >",
604+ path: ".<additionalProperties >",
494605 change: TypeAdd {
495606 added: Integer,
496607 },
497608 },
498609 Change {
499- path: ".<additional properties >",
610+ path: ".<additionalProperties >",
500611 change: TypeAdd {
501612 added: Object,
502613 },
503614 },
504615 Change {
505- path: ".<additional properties >",
616+ path: ".<additionalProperties >",
506617 change: TypeAdd {
507618 added: Array,
508619 },
509620 },
510621 Change {
511- path: ".<additional properties >",
622+ path: ".<additionalProperties >",
512623 change: TypeAdd {
513624 added: Boolean,
514625 },
515626 },
516627 Change {
517- path: ".<additional properties >",
628+ path: ".<additionalProperties >",
518629 change: TypeAdd {
519630 added: Null,
520631 },
@@ -536,43 +647,43 @@ mod tests {
536647 @r###"
537648 [
538649 Change {
539- path: ".<additional properties >",
650+ path: ".<additionalProperties >",
540651 change: TypeRemove {
541652 removed: String,
542653 },
543654 },
544655 Change {
545- path: ".<additional properties >",
656+ path: ".<additionalProperties >",
546657 change: TypeRemove {
547658 removed: Number,
548659 },
549660 },
550661 Change {
551- path: ".<additional properties >",
662+ path: ".<additionalProperties >",
552663 change: TypeRemove {
553664 removed: Integer,
554665 },
555666 },
556667 Change {
557- path: ".<additional properties >",
668+ path: ".<additionalProperties >",
558669 change: TypeRemove {
559670 removed: Object,
560671 },
561672 },
562673 Change {
563- path: ".<additional properties >",
674+ path: ".<additionalProperties >",
564675 change: TypeRemove {
565676 removed: Array,
566677 },
567678 },
568679 Change {
569- path: ".<additional properties >",
680+ path: ".<additionalProperties >",
570681 change: TypeRemove {
571682 removed: Boolean,
572683 },
573684 },
574685 Change {
575- path: ".<additional properties >",
686+ path: ".<additionalProperties >",
576687 change: TypeRemove {
577688 removed: Null,
578689 },
@@ -737,4 +848,47 @@ mod tests {
737848 "###
738849 ) ;
739850 }
851+
852+ #[ test]
853+ fn add_property_in_array_of_anyof ( ) {
854+ // rough shape of a sentry eventstream message
855+ // https://github.com/getsentry/sentry-kafka-schemas/pull/79/files
856+ let lhs = json ! { {
857+ "anyOf" : [
858+ {
859+ "type" : "array" ,
860+ "items" : [
861+ { "const" : "start_unmerge" } ,
862+ { "type" : "object" }
863+ ]
864+ }
865+ ]
866+ } } ;
867+
868+ let rhs = json ! { {
869+ "anyOf" : [
870+ {
871+ "type" : "array" ,
872+ "items" : [
873+ { "const" : "start_unmerge" } ,
874+ { "type" : "object" , "properties" : { "transaction_id" : { "type" : "string" } } }
875+ ]
876+ }
877+ ]
878+ } } ;
879+
880+ let diff = diff ( lhs, rhs) . unwrap ( ) ;
881+
882+ assert_debug_snapshot ! ( diff, @r###"
883+ [
884+ Change {
885+ path: ".<anyOf:0>.1",
886+ change: PropertyAdd {
887+ lhs_additional_properties: true,
888+ added: "transaction_id",
889+ },
890+ },
891+ ]
892+ "### ) ;
893+ }
740894}
0 commit comments