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 println!("{cons}");
147 for (i, v) in next_array_val.iter().enumerate() {
148 let idx_str = format!("/{i}");
150 let idx_path = PointerBuf::parse(&idx_str).unwrap();
151
152 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
165pub fn patch_ext(obj: &mut Value, p: PatchOperation) -> Result<(), PatchError> {
166 match p {
167 PatchOperation::Add(op) => add_or_replace(obj, &op.path, &op.value, false)?,
168 PatchOperation::Remove(op) => remove(obj, &op.path)?,
169 PatchOperation::Replace(op) => add_or_replace(obj, &op.path, &op.value, true)?,
170 x => patch(obj, &[x])?,
171 }
172 Ok(())
173}
174
175fn add_or_replace(obj: &mut Value, path: &PointerBuf, value: &Value, replace: bool) -> Result<(), PatchError> {
176 let Some((subpath, tail)) = path.split_back() else {
177 return Ok(());
178 };
179
180 let mode = if replace { PatchMode::Error } else { PatchMode::Create };
183 for v in patch_ext_helper(subpath, obj, mode)? {
184 match v {
185 Value::Object(map) => {
186 let key = tail.decoded().into();
187 if replace && !map.contains_key(&key) {
188 return Err(PatchError::TargetDoesNotExist(path.as_str().into()));
189 }
190 map.insert(key, value.clone());
191 },
192 Value::Array(vec) => match tail.to_index()? {
193 Index::Num(idx) => {
194 vec.get(idx).ok_or(PatchError::OutOfBounds(idx))?;
195 if replace {
196 vec[idx] = value.clone();
197 } else {
198 vec.insert(idx, value.clone());
199 }
200 },
201 Index::Next => {
202 vec.push(value.clone());
203 },
204 },
205 _ => {
206 return Err(PatchError::UnexpectedType(path.as_str().into()));
207 },
208 }
209 }
210
211 Ok(())
212}
213
214fn remove(obj: &mut Value, path: &PointerBuf) -> Result<(), PatchError> {
215 let Some((subpath, key)) = path.split_back() else {
216 return Ok(());
217 };
218
219 for v in patch_ext_helper(subpath, obj, PatchMode::Skip)? {
220 v.as_object_mut()
221 .ok_or(PatchError::UnexpectedType(subpath.as_str().into()))?
222 .remove(key.decoded().as_ref());
223 }
224
225 Ok(())
226}
227
228fn patch_ext_helper<'a>(
231 path: &Pointer,
232 value: &'a mut Value,
233 mode: PatchMode,
234) -> Result<Vec<&'a mut Value>, PatchError> {
235 let Some(idx) = path.as_str().find("/*") else {
236 if path.resolve(value).is_err() {
237 match mode {
238 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
239 PatchMode::Create => {
240 path.assign(value, json!({}))?;
241 },
242 PatchMode::Skip => return Ok(vec![]),
243 }
244 }
245 return Ok(vec![path.resolve_mut(value)?]);
246 };
247
248 let (head, cons) = path.split_at(idx).unwrap();
250 let mut res = vec![];
251
252 if head.resolve(value).is_err() {
256 match mode {
257 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
258 PatchMode::Create => {
259 path.assign(value, json!([]))?;
260 },
261 PatchMode::Skip => return Ok(vec![]),
262 }
263 }
264
265 let next_array_val =
267 head.resolve_mut(value)?.as_array_mut().ok_or(PatchError::UnexpectedType(head.as_str().into()))?;
268
269 for v in next_array_val {
271 if let Some((_, c)) = cons.split_front() {
275 res.extend(patch_ext_helper(c, v, mode)?);
276 } else {
277 panic!("cons can't be root");
278 }
279 }
280 Ok(res)
281}
282
283#[cfg(test)]
284mod tests {
285 use assertables::*;
286 use rstest::*;
287
288 use super::*;
289 use crate as json_patch_ext; #[fixture]
292 fn data() -> Value {
293 json!({
294 "foo": [
295 {"baz": {"buzz": 0}},
296 {"baz": {"quzz": 1}},
297 {"baz": {"fixx": 2}},
298 ],
299 })
300 }
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
350 #[rstest]
351 fn test_patch_ext_add(mut data: Value) {
352 let path = format_ptr!("/foo/*/baz/buzz");
353 let res = patch_ext(&mut data, add_operation(path, json!(42)));
354 assert_ok!(res);
355 assert_eq!(
356 data,
357 json!({
358 "foo": [
359 {"baz": {"buzz": 42 }},
360 {"baz": {"quzz": 1, "buzz": 42}},
361 {"baz": {"fixx": 2, "buzz": 42}},
362 ],
363 })
364 );
365 }
366
367 #[rstest]
368 fn test_patch_ext_add_escaped() {
369 let path = format_ptr!("/foo/bar/{}", escape("testing.sh/baz"));
370 let mut data = json!({});
371 let res = patch_ext(&mut data, add_operation(path, json!(42)));
372 assert_ok!(res);
373 assert_eq!(data, json!({"foo": {"bar": {"testing.sh/baz": 42}}}));
374 }
375
376 #[rstest]
377 fn test_patch_ext_replace(mut data: Value) {
378 let path = format_ptr!("/foo/*/baz");
379 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
380 assert_ok!(res);
381 assert_eq!(
382 data,
383 json!({
384 "foo": [
385 {"baz": 42},
386 {"baz": 42},
387 {"baz": 42},
388 ],
389 })
390 );
391 }
392
393 #[rstest]
394 fn test_patch_ext_replace_err(mut data: Value) {
395 let path = format_ptr!("/foo/*/baz/buzz");
396 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
397 println!("{data:?}");
398 assert_err!(res);
399 }
400
401 #[rstest]
402 fn test_patch_ext_remove(mut data: Value) {
403 let path = format_ptr!("/foo/*/baz/quzz");
404 let res = patch_ext(&mut data, remove_operation(path));
405 assert_ok!(res);
406 assert_eq!(
407 data,
408 json!({
409 "foo": [
410 {"baz": {"buzz": 0}},
411 {"baz": {}},
412 {"baz": {"fixx": 2}},
413 ],
414 })
415 );
416 }
417}