@@ -118,6 +118,47 @@ pub fn escape(input: &str) -> String {
118118 Token :: new ( input) . encoded ( ) . into ( )
119119}
120120
121+ pub fn matches ( path : & Pointer , value : & Value ) -> Vec < PointerBuf > {
122+ let Some ( idx) = path. as_str ( ) . find ( "/*" ) else {
123+ // Base case -- no stars;
124+ // If we can't resolve, there's no match to be found
125+ if path. resolve ( value) . is_ok ( ) {
126+ return vec ! [ path. to_buf( ) ] ;
127+ } else {
128+ return vec ! [ ] ;
129+ }
130+ } ;
131+
132+ // we checked the index above so unwrap is safe here
133+ let ( head, cons) = path. split_at ( idx) . unwrap ( ) ;
134+ let mut res = vec ! [ ] ;
135+
136+ // If we can't resolve the head, or it's not an array, no match found
137+ let Ok ( head_val) = head. resolve ( value) else {
138+ return vec ! [ ] ;
139+ } ;
140+ let Some ( next_array_val) = head_val. as_array ( ) else {
141+ return vec ! [ ] ;
142+ } ;
143+
144+ for ( i, v) in next_array_val. iter ( ) . enumerate ( ) {
145+ // /1 is a valid pointer so the unsafe block below is fine
146+ let idx_str = format ! ( "/{i}" ) ;
147+ let idx_path = unsafe { PointerBuf :: new_unchecked ( idx_str) } ;
148+
149+ // The cons pointer either looks like /* or /*/something, so we need to split_front
150+ // to get the array marker out, and either return the current path if there's nothing
151+ // else, or recurse and concatenate the subpath(s) to the head
152+ if let Some ( ( _, c) ) = cons. split_front ( ) {
153+ let subpaths = matches ( c, v) ;
154+ res. extend ( subpaths. iter ( ) . map ( |p| head. concat ( & idx_path. concat ( p) ) ) ) ;
155+ } else {
156+ res. push ( head. concat ( & idx_path) ) ;
157+ }
158+ }
159+ res
160+ }
161+
121162pub fn patch_ext ( obj : & mut Value , p : PatchOperation ) -> Result < ( ) , PatchError > {
122163 match p {
123164 PatchOperation :: Add ( op) => add_or_replace ( obj, & op. path , & op. value , false ) ?,
@@ -217,9 +258,16 @@ fn patch_ext_helper<'a>(
217258 PatchMode :: Skip => return Ok ( vec ! [ ] ) ,
218259 }
219260 }
261+
262+ // Head now points at what we believe is an array; if not, it's an error.
220263 let next_array_val =
221264 head. resolve_mut ( value) ?. as_array_mut ( ) . ok_or ( PatchError :: UnexpectedType ( head. as_str ( ) . into ( ) ) ) ?;
265+
266+ // Iterate over all the array values and recurse, returning all found values
222267 for v in next_array_val {
268+ // The cons pointer either looks like /* or /*/something, so we need to split_front
269+ // to get the array marker out, and either return the current value if there's nothing
270+ // else, or recurse and return all the found values
223271 if let Some ( ( _, c) ) = cons. split_front ( ) {
224272 res. extend ( patch_ext_helper ( c, v, mode) ?) ;
225273 } else {
@@ -248,6 +296,46 @@ mod tests {
248296 } )
249297 }
250298
299+ #[ rstest]
300+ fn test_matches_1 ( data : Value ) {
301+ let path = format_ptr ! ( "/foo" ) ;
302+ let m = matches ( & path, & data) ;
303+ assert_eq ! ( m, vec![ format_ptr!( "/foo" ) ] ) ;
304+ }
305+
306+ #[ rstest]
307+ fn test_matches_2 ( data : Value ) {
308+ let path = format_ptr ! ( "/foo/*/baz" ) ;
309+ let m = matches ( & path, & data) ;
310+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0/baz" ) , format_ptr!( "/foo/1/baz" ) , format_ptr!( "/foo/2/baz" ) ] ) ;
311+ }
312+
313+ #[ rstest]
314+ fn test_matches_3 ( data : Value ) {
315+ let path = format_ptr ! ( "/foo/*" ) ;
316+ let m = matches ( & path, & data) ;
317+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0" ) , format_ptr!( "/foo/1" ) , format_ptr!( "/foo/2" ) ] ) ;
318+ }
319+
320+ #[ rstest]
321+ #[ case( format_ptr!( "/foo/*/baz/fixx" ) ) ]
322+ #[ case( format_ptr!( "/foo/2/baz/fixx" ) ) ]
323+ fn test_matches_4 ( #[ case] path : PointerBuf , data : Value ) {
324+ let m = matches ( & path, & data) ;
325+ assert_eq ! ( m, vec![ format_ptr!( "/foo/2/baz/fixx" ) ] ) ;
326+ }
327+
328+ #[ rstest]
329+ #[ case( format_ptr!( "/*" ) ) ]
330+ #[ case( format_ptr!( "/food" ) ) ]
331+ #[ case( format_ptr!( "/foo/3/baz" ) ) ]
332+ #[ case( format_ptr!( "/foo/bar/baz" ) ) ]
333+ #[ case( format_ptr!( "/foo/0/baz/fixx" ) ) ]
334+ fn test_no_match ( #[ case] path : PointerBuf , data : Value ) {
335+ let m = matches ( & path, & data) ;
336+ assert_is_empty ! ( m) ;
337+ }
338+
251339 #[ rstest]
252340 fn test_patch_ext_add ( mut data : Value ) {
253341 let path = format_ptr ! ( "/foo/*/baz/buzz" ) ;
0 commit comments