@@ -45,10 +45,10 @@ pub use json_patch::{
45
45
} ;
46
46
#[ doc( no_inline) ]
47
47
use jsonptr:: index:: Index ;
48
- use jsonptr:: Token ;
49
48
pub use jsonptr:: {
50
49
Pointer ,
51
50
PointerBuf ,
51
+ Token ,
52
52
} ;
53
53
use serde_json:: {
54
54
json,
@@ -63,6 +63,7 @@ pub mod prelude {
63
63
copy_operation,
64
64
escape,
65
65
format_ptr,
66
+ matches,
66
67
move_operation,
67
68
patch_ext,
68
69
remove_operation,
@@ -79,6 +80,7 @@ pub mod prelude {
79
80
RemoveOperation ,
80
81
ReplaceOperation ,
81
82
TestOperation ,
83
+ Token ,
82
84
} ;
83
85
}
84
86
@@ -118,6 +120,48 @@ pub fn escape(input: &str) -> String {
118
120
Token :: new ( input) . encoded ( ) . into ( )
119
121
}
120
122
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
+ println ! ( "{cons}" ) ;
147
+ for ( i, v) in next_array_val. iter ( ) . enumerate ( ) {
148
+ // /1 is a valid pointer so the unwrap below is fine
149
+ let idx_str = format ! ( "/{i}" ) ;
150
+ let idx_path = PointerBuf :: parse ( & idx_str) . unwrap ( ) ;
151
+
152
+ // The cons pointer either looks like /* or /*/something, so we need to split_front
153
+ // to get the array marker out, and either return the current path if there's nothing
154
+ // else, or recurse and concatenate the subpath(s) to the head
155
+ if let Some ( ( _, c) ) = cons. split_front ( ) {
156
+ let subpaths = matches ( c, v) ;
157
+ res. extend ( subpaths. iter ( ) . map ( |( p, v) | ( head. concat ( & idx_path. concat ( p) ) , * v) ) ) ;
158
+ } else {
159
+ panic ! ( "cons can't be root" ) ;
160
+ }
161
+ }
162
+ res
163
+ }
164
+
121
165
pub fn patch_ext ( obj : & mut Value , p : PatchOperation ) -> Result < ( ) , PatchError > {
122
166
match p {
123
167
PatchOperation :: Add ( op) => add_or_replace ( obj, & op. path , & op. value , false ) ?,
@@ -217,13 +261,20 @@ fn patch_ext_helper<'a>(
217
261
PatchMode :: Skip => return Ok ( vec ! [ ] ) ,
218
262
}
219
263
}
264
+
265
+ // Head now points at what we believe is an array; if not, it's an error.
220
266
let next_array_val =
221
267
head. resolve_mut ( value) ?. as_array_mut ( ) . ok_or ( PatchError :: UnexpectedType ( head. as_str ( ) . into ( ) ) ) ?;
268
+
269
+ // Iterate over all the array values and recurse, returning all found values
222
270
for v in next_array_val {
271
+ // The cons pointer either looks like /* or /*/something, so we need to split_front
272
+ // to get the array marker out, and either return the current value if there's nothing
273
+ // else, or recurse and return all the found values
223
274
if let Some ( ( _, c) ) = cons. split_front ( ) {
224
275
res. extend ( patch_ext_helper ( c, v, mode) ?) ;
225
276
} else {
226
- res . push ( v ) ;
277
+ panic ! ( "cons can't be root" ) ;
227
278
}
228
279
}
229
280
Ok ( res)
@@ -248,6 +299,54 @@ mod tests {
248
299
} )
249
300
}
250
301
302
+ #[ rstest]
303
+ fn test_matches_1 ( data : Value ) {
304
+ let path = format_ptr ! ( "/foo" ) ;
305
+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
306
+ assert_eq ! ( m, vec![ format_ptr!( "/foo" ) ] ) ;
307
+ }
308
+
309
+ #[ rstest]
310
+ fn test_matches_2 ( data : Value ) {
311
+ let path = format_ptr ! ( "/foo/*/baz" ) ;
312
+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
313
+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0/baz" ) , format_ptr!( "/foo/1/baz" ) , format_ptr!( "/foo/2/baz" ) ] ) ;
314
+ }
315
+
316
+ #[ rstest]
317
+ fn test_matches_3 ( data : Value ) {
318
+ let path = format_ptr ! ( "/foo/*" ) ;
319
+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
320
+ assert_eq ! ( m, vec![ format_ptr!( "/foo/0" ) , format_ptr!( "/foo/1" ) , format_ptr!( "/foo/2" ) ] ) ;
321
+ }
322
+
323
+ #[ rstest]
324
+ #[ case( format_ptr!( "/foo/*/baz/fixx" ) ) ]
325
+ #[ case( format_ptr!( "/foo/2/baz/fixx" ) ) ]
326
+ fn test_matches_4 ( #[ case] path : PointerBuf , data : Value ) {
327
+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
328
+ assert_eq ! ( m, vec![ format_ptr!( "/foo/2/baz/fixx" ) ] ) ;
329
+ }
330
+
331
+ #[ rstest]
332
+ fn test_matches_root ( ) {
333
+ let path = format_ptr ! ( "/*" ) ;
334
+ let data = json ! ( [ "foo" , "bar" ] ) ;
335
+ let m: Vec < _ > = matches ( & path, & data) . iter ( ) . map ( |( p, _) | p. clone ( ) ) . collect ( ) ;
336
+ assert_eq ! ( m, vec![ format_ptr!( "/0" ) , format_ptr!( "/1" ) ] ) ;
337
+ }
338
+
339
+ #[ rstest]
340
+ #[ case( format_ptr!( "/*" ) ) ]
341
+ #[ case( format_ptr!( "/food" ) ) ]
342
+ #[ case( format_ptr!( "/foo/3/baz" ) ) ]
343
+ #[ case( format_ptr!( "/foo/bar/baz" ) ) ]
344
+ #[ case( format_ptr!( "/foo/0/baz/fixx" ) ) ]
345
+ fn test_no_match ( #[ case] path : PointerBuf , data : Value ) {
346
+ let m = matches ( & path, & data) ;
347
+ assert_is_empty ! ( m) ;
348
+ }
349
+
251
350
#[ rstest]
252
351
fn test_patch_ext_add ( mut data : Value ) {
253
352
let path = format_ptr ! ( "/foo/*/baz/buzz" ) ;
@@ -299,7 +398,6 @@ mod tests {
299
398
assert_err ! ( res) ;
300
399
}
301
400
302
-
303
401
#[ rstest]
304
402
fn test_patch_ext_remove ( mut data : Value ) {
305
403
let path = format_ptr ! ( "/foo/*/baz/quzz" ) ;
0 commit comments