@@ -7,7 +7,7 @@ use schemars::JsonSchema;
7
7
use serde:: { Deserialize , Serialize } ;
8
8
use serde_json:: Value ;
9
9
use std:: collections:: HashMap ;
10
- use tracing:: debug;
10
+ use tracing:: { debug, info } ;
11
11
12
12
use super :: { command_resource, dscerror, invoke_result:: { ExportResult , GetResult , ResolveResult , ResourceTestResponse , SetResult , TestResult , ValidateResult } , resource_manifest:: import_manifest} ;
13
13
@@ -338,6 +338,16 @@ pub fn get_well_known_properties() -> HashMap<String, Value> {
338
338
}
339
339
340
340
#[ must_use]
341
+ /// Performs a comparison of two JSON Values if the expected is a strict subset of the actual
342
+ ///
343
+ /// # Arguments
344
+ ///
345
+ /// * `expected` - The expected value
346
+ /// * `actual` - The actual value
347
+ ///
348
+ /// # Returns
349
+ ///
350
+ /// An array of top level properties that differ, if any
341
351
pub fn get_diff ( expected : & Value , actual : & Value ) -> Vec < String > {
342
352
let mut diff_properties: Vec < String > = Vec :: new ( ) ;
343
353
if expected. is_null ( ) {
@@ -363,28 +373,176 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
363
373
if value. is_object ( ) {
364
374
let sub_diff = get_diff ( value, & actual[ key] ) ;
365
375
if !sub_diff. is_empty ( ) {
376
+ debug ! ( "diff: sub diff for {key}" ) ;
366
377
diff_properties. push ( key. to_string ( ) ) ;
367
378
}
368
379
}
369
380
else {
370
- match actual. as_object ( ) {
371
- Some ( actual_object) => {
372
- if actual_object. contains_key ( key) {
373
- if value != & actual[ key] {
381
+ // skip `$schema` key as that is provided as input, but not output typically
382
+ if key == "$schema" {
383
+ continue ;
384
+ }
385
+
386
+ if let Some ( actual_object) = actual. as_object ( ) {
387
+ if actual_object. contains_key ( key) {
388
+ if let Some ( value_array) = value. as_array ( ) {
389
+ if let Some ( actual_array) = actual[ key] . as_array ( ) {
390
+ if !is_same_array ( value_array, actual_array) {
391
+ info ! ( "diff: arrays differ for {key}" ) ;
392
+ diff_properties. push ( key. to_string ( ) ) ;
393
+ }
394
+ } else {
395
+ info ! ( "diff: {} is not an array" , actual[ key] ) ;
374
396
diff_properties. push ( key. to_string ( ) ) ;
375
397
}
376
- }
377
- else {
398
+ } else if value != & actual[ key] {
378
399
diff_properties. push ( key. to_string ( ) ) ;
379
400
}
380
- } ,
381
- None => {
401
+ } else {
402
+ info ! ( "diff: {key} missing" ) ;
382
403
diff_properties. push ( key. to_string ( ) ) ;
383
- } ,
404
+ }
405
+ } else {
406
+ info ! ( "diff: {key} not object" ) ;
407
+ diff_properties. push ( key. to_string ( ) ) ;
384
408
}
385
409
}
386
410
}
387
411
}
388
412
389
413
diff_properties
390
414
}
415
+
416
+ /// Compares two arrays independent of order
417
+ fn is_same_array ( expected : & Vec < Value > , actual : & Vec < Value > ) -> bool {
418
+ if expected. len ( ) != actual. len ( ) {
419
+ info ! ( "diff: arrays are different lengths" ) ;
420
+ return false ;
421
+ }
422
+
423
+ for item in expected {
424
+ if !array_contains ( actual, item) {
425
+ info ! ( "diff: actual array missing expected element" ) ;
426
+ return false ;
427
+ }
428
+ }
429
+
430
+ true
431
+ }
432
+
433
+ fn array_contains ( array : & Vec < Value > , find : & Value ) -> bool {
434
+ for item in array {
435
+ if find. is_boolean ( ) && item. is_boolean ( ) && find. as_bool ( ) . unwrap ( ) == item. as_bool ( ) . unwrap ( ) {
436
+ return true ;
437
+ }
438
+
439
+ if find. is_f64 ( ) && item. is_f64 ( ) && ( find. as_f64 ( ) . unwrap ( ) - item. as_f64 ( ) . unwrap ( ) ) . abs ( ) < 0.1 {
440
+ return true ;
441
+ }
442
+
443
+ if find. is_i64 ( ) && item. is_i64 ( ) && find. as_i64 ( ) . unwrap ( ) == item. as_i64 ( ) . unwrap ( ) {
444
+ return true ;
445
+ }
446
+
447
+ if find. is_null ( ) && item. is_null ( ) {
448
+ return true ;
449
+ }
450
+
451
+ if find. is_number ( ) && item. is_number ( ) && find. as_number ( ) . unwrap ( ) == item. as_number ( ) . unwrap ( ) {
452
+ return true ;
453
+ }
454
+
455
+ if find. is_string ( ) && item. is_string ( ) && find. as_str ( ) . unwrap ( ) == item. as_str ( ) . unwrap ( ) {
456
+ return true ;
457
+ }
458
+
459
+ if find. is_u64 ( ) && item. is_u64 ( ) && find. as_u64 ( ) . unwrap ( ) == item. as_u64 ( ) . unwrap ( ) {
460
+ return true ;
461
+ }
462
+
463
+ if find. is_object ( ) && item. is_object ( ) {
464
+ let obj_diff = get_diff ( find, item) ;
465
+ if obj_diff. is_empty ( ) {
466
+ return true ;
467
+ }
468
+ }
469
+
470
+ if find. is_array ( ) && item. is_array ( ) && is_same_array ( item. as_array ( ) . unwrap ( ) , find. as_array ( ) . unwrap ( ) ) {
471
+ return true ;
472
+ }
473
+ }
474
+
475
+ false
476
+ }
477
+
478
+ #[ test]
479
+ fn same_array ( ) {
480
+ use serde_json:: json;
481
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( null) ] ;
482
+ let array_two = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( null) ] ;
483
+ assert_eq ! ( is_same_array( & array_one, & array_two) , true ) ;
484
+ }
485
+
486
+ #[ test]
487
+ fn same_array_out_of_order ( ) {
488
+ use serde_json:: json;
489
+ let array_one = vec ! [ json!( "a" ) , json!( true ) , json!( { "a" : "b" } ) ] ;
490
+ let array_two = vec ! [ json!( { "a" : "b" } ) , json!( "a" ) , json!( true ) ] ;
491
+ assert_eq ! ( is_same_array( & array_one, & array_two) , true ) ;
492
+ }
493
+
494
+ #[ test]
495
+ fn different_array ( ) {
496
+ use serde_json:: json;
497
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) ] ;
498
+ let array_two = vec ! [ json!( { "a" : "b" } ) , json!( "a" ) , json!( 2 ) ] ;
499
+ assert_eq ! ( is_same_array( & array_one, & array_two) , false ) ;
500
+ }
501
+
502
+ #[ test]
503
+ fn different_array_sizes ( ) {
504
+ use serde_json:: json;
505
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) ] ;
506
+ let array_two = vec ! [ json!( { "a" : "b" } ) , json!( "a" ) ] ;
507
+ assert_eq ! ( is_same_array( & array_one, & array_two) , false ) ;
508
+ }
509
+
510
+ #[ test]
511
+ fn array_with_multiple_objects_with_actual_superset ( ) {
512
+ use serde_json:: json;
513
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( { "c" : "d" } ) ] ;
514
+ let array_two = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "c" : "d" , "a" : "b" } ) , json!( { "c" : "d" } ) ] ;
515
+ assert_eq ! ( is_same_array( & array_one, & array_two) , true ) ;
516
+ }
517
+
518
+ #[ test]
519
+ fn array_with_multiple_objects_with_expected_superset ( ) {
520
+ use serde_json:: json;
521
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" , "c" : "d" } ) , json!( { "c" : "d" } ) ] ;
522
+ let array_two = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( { "c" : "d" } ) ] ;
523
+ assert_eq ! ( is_same_array( & array_one, & array_two) , false ) ;
524
+ }
525
+
526
+ #[ test]
527
+ fn array_with_duplicates_out_of_order ( ) {
528
+ use serde_json:: json;
529
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( { "a" : "b" } ) ] ;
530
+ let array_two = vec ! [ json!( { "a" : "b" } ) , json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) ] ;
531
+ assert_eq ! ( is_same_array( & array_one, & array_two) , true ) ;
532
+ }
533
+
534
+ #[ test]
535
+ fn same_array_with_nested_array ( ) {
536
+ use serde_json:: json;
537
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( vec![ json!( "a" ) , json!( 1 ) ] ) ] ;
538
+ let array_two = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( vec![ json!( "a" ) , json!( 1 ) ] ) ] ;
539
+ assert_eq ! ( is_same_array( & array_one, & array_two) , true ) ;
540
+ }
541
+
542
+ #[ test]
543
+ fn different_array_with_nested_array ( ) {
544
+ use serde_json:: json;
545
+ let array_one = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( vec![ json!( "a" ) , json!( 1 ) ] ) ] ;
546
+ let array_two = vec ! [ json!( "a" ) , json!( 1 ) , json!( { "a" : "b" } ) , json!( vec![ json!( "a" ) , json!( 2 ) ] ) ] ;
547
+ assert_eq ! ( is_same_array( & array_one, & array_two) , false ) ;
548
+ }
0 commit comments