1mod errors;
30mod macros;
31
32use json_patch::patch;
33#[doc(no_inline)]
36pub use json_patch::{
37 AddOperation,
38 CopyOperation,
39 MoveOperation,
40 Patch,
41 PatchOperation,
42 RemoveOperation,
43 ReplaceOperation,
44 TestOperation,
45};
46#[doc(no_inline)]
47use jsonptr::index::Index;
48pub use jsonptr::{
49 Pointer,
50 PointerBuf,
51 Token,
52};
53use serde_json::{
54 json,
55 Value,
56};
57
58pub use crate::errors::PatchError;
59
60pub mod prelude {
61 pub use super::{
62 add_operation,
63 copy_operation,
64 escape,
65 format_ptr,
66 matches,
67 move_operation,
68 patch_ext,
69 remove_operation,
70 replace_operation,
71 test_operation,
72 AddOperation,
73 CopyOperation,
74 MoveOperation,
75 Patch,
76 PatchError,
77 PatchOperation,
78 Pointer,
79 PointerBuf,
80 RemoveOperation,
81 ReplaceOperation,
82 TestOperation,
83 Token,
84 };
85}
86
87#[derive(Debug, Clone, Copy)]
89enum PatchMode {
90 Error,
91 Create,
92 Skip,
93}
94
95pub fn add_operation(path: PointerBuf, value: Value) -> PatchOperation {
96 PatchOperation::Add(AddOperation { path, value })
97}
98
99pub fn copy_operation(from: PointerBuf, path: PointerBuf) -> PatchOperation {
100 PatchOperation::Copy(CopyOperation { from, path })
101}
102
103pub fn move_operation(from: PointerBuf, path: PointerBuf) -> PatchOperation {
104 PatchOperation::Move(MoveOperation { from, path })
105}
106
107pub fn remove_operation(path: PointerBuf) -> PatchOperation {
108 PatchOperation::Remove(RemoveOperation { path })
109}
110
111pub fn replace_operation(path: PointerBuf, value: Value) -> PatchOperation {
112 PatchOperation::Replace(ReplaceOperation { path, value })
113}
114
115pub fn test_operation(path: PointerBuf, value: Value) -> PatchOperation {
116 PatchOperation::Test(TestOperation { path, value })
117}
118
119pub fn escape(input: &str) -> String {
120 Token::new(input).encoded().into()
121}
122
123pub fn matches<'a>(path: &Pointer, value: &'a Value) -> Vec<(PointerBuf, &'a Value)> {
124 let Some(idx) = path.as_str().find("/*") else {
125 if let Ok(v) = path.resolve(value) {
128 return vec![(path.to_buf(), v)];
129 } else {
130 return vec![];
131 }
132 };
133
134 let (head, cons) = path.split_at(idx).unwrap();
136 let mut res = vec![];
137
138 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 let idx_str = format!("/{i}");
149 let idx_path = PointerBuf::parse(&idx_str).unwrap();
150
151 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 panic!("cons can't be root");
159 }
160 }
161 res
162}
163
164pub fn patch_ext(obj: &mut Value, p: PatchOperation) -> Result<(), PatchError> {
165 match p {
166 PatchOperation::Add(op) => add_or_replace(obj, &op.path, &op.value, false)?,
167 PatchOperation::Remove(op) => remove(obj, &op.path)?,
168 PatchOperation::Replace(op) => add_or_replace(obj, &op.path, &op.value, true)?,
169 x => patch(obj, &[x])?,
170 }
171 Ok(())
172}
173
174fn add_or_replace(obj: &mut Value, path: &PointerBuf, value: &Value, replace: bool) -> Result<(), PatchError> {
175 let Some((subpath, tail)) = path.split_back() else {
176 return Ok(());
177 };
178
179 let mode = if replace { PatchMode::Error } else { PatchMode::Create };
182 for v in patch_ext_helper(subpath, obj, mode)? {
183 match v {
184 Value::Object(map) => {
185 let key = tail.decoded().into();
186 if replace && !map.contains_key(&key) {
187 return Err(PatchError::TargetDoesNotExist(path.to_string()));
188 }
189 map.insert(key, value.clone());
190 },
191 Value::Array(vec) => match tail.to_index()? {
192 Index::Num(idx) => {
193 vec.get(idx).ok_or(PatchError::OutOfBounds(idx))?;
194 if replace {
195 vec[idx] = value.clone();
196 } else {
197 vec.insert(idx, value.clone());
198 }
199 },
200 Index::Next => {
201 vec.push(value.clone());
202 },
203 },
204 _ => {
205 return Err(PatchError::UnexpectedType(path.to_string()));
206 },
207 }
208 }
209
210 Ok(())
211}
212
213fn remove(obj: &mut Value, path: &PointerBuf) -> Result<(), PatchError> {
214 let Some((subpath, key)) = path.split_back() else {
215 return Ok(());
216 };
217
218 for v in patch_ext_helper(subpath, obj, PatchMode::Skip)? {
219 match v {
220 Value::Object(map) => {
221 map.remove(key.decoded().as_ref());
222 },
223 Value::Array(vec) => {
224 if let Index::Num(idx) = key.to_index()? {
225 vec.get(idx).ok_or(PatchError::OutOfBounds(idx))?;
226 vec.remove(idx);
227 } else {
228 return Err(PatchError::UnexpectedType(key.to_string()));
229 }
230 },
231 _ => {
232 return Err(PatchError::UnexpectedType(path.to_string()));
233 },
234 }
235 }
236
237 Ok(())
238}
239
240fn patch_ext_helper<'a>(
243 path: &Pointer,
244 value: &'a mut Value,
245 mode: PatchMode,
246) -> Result<Vec<&'a mut Value>, PatchError> {
247 let Some(idx) = path.as_str().find("/*") else {
248 if path.resolve(value).is_err() {
249 match mode {
250 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
251 PatchMode::Create => {
252 path.assign(value, json!({}))?;
253 },
254 PatchMode::Skip => return Ok(vec![]),
255 }
256 }
257 return Ok(vec![path.resolve_mut(value)?]);
258 };
259
260 let (head, cons) = path.split_at(idx).unwrap();
262 let mut res = vec![];
263
264 if head.resolve(value).is_err() {
268 match mode {
269 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
270 PatchMode::Create => {
271 path.assign(value, json!([]))?;
272 },
273 PatchMode::Skip => return Ok(vec![]),
274 }
275 }
276
277 let next_array_val =
279 head.resolve_mut(value)?.as_array_mut().ok_or(PatchError::UnexpectedType(head.as_str().into()))?;
280
281 for v in next_array_val {
283 if let Some((_, c)) = cons.split_front() {
287 res.extend(patch_ext_helper(c, v, mode)?);
288 } else {
289 panic!("cons can't be root");
290 }
291 }
292 Ok(res)
293}
294
295#[cfg(test)]
296mod tests {
297 use assertables::*;
298 use rstest::*;
299
300 use super::*;
301 use crate as json_patch_ext; #[fixture]
304 fn data() -> Value {
305 json!({
306 "foo": [
307 {"baz": {"buzz": 0}},
308 {"baz": {"quzz": 1}},
309 {"baz": {"fixx": 2}},
310 ],
311 })
312 }
313
314 #[rstest]
315 fn test_matches_1(data: Value) {
316 let path = format_ptr!("/foo");
317 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
318 assert_eq!(m, vec![format_ptr!("/foo")]);
319 }
320
321 #[rstest]
322 fn test_matches_2(data: Value) {
323 let path = format_ptr!("/foo/*/baz");
324 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
325 assert_eq!(m, vec![format_ptr!("/foo/0/baz"), format_ptr!("/foo/1/baz"), format_ptr!("/foo/2/baz")]);
326 }
327
328 #[rstest]
329 fn test_matches_3(data: Value) {
330 let path = format_ptr!("/foo/*");
331 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
332 assert_eq!(m, vec![format_ptr!("/foo/0"), format_ptr!("/foo/1"), format_ptr!("/foo/2")]);
333 }
334
335 #[rstest]
336 #[case(format_ptr!("/foo/*/baz/fixx"))]
337 #[case(format_ptr!("/foo/2/baz/fixx"))]
338 fn test_matches_4(#[case] path: PointerBuf, data: Value) {
339 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
340 assert_eq!(m, vec![format_ptr!("/foo/2/baz/fixx")]);
341 }
342
343 #[rstest]
344 fn test_matches_root() {
345 let path = format_ptr!("/*");
346 let data = json!(["foo", "bar"]);
347 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
348 assert_eq!(m, vec![format_ptr!("/0"), format_ptr!("/1")]);
349 }
350
351 #[rstest]
352 #[case(format_ptr!("/*"))]
353 #[case(format_ptr!("/food"))]
354 #[case(format_ptr!("/foo/3/baz"))]
355 #[case(format_ptr!("/foo/bar/baz"))]
356 #[case(format_ptr!("/foo/0/baz/fixx"))]
357 fn test_no_match(#[case] path: PointerBuf, data: Value) {
358 let m = matches(&path, &data);
359 assert_is_empty!(m);
360 }
361
362 #[rstest]
363 fn test_patch_ext_add(mut data: Value) {
364 let path = format_ptr!("/foo/*/baz/buzz");
365 let res = patch_ext(&mut data, add_operation(path, json!(42)));
366 assert_ok!(res);
367 assert_eq!(
368 data,
369 json!({
370 "foo": [
371 {"baz": {"buzz": 42 }},
372 {"baz": {"quzz": 1, "buzz": 42}},
373 {"baz": {"fixx": 2, "buzz": 42}},
374 ],
375 })
376 );
377 }
378
379 #[rstest]
380 fn test_patch_ext_add_vec1(mut data: Value) {
381 let path = format_ptr!("/foo/1");
382 let res = patch_ext(&mut data, add_operation(path, json!(42)));
383 assert_ok!(res);
384 assert_eq!(
385 data,
386 json!({
387 "foo": [
388 {"baz": {"buzz": 0}},
389 42,
390 {"baz": {"quzz": 1}},
391 {"baz": {"fixx": 2}},
392 ],
393 })
394 );
395 }
396
397 #[rstest]
398 fn test_patch_ext_add_vec2(mut data: Value) {
399 let path = format_ptr!("/foo/-");
400 let res = patch_ext(&mut data, add_operation(path, json!(42)));
401 assert_ok!(res);
402 assert_eq!(
403 data,
404 json!({
405 "foo": [
406 {"baz": {"buzz": 0}},
407 {"baz": {"quzz": 1}},
408 {"baz": {"fixx": 2}},
409 42,
410 ],
411 })
412 );
413 }
414
415 #[rstest]
416 fn test_patch_ext_add_vec_err(mut data: Value) {
417 let path = format_ptr!("/foo/a");
418 let res = patch_ext(&mut data, add_operation(path, json!(42)));
419 assert_err!(res);
420 }
421
422 #[rstest]
423 fn test_patch_ext_add_escaped() {
424 let path = format_ptr!("/foo/bar/{}", escape("testing.sh/baz"));
425 let mut data = json!({});
426 let res = patch_ext(&mut data, add_operation(path, json!(42)));
427 assert_ok!(res);
428 assert_eq!(data, json!({"foo": {"bar": {"testing.sh/baz": 42}}}));
429 }
430
431 #[rstest]
432 fn test_patch_ext_replace(mut data: Value) {
433 let path = format_ptr!("/foo/*/baz");
434 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
435 assert_ok!(res);
436 assert_eq!(
437 data,
438 json!({
439 "foo": [
440 {"baz": 42},
441 {"baz": 42},
442 {"baz": 42},
443 ],
444 })
445 );
446 }
447
448 #[rstest]
449 fn test_patch_ext_replace_vec1(mut data: Value) {
450 let path = format_ptr!("/foo/1");
451 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
452 assert_ok!(res);
453 assert_eq!(
454 data,
455 json!({
456 "foo": [
457 {"baz": {"buzz": 0}},
458 42,
459 {"baz": {"fixx": 2}},
460 ],
461 })
462 );
463 }
464
465 #[rstest]
466 fn test_patch_ext_replace_vec2(mut data: Value) {
467 let path = format_ptr!("/foo/-");
468 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
469 assert_ok!(res);
470 assert_eq!(
471 data,
472 json!({
473 "foo": [
474 {"baz": {"buzz": 0}},
475 {"baz": {"quzz": 1}},
476 {"baz": {"fixx": 2}},
477 42,
478 ],
479 })
480 );
481 }
482
483 #[rstest]
484 fn test_patch_ext_replace_err(mut data: Value) {
485 let path = format_ptr!("/foo/*/baz/buzz");
486 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
487 assert_err!(res);
488 }
489
490 #[rstest]
491 fn test_patch_ext_remove(mut data: Value) {
492 let path = format_ptr!("/foo/*/baz/quzz");
493 let res = patch_ext(&mut data, remove_operation(path));
494 assert_ok!(res);
495 assert_eq!(
496 data,
497 json!({
498 "foo": [
499 {"baz": {"buzz": 0}},
500 {"baz": {}},
501 {"baz": {"fixx": 2}},
502 ],
503 })
504 );
505 }
506
507 #[rstest]
508 fn test_patch_ext_remove_vec(mut data: Value) {
509 let path = format_ptr!("/foo/1");
510 let res = patch_ext(&mut data, remove_operation(path));
511 assert_ok!(res);
512 assert_eq!(
513 data,
514 json!({
515 "foo": [
516 {"baz": {"buzz": 0}},
517 {"baz": {"fixx": 2}},
518 ],
519 })
520 );
521 }
522
523 #[rstest]
524 fn test_patch_ext_remove_vec_err(mut data: Value) {
525 let path = format_ptr!("/foo/-");
526 let res = patch_ext(&mut data, remove_operation(path));
527 assert_err!(res);
528 }
529}