Skip to content

Commit 491c203

Browse files
committed
add matches function
1 parent 2bb6366 commit 491c203

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib.rs

+101-3
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ pub use json_patch::{
4545
};
4646
#[doc(no_inline)]
4747
use jsonptr::index::Index;
48-
use jsonptr::Token;
4948
pub use jsonptr::{
5049
Pointer,
5150
PointerBuf,
51+
Token,
5252
};
5353
use 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,48 @@ 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+
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+
121165
pub fn patch_ext(obj: &mut Value, p: PatchOperation) -> Result<(), PatchError> {
122166
match p {
123167
PatchOperation::Add(op) => add_or_replace(obj, &op.path, &op.value, false)?,
@@ -217,13 +261,20 @@ fn patch_ext_helper<'a>(
217261
PatchMode::Skip => return Ok(vec![]),
218262
}
219263
}
264+
265+
// Head now points at what we believe is an array; if not, it's an error.
220266
let next_array_val =
221267
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
222270
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
223274
if let Some((_, c)) = cons.split_front() {
224275
res.extend(patch_ext_helper(c, v, mode)?);
225276
} else {
226-
res.push(v);
277+
panic!("cons can't be root");
227278
}
228279
}
229280
Ok(res)
@@ -248,6 +299,54 @@ mod tests {
248299
})
249300
}
250301

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+
251350
#[rstest]
252351
fn test_patch_ext_add(mut data: Value) {
253352
let path = format_ptr!("/foo/*/baz/buzz");
@@ -299,7 +398,6 @@ mod tests {
299398
assert_err!(res);
300399
}
301400

302-
303401
#[rstest]
304402
fn test_patch_ext_remove(mut data: Value) {
305403
let path = format_ptr!("/foo/*/baz/quzz");

0 commit comments

Comments
 (0)