@@ -95,6 +95,9 @@ impl JsonPlanBuilder {
9595 root_node : & mut ExecutionPlanNode
9696 ) {
9797 trace ! ( %json, %path, ">>> process_body_node" ) ;
98+
99+ let rewritten_path = remove_marker ( & path) ;
100+
98101 match & json {
99102 Value :: Array ( items) => {
100103 if context. matcher_is_defined ( path) {
@@ -107,7 +110,11 @@ impl JsonPlanBuilder {
107110
108111 if let Some ( template) = items. first ( ) {
109112 let mut for_each_node = ExecutionPlanNode :: action ( "for-each" ) ;
110- let item_path = path. join ( "[*]" ) ;
113+ let marker = format ! ( "{}*" , path. last_field( ) . unwrap_or_default( ) ) ;
114+ for_each_node. add ( ExecutionPlanNode :: value_node ( marker. as_str ( ) ) ) ;
115+ let item_path = path. parent ( )
116+ . unwrap_or_else ( || path. clone ( ) )
117+ . join_field ( marker) ;
111118 for_each_node. add ( ExecutionPlanNode :: resolve_current_value ( path) ) ;
112119 let mut item_node = ExecutionPlanNode :: container ( & item_path) ;
113120 match template {
@@ -120,7 +127,11 @@ impl JsonPlanBuilder {
120127 ExecutionPlanNode :: action ( "check:exists" )
121128 . add ( ExecutionPlanNode :: resolve_current_value ( & item_path) )
122129 ) ;
123- if context. matcher_is_defined ( & item_path) {
130+
131+ let matchers = context. select_best_matcher ( & item_path)
132+ . and_rules ( & context. select_best_matcher ( & rewritten_path) )
133+ . remove_duplicates ( ) ;
134+ if !matchers. is_empty ( ) {
124135 let matchers = context. select_best_matcher ( & item_path) ;
125136 presence_check. add ( ExecutionPlanNode :: annotation ( format ! ( "[*] {}" , matchers. generate_description( false ) ) ) ) ;
126137 presence_check. add ( build_matching_rule_node ( & ExecutionPlanNode :: value_node ( template) ,
@@ -166,7 +177,10 @@ impl JsonPlanBuilder {
166177 ExecutionPlanNode :: action ( "check:exists" )
167178 . add ( ExecutionPlanNode :: resolve_current_value ( & item_path) )
168179 ) ;
169- if context. matcher_is_defined ( & item_path) {
180+ let matchers = context. select_best_matcher ( & item_path)
181+ . and_rules ( & context. select_best_matcher ( & rewritten_path) )
182+ . remove_duplicates ( ) ;
183+ if !matchers. is_empty ( ) {
170184 let matchers = context. select_best_matcher ( & item_path) ;
171185 presence_check. add ( ExecutionPlanNode :: annotation ( format ! ( "[{}] {}" , index, matchers. generate_description( false ) ) ) ) ;
172186 presence_check. add ( build_matching_rule_node ( & ExecutionPlanNode :: value_node ( item) ,
@@ -179,6 +193,11 @@ impl JsonPlanBuilder {
179193 . add ( ExecutionPlanNode :: value_node ( NodeValue :: NULL ) )
180194 ) ;
181195 }
196+ presence_check. add (
197+ ExecutionPlanNode :: action ( "error" )
198+ . add ( ExecutionPlanNode :: value_node ( format ! ( "Expected a value for '{}' but it was missing" ,
199+ item_path. as_json_pointer( ) . unwrap( ) ) ) )
200+ ) ;
182201 item_node. add ( presence_check) ;
183202 root_node. add ( item_node) ;
184203 }
@@ -187,7 +206,9 @@ impl JsonPlanBuilder {
187206 }
188207 }
189208 Value :: Object ( entries) => {
190- let rules = context. select_best_matcher ( path) ;
209+ let rules = context. select_best_matcher ( & path)
210+ . and_rules ( & context. select_best_matcher ( & rewritten_path) )
211+ . remove_duplicates ( ) ;
191212 if !rules. is_empty ( ) && should_apply_to_map_entries ( & rules) {
192213 root_node. add ( ExecutionPlanNode :: annotation ( rules. generate_description ( true ) ) ) ;
193214 root_node. add ( build_matching_rule_node ( & ExecutionPlanNode :: value_node ( json. clone ( ) ) ,
@@ -230,8 +251,10 @@ impl JsonPlanBuilder {
230251 }
231252 }
232253 _ => {
233- if context. matcher_is_defined ( path) {
234- let matchers = context. select_best_matcher ( path) ;
254+ let matchers = context. select_best_matcher ( & path)
255+ . and_rules ( & context. select_best_matcher ( & rewritten_path) )
256+ . remove_duplicates ( ) ;
257+ if !matchers. is_empty ( ) {
235258 root_node. add ( ExecutionPlanNode :: annotation ( format ! ( "{} {}" , path. last_field( ) . unwrap_or_default( ) , matchers. generate_description( false ) ) ) ) ;
236259 root_node. add ( build_matching_rule_node ( & ExecutionPlanNode :: value_node ( json) ,
237260 & ExecutionPlanNode :: resolve_current_value ( path) , & matchers, false ) ) ;
@@ -305,7 +328,7 @@ impl XMLPlanBuilder {
305328 node : & mut ExecutionPlanNode
306329 ) {
307330 let name = name ( element) ;
308- let element_path = if path. ends_with ( format ! ( "{}[* ]" , name) . as_str ( ) ) {
331+ let element_path = if path. ends_with ( format ! ( "['{}*' ]" , name) . as_str ( ) ) {
309332 path. clone ( )
310333 } else if let Some ( index) = index {
311334 path. join_field ( & name) . join_index ( index)
@@ -397,13 +420,14 @@ impl XMLPlanBuilder {
397420 . filter ( |m| m. is_length_type_matcher ( ) ) ;
398421 if !rules. is_empty ( ) {
399422 parent_node. add ( ExecutionPlanNode :: annotation ( format ! ( "{} {}" ,
400- path . last_field( ) . unwrap_or_default( ) ,
423+ p . last_field( ) . unwrap_or_default( ) ,
401424 rules. generate_description( true ) ) ) ) ;
402425 parent_node. add ( build_matching_rule_node ( & ExecutionPlanNode :: value_node ( elements[ 0 ] ) ,
403- & ExecutionPlanNode :: resolve_current_value ( path ) , & rules, true ) ) ;
426+ & ExecutionPlanNode :: resolve_current_value ( & p ) , & rules, true ) ) ;
404427 }
405428
406429 let mut for_each_node = ExecutionPlanNode :: action ( "for-each" ) ;
430+ for_each_node. add ( ExecutionPlanNode :: value_node ( format ! ( "{}*" , child_name) ) ) ;
407431 for_each_node. add ( ExecutionPlanNode :: resolve_current_value ( & p) ) ;
408432 let item_path = p. join ( "[*]" ) ;
409433
@@ -423,12 +447,11 @@ impl XMLPlanBuilder {
423447 ) {
424448 let text_nodes = text_nodes ( element) ;
425449 let p = path. join ( "#text" ) ;
450+ let no_markers = remove_marker ( & p) ;
426451 let no_indices = drop_indices ( & p) ;
427- let matchers = context. select_best_matcher ( & p )
452+ let matchers = context. select_best_matcher_from ( & no_markers , & no_indices )
428453 . filter ( |matcher| !matcher. is_type_matcher ( ) )
429- . and_rules ( & context. select_best_matcher ( & no_indices)
430- . filter ( |matcher| !matcher. is_type_matcher ( ) )
431- ) . remove_duplicates ( ) ;
454+ . remove_duplicates ( ) ;
432455 if !matchers. is_empty ( ) {
433456 node. add ( ExecutionPlanNode :: annotation ( format ! ( "{} {}" , p. last_field( ) . unwrap_or_default( ) ,
434457 matchers. generate_description( false ) ) ) ) ;
@@ -538,7 +561,33 @@ fn drop_indices(path: &DocPath) -> DocPath {
538561 PathToken :: Index ( _) | PathToken :: StarIndex => false ,
539562 _ => true
540563 } )
541- . cloned ( ) )
564+ . map ( |token| {
565+ if let PathToken :: Field ( name) = token {
566+ if name. ends_with ( '*' ) {
567+ PathToken :: Field ( name. trim_end_matches ( '*' ) . to_string ( ) )
568+ } else {
569+ token. clone ( )
570+ }
571+ } else {
572+ token. clone ( )
573+ }
574+ } ) )
575+ }
576+
577+ fn remove_marker ( path : & DocPath ) -> DocPath {
578+ DocPath :: from_tokens ( path. tokens ( )
579+ . iter ( )
580+ . flat_map ( |token| {
581+ if let PathToken :: Field ( name) = token {
582+ if name. ends_with ( '*' ) {
583+ vec ! [ PathToken :: Field ( name. trim_end_matches( '*' ) . to_string( ) ) , PathToken :: Index ( 0 ) ]
584+ } else {
585+ vec ! [ token. clone( ) ]
586+ }
587+ } else {
588+ vec ! [ token. clone( ) ]
589+ }
590+ } ) )
542591}
543592
544593#[ cfg( feature = "xml" ) ]
@@ -739,6 +788,9 @@ mod tests {
739788 json:100,
740789 ~>$[0],
741790 NULL
791+ ),
792+ %error (
793+ 'Expected a value for \'/0\' but it was missing'
742794 )
743795 )
744796 ),
@@ -751,6 +803,9 @@ mod tests {
751803 json:200,
752804 ~>$[1],
753805 NULL
806+ ),
807+ %error (
808+ 'Expected a value for \'/1\' but it was missing'
754809 )
755810 )
756811 ),
@@ -763,6 +818,9 @@ mod tests {
763818 json:300,
764819 ~>$[2],
765820 NULL
821+ ),
822+ %error (
823+ 'Expected a value for \'/2\' but it was missing'
766824 )
767825 )
768826 )
@@ -938,22 +996,23 @@ mod tests {
938996 json:{"min":2}
939997 ),
940998 %for-each (
999+ 'item*',
9411000 ~>$.item,
942- :$.item[* ] (
1001+ :$['item*' ] (
9431002 %json:expect:entries (
9441003 'OBJECT',
9451004 ['a'],
946- ~>$.item[* ]
1005+ ~>$['item*' ]
9471006 ),
9481007 %expect:only-entries (
9491008 ['a'],
950- ~>$.item[* ]
1009+ ~>$['item*' ]
9511010 ),
952- :$.item[* ].a (
1011+ :$['item*' ].a (
9531012 #{'a must match by type'},
9541013 %match:type (
9551014 json:100,
956- ~>$.item[* ].a,
1015+ ~>$['item*' ].a,
9571016 json:{}
9581017 )
9591018 )
0 commit comments