@@ -487,22 +487,114 @@ async fn es_compat_index_field_capabilities(
487
487
Ok ( search_response_rest)
488
488
}
489
489
490
+ fn filter_source (
491
+ value : & mut serde_json:: Value ,
492
+ _source_excludes : & Option < Vec < String > > ,
493
+ _source_includes : & Option < Vec < String > > ,
494
+ ) {
495
+ fn navigate_and_remove ( value : & mut serde_json:: Value , path : & str ) {
496
+ for ( prefix, suffix) in generate_path_variants_with_suffix ( path) {
497
+ match value {
498
+ serde_json:: Value :: Object ( ref mut map) => {
499
+ if let Some ( suffix) = suffix {
500
+ if let Some ( sub_value) = map. get_mut ( prefix) {
501
+ navigate_and_remove ( sub_value, suffix) ;
502
+ return ;
503
+ }
504
+ } else {
505
+ map. remove ( prefix) ;
506
+ }
507
+ }
508
+ _ => continue ,
509
+ }
510
+ }
511
+ }
512
+ fn navigate_and_include (
513
+ value : & mut serde_json:: Value ,
514
+ current_path : & str ,
515
+ include_paths : & Vec < String > ,
516
+ ) {
517
+ if let Some ( ref mut map) = value. as_object_mut ( ) {
518
+ map. retain ( |key, sub_value| {
519
+ let path = if current_path. is_empty ( ) {
520
+ key. to_string ( )
521
+ } else {
522
+ format ! ( "{}.{}" , current_path, key)
523
+ } ;
524
+
525
+ if include_paths. contains ( & path) {
526
+ // Exact match keep whole node
527
+ return true ;
528
+ }
529
+ // check if the path is sub path of any allowed path
530
+ for allowed_path in include_paths {
531
+ if allowed_path. starts_with ( path. as_str ( ) ) {
532
+ navigate_and_include ( sub_value, & path, include_paths) ;
533
+ return true ;
534
+ }
535
+ }
536
+ false
537
+ } ) ;
538
+ }
539
+ }
540
+
541
+ // Remove fields that are not included
542
+ if let Some ( includes) = _source_includes {
543
+ navigate_and_include ( value, "" , includes) ;
544
+ }
545
+
546
+ // Exclude fields
547
+ if let Some ( excludes) = _source_excludes {
548
+ for exclude in excludes {
549
+ navigate_and_remove ( value, exclude) ;
550
+ }
551
+ }
552
+ }
553
+
554
+ /// "app.id.name" -> [("app", Some("id.name")), ("app.id", Some("name")), ("app.id.name", None)]
555
+ fn generate_path_variants_with_suffix ( input : & str ) -> Vec < ( & str , Option < & str > ) > {
556
+ let mut variants = Vec :: new ( ) ;
557
+
558
+ // Iterate over each character in the input.
559
+ for ( idx, ch) in input. char_indices ( ) {
560
+ if ch == '.' {
561
+ // If a dot is found, create a variant using the current slice and the remainder of the
562
+ // string.
563
+ let prefix = & input[ 0 ..idx] ;
564
+ let suffix = if idx + 1 < input. len ( ) {
565
+ Some ( & input[ idx + 1 ..] )
566
+ } else {
567
+ None
568
+ } ;
569
+ variants. push ( ( prefix, suffix) ) ;
570
+ }
571
+ }
572
+
573
+ // Add the final variant, which includes the entire string as the prefix and None as the suffix.
574
+ variants. push ( ( & input[ 0 ..] , None ) ) ;
575
+
576
+ variants
577
+ }
578
+
490
579
fn convert_hit (
491
580
hit : quickwit_proto:: search:: Hit ,
492
581
append_shard_doc : bool ,
493
582
_source_excludes : & Option < Vec < String > > ,
494
583
_source_includes : & Option < Vec < String > > ,
495
584
) -> ElasticHit {
496
- let mut fields: BTreeMap < String , serde_json:: Value > =
497
- serde_json:: from_str ( & hit. json ) . unwrap_or_default ( ) ;
498
- if let Some ( _source_includes) = _source_includes {
499
- fields. retain ( |key, _| _source_includes. contains ( key) ) ;
500
- }
501
- if let Some ( _source_excludes) = _source_excludes {
502
- for exclude in _source_excludes {
503
- fields. remove ( exclude) ;
585
+ let mut json: serde_json:: Value = serde_json:: from_str ( & hit. json ) . unwrap_or ( json ! ( { } ) ) ;
586
+ filter_source ( & mut json, _source_excludes, _source_includes) ;
587
+ let source =
588
+ Source :: from_string ( serde_json:: to_string ( & json) . unwrap_or_else ( |_| "{}" . to_string ( ) ) )
589
+ . unwrap_or_else ( |_| Source :: from_string ( "{}" . to_string ( ) ) . unwrap ( ) ) ;
590
+
591
+ let mut fields: BTreeMap < String , serde_json:: Value > = Default :: default ( ) ;
592
+ if let serde_json:: Value :: Object ( map) = json {
593
+ for ( key, val) in map {
594
+ fields. insert ( key, val) ;
504
595
}
505
596
}
597
+
506
598
let mut sort = Vec :: new ( ) ;
507
599
if let Some ( partial_hit) = hit. partial_hit {
508
600
if let Some ( sort_value) = partial_hit. sort_value {
@@ -517,9 +609,6 @@ fn convert_hit(
517
609
) ) ;
518
610
}
519
611
}
520
- let source =
521
- Source :: from_string ( serde_json:: to_string ( & fields) . unwrap_or_else ( |_| "{}" . to_string ( ) ) )
522
- . unwrap_or_else ( |_| Source :: from_string ( "{}" . to_string ( ) ) . unwrap ( ) ) ;
523
612
524
613
ElasticHit {
525
614
fields,
@@ -745,7 +834,7 @@ pub(crate) fn str_lines(body: &str) -> impl Iterator<Item = &str> {
745
834
mod tests {
746
835
use hyper:: StatusCode ;
747
836
748
- use super :: partial_hit_from_search_after_param;
837
+ use super :: { partial_hit_from_search_after_param, * } ;
749
838
750
839
#[ test]
751
840
fn test_partial_hit_from_search_after_param_invalid_length ( ) {
@@ -791,4 +880,136 @@ mod tests {
791
880
u32}`"
792
881
) ;
793
882
}
883
+
884
+ #[ test]
885
+ fn test_single_element ( ) {
886
+ let input = "app" ;
887
+ let expected = vec ! [ ( "app" , None ) ] ;
888
+ assert_eq ! ( generate_path_variants_with_suffix( input) , expected) ;
889
+ }
890
+
891
+ #[ test]
892
+ fn test_two_elements ( ) {
893
+ let input = "app.id" ;
894
+ let expected = vec ! [ ( "app" , Some ( "id" ) ) , ( "app.id" , None ) ] ;
895
+ assert_eq ! ( generate_path_variants_with_suffix( input) , expected) ;
896
+ }
897
+
898
+ #[ test]
899
+ fn test_multiple_elements ( ) {
900
+ let input = "app.id.name" ;
901
+ let expected = vec ! [
902
+ ( "app" , Some ( "id.name" ) ) ,
903
+ ( "app.id" , Some ( "name" ) ) ,
904
+ ( "app.id.name" , None ) ,
905
+ ] ;
906
+ assert_eq ! ( generate_path_variants_with_suffix( input) , expected) ;
907
+ }
908
+
909
+ #[ test]
910
+ fn test_include_fields1 ( ) {
911
+ let mut fields = json ! ( {
912
+ "app" : { "id" : 123 , "name" : "Blub" } ,
913
+ "user" : { "id" : 456 , "name" : "Fred" }
914
+ } ) ;
915
+
916
+ let includes = Some ( vec ! [ "app.id" . to_string( ) ] ) ;
917
+ filter_source ( & mut fields, & None , & includes) ;
918
+
919
+ let expected = json ! ( {
920
+ "app" : { "id" : 123 }
921
+ } ) ;
922
+
923
+ assert_eq ! ( fields, expected) ;
924
+ }
925
+ #[ test]
926
+ fn test_include_fields2 ( ) {
927
+ let mut fields = json ! ( {
928
+ "app" : { "id" : 123 , "name" : "Blub" } ,
929
+ "app.id" : { "id" : 123 , "name" : "Blub" } ,
930
+ "user" : { "id" : 456 , "name" : "Fred" }
931
+ } ) ;
932
+
933
+ let includes = Some ( vec ! [ "app" . to_string( ) , "app.id" . to_string( ) ] ) ;
934
+ filter_source ( & mut fields, & None , & includes) ;
935
+
936
+ let expected = json ! ( {
937
+ "app" : { "id" : 123 , "name" : "Blub" } ,
938
+ "app.id" : { "id" : 123 , "name" : "Blub" } ,
939
+ } ) ;
940
+
941
+ assert_eq ! ( fields, expected) ;
942
+ }
943
+
944
+ #[ test]
945
+ fn test_exclude_fields ( ) {
946
+ let mut fields = json ! ( {
947
+ "app" : {
948
+ "id" : 123 ,
949
+ "name" : "Blub"
950
+ } ,
951
+ "user" : {
952
+ "id" : 456 ,
953
+ "name" : "Fred"
954
+ }
955
+ } ) ;
956
+
957
+ let excludes = Some ( vec ! [ "app.name" . to_string( ) , "user.id" . to_string( ) ] ) ;
958
+ filter_source ( & mut fields, & excludes, & None ) ;
959
+
960
+ let expected = json ! ( {
961
+ "app" : {
962
+ "id" : 123
963
+ } ,
964
+ "user" : {
965
+ "name" : "Fred"
966
+ }
967
+ } ) ;
968
+
969
+ assert_eq ! ( fields, expected) ;
970
+ }
971
+
972
+ #[ test]
973
+ fn test_include_and_exclude_fields ( ) {
974
+ let mut fields = json ! ( {
975
+ "app" : { "id" : 123 , "name" : "Blub" , "version" : "1.0" } ,
976
+ "user" : { "id" : 456 , "name" : "Fred" , "email" : "[email protected] " }
977
+ } ) ;
978
+
979
+ let includes = Some ( vec ! [
980
+ "app" . to_string( ) ,
981
+ "user.name" . to_string( ) ,
982
+ "user.email" . to_string( ) ,
983
+ ] ) ;
984
+ let excludes = Some ( vec ! [ "app.version" . to_string( ) , "user.email" . to_string( ) ] ) ;
985
+ filter_source ( & mut fields, & excludes, & includes) ;
986
+
987
+ let expected = json ! ( {
988
+ "app" : { "id" : 123 , "name" : "Blub" } ,
989
+ "user" : { "name" : "Fred" }
990
+ } ) ;
991
+
992
+ assert_eq ! ( fields, expected) ;
993
+ }
994
+
995
+ #[ test]
996
+ fn test_no_includes_or_excludes ( ) {
997
+ let mut fields = json ! ( {
998
+ "app" : {
999
+ "id" : 123 ,
1000
+ "name" : "Blub"
1001
+ }
1002
+ } ) ;
1003
+
1004
+ filter_source ( & mut fields, & None , & None ) ;
1005
+
1006
+ let expected = json ! ( {
1007
+ "app" : {
1008
+ "id" : 123 ,
1009
+ "name" : "Blub"
1010
+ }
1011
+ } ) ;
1012
+
1013
+ assert_eq ! ( fields, expected) ;
1014
+ }
794
1015
}
0 commit comments