Skip to content

Commit deb64b5

Browse files
committed
add matches function
1 parent 02750ec commit deb64b5

File tree

4 files changed

+97
-9
lines changed

4 files changed

+97
-9
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ license = "MIT"
88
readme = "README.md"
99

1010
[dependencies]
11-
json-patch = "3"
12-
jsonptr = "0.6.3"
11+
json-patch = "4"
12+
jsonptr = "0.7.1"
1313
serde_json = "1"
1414
thiserror = "1"
1515

src/errors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use jsonptr::assign::AssignError;
1+
use jsonptr::assign;
22
use jsonptr::index::ParseIndexError;
33
use jsonptr::resolve::ResolveError;
44
pub use thiserror::Error;
@@ -21,7 +21,7 @@ pub enum PatchError {
2121
ResolveError(#[from] ResolveError),
2222

2323
#[error("json path assign error: {0}")]
24-
AssignError(#[from] AssignError),
24+
AssignError(#[from] assign::Error),
2525

2626
#[error("index parse error: {0}")]
2727
ParseIndexError(#[from] ParseIndexError),

src/lib.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
121162
pub 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

Comments
 (0)