@@ -45,10 +45,10 @@ pub use json_patch::{
4545} ;
4646#[ doc( no_inline) ]
4747use jsonptr:: index:: Index ;
48- use jsonptr:: Token ;
4948pub use jsonptr:: {
5049 Pointer ,
5150 PointerBuf ,
51+ Token ,
5252} ;
5353use serde_json:: {
5454 json,
@@ -63,6 +63,7 @@ pub mod prelude {
6363 copy_operation,
6464 escape,
6565 format_ptr,
66+ matches,
6667 move_operation,
6768 patch_ext,
6869 remove_operation,
@@ -79,6 +80,7 @@ pub mod prelude {
7980 RemoveOperation ,
8081 ReplaceOperation ,
8182 TestOperation ,
83+ Token ,
8284 } ;
8385}
8486
@@ -118,6 +120,49 @@ pub fn escape(input: &str) -> String {
118120 Token :: new ( input) . encoded ( ) . into ( )
119121}
120122
123+ pub fn matches < ' a > ( path : & Pointer , value : & ' a Value ) -> Vec < ( PointerBuf , & ' a Value ) > {
124+ let Some ( idx) = path. as_str ( ) . find ( "/*" ) else {
125+ // Base case -- no stars;
126+ // If we can't resolve, there's no match to be found
127+ if let Ok ( v) = path. resolve ( value) {
128+ return vec ! [ ( path. to_buf( ) , v) ] ;
129+ } else {
130+ return vec ! [ ] ;
131+ }
132+ } ;
133+
134+ // we checked the index above so unwrap is safe here
135+ let ( head, cons) = path. split_at ( idx) . unwrap ( ) ;
136+ let mut res = vec ! [ ] ;
137+
138+ // If we can't resolve the head, or it's not an array, no match found
139+ let Ok ( head_val) = head. resolve ( value) else {
140+ return vec ! [ ] ;
141+ } ;
142+ let Some ( next_array_val) = head_val. as_array ( ) else {
143+ return vec ! [ ] ;
144+ } ;
145+
146+ for ( i, v) in next_array_val. iter ( ) . enumerate ( ) {
147+ // /1 is a valid pointer so the unwrap below is fine
148+ let idx_str = format ! ( "/{i}" ) ;
149+ let idx_path = PointerBuf :: parse ( & idx_str) . unwrap ( ) ;
150+
151+ // The cons pointer either looks like /* or /*/something, so we need to split_front
152+ // to get the array marker out, and either return the current path if there's nothing
153+ // else, or recurse and concatenate the subpath(s) to the head
154+ if let Some ( ( _, c) ) = cons. split_front ( ) {
155+ let subpaths = matches ( c, v) ;
156+ res. extend ( subpaths. iter ( ) . map ( |( p, v) | ( head. concat ( & idx_path. concat ( p) ) , * v) ) ) ;
157+ } else {
158+ // I don't _think_ this case can ever be hit, based on the source it's only
159+ // true if cons is the "root" token, but I'll leave it in just in case
160+ res. push ( ( head. concat ( & idx_path) , v) ) ;
161+ }
162+ }
163+ res
164+ }
165+
121166pub fn patch_ext ( obj : & mut Value , p : PatchOperation ) -> Result < ( ) , PatchError > {
122167 match p {
123168 PatchOperation :: Add ( op) => add_or_replace ( obj, & op. path , & op. value , false ) ?,
@@ -217,9 +262,16 @@ fn patch_ext_helper<'a>(
217262 PatchMode :: Skip => return Ok ( vec ! [ ] ) ,
218263 }
219264 }
265+
266+ // Head now points at what we believe is an array; if not, it's an error.
220267 let next_array_val =
221268 head. resolve_mut ( value) ?. as_array_mut ( ) . ok_or ( PatchError :: UnexpectedType ( head. as_str ( ) . into ( ) ) ) ?;
269+
270+ // Iterate over all the array values and recurse, returning all found values
222271 for v in next_array_val {
272+ // The cons pointer either looks like /* or /*/something, so we need to split_front
273+ // to get the array marker out, and either return the current value if there's nothing
274+ // else, or recurse and return all the found values
223275 if let Some ( ( _, c) ) = cons. split_front ( ) {
224276 res. extend ( patch_ext_helper ( c, v, mode) ?) ;
225277 } else {
@@ -248,6 +300,54 @@ mod tests {
248300 } )
249301 }
250302
303+ #[ rstest]
304+ fn test_matches_1 ( data : Value ) {
305+ let path = format_ptr ! ( "/foo" ) ;
306+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
307+ assert_eq ! ( m, vec![ format_ptr!( "/foo" ) ] ) ;
308+ }
309+
310+ #[ rstest]
311+ fn test_matches_2 ( data : Value ) {
312+ let path = format_ptr ! ( "/foo/*/baz" ) ;
313+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
314+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0/baz" ) , format_ptr!( "/foo/1/baz" ) , format_ptr!( "/foo/2/baz" ) ] ) ;
315+ }
316+
317+ #[ rstest]
318+ fn test_matches_3 ( data : Value ) {
319+ let path = format_ptr ! ( "/foo/*" ) ;
320+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
321+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0" ) , format_ptr!( "/foo/1" ) , format_ptr!( "/foo/2" ) ] ) ;
322+ }
323+
324+ #[ rstest]
325+ #[ case( format_ptr!( "/foo/*/baz/fixx" ) ) ]
326+ #[ case( format_ptr!( "/foo/2/baz/fixx" ) ) ]
327+ fn test_matches_4 ( #[ case] path : PointerBuf , data : Value ) {
328+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
329+ assert_eq ! ( m, vec![ format_ptr!( "/foo/2/baz/fixx" ) ] ) ;
330+ }
331+
332+ #[ rstest]
333+ fn test_matches_root ( ) {
334+ let path = format_ptr ! ( "/*" ) ;
335+ let data = json ! ( [ "foo" , "bar" ] ) ;
336+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
337+ assert_eq ! ( m, vec![ format_ptr!( "/0" ) , format_ptr!( "/1" ) ] ) ;
338+ }
339+
340+ #[ rstest]
341+ #[ case( format_ptr!( "/*" ) ) ]
342+ #[ case( format_ptr!( "/food" ) ) ]
343+ #[ case( format_ptr!( "/foo/3/baz" ) ) ]
344+ #[ case( format_ptr!( "/foo/bar/baz" ) ) ]
345+ #[ case( format_ptr!( "/foo/0/baz/fixx" ) ) ]
346+ fn test_no_match ( #[ case] path : PointerBuf , data : Value ) {
347+ let m = matches ( & path, & data) ;
348+ assert_is_empty ! ( m) ;
349+ }
350+
251351 #[ rstest]
252352 fn test_patch_ext_add ( mut data : Value ) {
253353 let path = format_ptr ! ( "/foo/*/baz/buzz" ) ;
@@ -299,7 +399,6 @@ mod tests {
299399 assert_err ! ( res) ;
300400 }
301401
302-
303402 #[ rstest]
304403 fn test_patch_ext_remove ( mut data : Value ) {
305404 let path = format_ptr ! ( "/foo/*/baz/quzz" ) ;
0 commit comments