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.as_str().into()));
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.as_str().into()));
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 v.as_object_mut()
220 .ok_or(PatchError::UnexpectedType(subpath.as_str().into()))?
221 .remove(key.decoded().as_ref());
222 }
223
224 Ok(())
225}
226
227fn patch_ext_helper<'a>(
230 path: &Pointer,
231 value: &'a mut Value,
232 mode: PatchMode,
233) -> Result<Vec<&'a mut Value>, PatchError> {
234 let Some(idx) = path.as_str().find("/*") else {
235 if path.resolve(value).is_err() {
236 match mode {
237 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
238 PatchMode::Create => {
239 path.assign(value, json!({}))?;
240 },
241 PatchMode::Skip => return Ok(vec![]),
242 }
243 }
244 return Ok(vec![path.resolve_mut(value)?]);
245 };
246
247 let (head, cons) = path.split_at(idx).unwrap();
249 let mut res = vec![];
250
251 if head.resolve(value).is_err() {
255 match mode {
256 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
257 PatchMode::Create => {
258 path.assign(value, json!([]))?;
259 },
260 PatchMode::Skip => return Ok(vec![]),
261 }
262 }
263
264 let next_array_val =
266 head.resolve_mut(value)?.as_array_mut().ok_or(PatchError::UnexpectedType(head.as_str().into()))?;
267
268 for v in next_array_val {
270 if let Some((_, c)) = cons.split_front() {
274 res.extend(patch_ext_helper(c, v, mode)?);
275 } else {
276 panic!("cons can't be root");
277 }
278 }
279 Ok(res)
280}
281
282#[cfg(test)]
283mod tests {
284 use assertables::*;
285 use rstest::*;
286
287 use super::*;
288 use crate as json_patch_ext; #[fixture]
291 fn data() -> Value {
292 json!({
293 "foo": [
294 {"baz": {"buzz": 0}},
295 {"baz": {"quzz": 1}},
296 {"baz": {"fixx": 2}},
297 ],
298 })
299 }
300
301 #[rstest]
302 fn test_matches_1(data: Value) {
303 let path = format_ptr!("/foo");
304 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
305 assert_eq!(m, vec![format_ptr!("/foo")]);
306 }
307
308 #[rstest]
309 fn test_matches_2(data: Value) {
310 let path = format_ptr!("/foo/*/baz");
311 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
312 assert_eq!(m, vec![format_ptr!("/foo/0/baz"), format_ptr!("/foo/1/baz"), format_ptr!("/foo/2/baz")]);
313 }
314
315 #[rstest]
316 fn test_matches_3(data: Value) {
317 let path = format_ptr!("/foo/*");
318 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
319 assert_eq!(m, vec![format_ptr!("/foo/0"), format_ptr!("/foo/1"), format_ptr!("/foo/2")]);
320 }
321
322 #[rstest]
323 #[case(format_ptr!("/foo/*/baz/fixx"))]
324 #[case(format_ptr!("/foo/2/baz/fixx"))]
325 fn test_matches_4(#[case] path: PointerBuf, data: Value) {
326 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
327 assert_eq!(m, vec![format_ptr!("/foo/2/baz/fixx")]);
328 }
329
330 #[rstest]
331 fn test_matches_root() {
332 let path = format_ptr!("/*");
333 let data = json!(["foo", "bar"]);
334 let m: Vec<_> = matches(&path, &data).iter().map(|(p, _)| p.clone()).collect();
335 assert_eq!(m, vec![format_ptr!("/0"), format_ptr!("/1")]);
336 }
337
338 #[rstest]
339 #[case(format_ptr!("/*"))]
340 #[case(format_ptr!("/food"))]
341 #[case(format_ptr!("/foo/3/baz"))]
342 #[case(format_ptr!("/foo/bar/baz"))]
343 #[case(format_ptr!("/foo/0/baz/fixx"))]
344 fn test_no_match(#[case] path: PointerBuf, data: Value) {
345 let m = matches(&path, &data);
346 assert_is_empty!(m);
347 }
348
349 #[rstest]
350 fn test_patch_ext_add(mut data: Value) {
351 let path = format_ptr!("/foo/*/baz/buzz");
352 let res = patch_ext(&mut data, add_operation(path, json!(42)));
353 assert_ok!(res);
354 assert_eq!(
355 data,
356 json!({
357 "foo": [
358 {"baz": {"buzz": 42 }},
359 {"baz": {"quzz": 1, "buzz": 42}},
360 {"baz": {"fixx": 2, "buzz": 42}},
361 ],
362 })
363 );
364 }
365
366 #[rstest]
367 fn test_patch_ext_add_escaped() {
368 let path = format_ptr!("/foo/bar/{}", escape("testing.sh/baz"));
369 let mut data = json!({});
370 let res = patch_ext(&mut data, add_operation(path, json!(42)));
371 assert_ok!(res);
372 assert_eq!(data, json!({"foo": {"bar": {"testing.sh/baz": 42}}}));
373 }
374
375 #[rstest]
376 fn test_patch_ext_replace(mut data: Value) {
377 let path = format_ptr!("/foo/*/baz");
378 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
379 assert_ok!(res);
380 assert_eq!(
381 data,
382 json!({
383 "foo": [
384 {"baz": 42},
385 {"baz": 42},
386 {"baz": 42},
387 ],
388 })
389 );
390 }
391
392 #[rstest]
393 fn test_patch_ext_replace_err(mut data: Value) {
394 let path = format_ptr!("/foo/*/baz/buzz");
395 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
396 println!("{data:?}");
397 assert_err!(res);
398 }
399
400 #[rstest]
401 fn test_patch_ext_remove(mut data: Value) {
402 let path = format_ptr!("/foo/*/baz/quzz");
403 let res = patch_ext(&mut data, remove_operation(path));
404 assert_ok!(res);
405 assert_eq!(
406 data,
407 json!({
408 "foo": [
409 {"baz": {"buzz": 0}},
410 {"baz": {}},
411 {"baz": {"fixx": 2}},
412 ],
413 })
414 );
415 }
416}