1use anyhow::{Result, bail, ensure};
2use surrealdb_types::ToSql;
3
4use crate::expr::operation::PatchError;
5use crate::expr::{Error, Operation};
6use crate::val::{Set, Strand, Value};
7
8#[derive(Clone, Copy)]
11enum Write {
12 Insert,
16 Overwrite,
19}
20
21enum Located<'a> {
23 Found(&'a mut Value),
25 Absent,
27 Many,
32}
33
34fn locate<'a>(this: &'a mut Value, path: &[Strand]) -> Located<'a> {
43 let mut at = this;
44 for token in path {
45 at = match at {
46 Value::Object(o) => match o.get_mut(token.as_str()) {
47 Some(v) => v,
48 None => return Located::Absent,
49 },
50 Value::Array(a) => match Operation::array_index(token.as_str()) {
51 Some(i) => match a.get_mut(i) {
52 Some(v) => v,
53 None => return Located::Absent,
54 },
55 None => return Located::Many,
56 },
57 Value::Set(_) => return Located::Many,
58 _ => return Located::Absent,
59 };
60 }
61 Located::Found(at)
62}
63
64fn normalize_sets(value: &mut Value) {
74 match value {
75 Value::Set(s) => {
76 let mut items = Vec::from(std::mem::take(s));
77 items.iter_mut().for_each(normalize_sets);
78 *s = Set::from(items);
79 }
80 Value::Array(a) => a.iter_mut().for_each(normalize_sets),
81 Value::Object(o) => o.iter_mut().for_each(|(_, v)| normalize_sets(v)),
82 _ => (),
83 }
84}
85
86fn write(this: &mut Value, path: &[Strand], value: Value, mode: Write) -> Result<()> {
105 let Some((last, left)) = path.split_last() else {
106 *this = value;
107 return Ok(());
108 };
109 let position = match last.as_str() {
112 "-" => Some(None),
113 token => Operation::array_index(token).map(Some),
114 };
115 if let Some(index) = position {
116 match locate(this, left) {
117 Located::Found(Value::Array(v)) => {
118 let len = v.len();
119 let out_of_bounds = || {
120 Error::InvalidPatch(PatchError {
121 message: format!(
122 "index {at} is out of bounds for array of length {len}",
123 at = index.unwrap_or(len),
124 ),
125 })
126 };
127 match mode {
128 Write::Insert => {
129 let at = index.unwrap_or(len);
130 if at > len {
131 bail!(out_of_bounds());
132 }
133 v.insert(at, value);
134 }
135 Write::Overwrite => {
136 let Some(slot) = index.and_then(|at| v.get_mut(at)) else {
137 bail!(out_of_bounds());
138 };
139 *slot = value;
140 }
141 }
142 return Ok(());
143 }
144 Located::Many => {
145 bail!(Error::InvalidPatch(PatchError {
146 message: format!(
147 "'{last}' names a position, but the path before it reaches every element of a sequence rather than one of them",
148 ),
149 }))
150 }
151 Located::Found(_) | Located::Absent => (),
153 }
154 }
155 this.put(&Operation::path_to_parts(path), value);
156 Ok(())
157}
158
159impl Value {
160 pub fn patch(&mut self, ops: Value) -> Result<()> {
161 let mut this = self.clone();
162 let operations = Operation::value_to_operations(ops)
163 .map_err(Error::InvalidPatch)
164 .map_err(anyhow::Error::new)?;
165
166 for op in &operations {
175 ensure!(
176 op.max_path_len() <= crate::val::MAX_VALUE_DEPTH,
177 Error::ValueTooDeep {
178 max: crate::val::MAX_VALUE_DEPTH,
179 }
180 );
181 }
182
183 for operation in operations {
185 match operation {
186 Operation::Add {
188 path,
189 value,
190 } => {
191 let names_position = path.last().is_some_and(|last| {
197 last.as_str() == "-" || Operation::array_index(last.as_str()).is_some()
198 });
199 if !names_position
200 && matches!(locate(&mut this, &path), Located::Found(Value::Array(_)))
201 {
202 this.inc(&Operation::path_to_parts(&path), value)?;
203 } else {
204 write(&mut this, &path, value, Write::Insert)?;
205 }
206 }
207 Operation::Remove {
209 path,
210 } => {
211 let path = Operation::path_to_parts(&path);
212 this.cut(&path);
213 }
214 Operation::Replace {
216 path,
217 value,
218 } => write(&mut this, &path, value, Write::Overwrite)?,
219 Operation::Change {
221 path,
222 value,
223 } => {
224 let parts = Operation::path_to_parts(&path);
225 if let Value::String(p) = value
226 && let Value::String(v) = this.pick(&parts)
227 {
228 let dmp = dmp::new();
229 let pch = dmp.patch_from_text(p.into_string()).map_err(|e| {
230 Error::InvalidPatch(PatchError {
231 message: format!("{e:?}"),
232 })
233 })?;
234 let (txt, _) = dmp.patch_apply(&pch, v.as_str()).map_err(|e| {
235 Error::InvalidPatch(PatchError {
236 message: format!("{e:?}"),
237 })
238 })?;
239 let txt = txt.into_iter().collect::<String>();
240 write(&mut this, &path, Value::from(txt), Write::Overwrite)?;
241 }
242 }
243 Operation::Copy {
245 path,
246 from,
247 } => {
248 let val = this.pick(&Operation::path_to_parts(&from));
249 write(&mut this, &path, val, Write::Insert)?;
250 }
251 Operation::Move {
253 path,
254 from,
255 } => {
256 let from = Operation::path_to_parts(&from);
261 let val = this.pick(&from);
262 this.cut(&from);
263 write(&mut this, &path, val, Write::Insert)?;
264 }
265 Operation::Test {
267 path,
268 value,
269 } => {
270 let path = Operation::path_to_parts(&path);
271 let val = this.pick(&path);
272 ensure!(
273 value == val,
274 Error::PatchTest {
275 expected: value.to_sql(),
276 got: val.to_sql(),
277 }
278 );
279 }
280 }
281 }
282 normalize_sets(&mut this);
283 *self = this;
284 Ok(())
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use crate::expr::Operation;
292 use crate::syn;
293
294 macro_rules! parse_val {
295 ($input:expr) => {
296 crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
297 };
298 }
299
300 #[tokio::test]
301 async fn patch_add_simple() {
302 let mut val = parse_val!("{ test: { other: null, something: 123 } }");
303 let ops = parse_val!("[{ op: 'add', path: '/temp', value: true }]");
304 let res = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
305 val.patch(ops).unwrap();
306 assert_eq!(res, val);
307 }
308
309 #[tokio::test]
314 async fn patch_rejects_path_past_depth_cap() {
315 use crate::val::MAX_VALUE_DEPTH;
316 let ops = parse_val!(&format!(
317 "[{{ op: 'add', path: '{}', value: 1 }}]",
318 "/a".repeat(MAX_VALUE_DEPTH + 1)
319 ));
320 let mut val = parse_val!("{ existing: 1 }");
321 let before = val.clone();
322 let err = val.patch(ops).unwrap_err();
323 assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
324 assert_eq!(val, before);
326 }
327
328 #[tokio::test]
331 async fn patch_rejects_from_path_past_depth_cap() {
332 use crate::val::MAX_VALUE_DEPTH;
333 let ops = parse_val!(&format!(
334 "[{{ op: 'copy', path: '/x', from: '{}' }}]",
335 "/a".repeat(MAX_VALUE_DEPTH + 1)
336 ));
337 let mut val = parse_val!("{ }");
338 let err = val.patch(ops).unwrap_err();
339 assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
340 }
341
342 #[tokio::test]
345 async fn patch_accepts_path_at_depth_cap() {
346 use crate::val::MAX_VALUE_DEPTH;
347 let ops = parse_val!(&format!(
348 "[{{ op: 'add', path: '{}', value: 1 }}]",
349 "/a".repeat(MAX_VALUE_DEPTH)
350 ));
351 let mut val = parse_val!("{ }");
352 val.patch(ops).expect("a path at the cap must apply");
353 }
354
355 #[tokio::test]
356 async fn patch_remove_simple() {
357 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
358 let ops = parse_val!("[{ op: 'remove', path: '/temp' }]");
359 let res = parse_val!("{ test: { other: null, something: 123 } }");
360 val.patch(ops).unwrap();
361 assert_eq!(res, val);
362 }
363
364 #[tokio::test]
365 async fn patch_replace_simple() {
366 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
367 let ops = parse_val!("[{ op: 'replace', path: '/temp', value: 'text' }]");
368 let res = parse_val!("{ test: { other: null, something: 123 }, temp: 'text' }");
369 val.patch(ops).unwrap();
370 assert_eq!(res, val);
371 }
372
373 #[tokio::test]
374 async fn patch_change_simple() {
375 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: 'test' }");
376 let ops = parse_val!(
377 "[{ op: 'change', path: '/temp', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
378 );
379 let res = parse_val!("{ test: { other: null, something: 123 }, temp: 'text' }");
380 val.patch(ops).unwrap();
381 assert_eq!(res, val);
382 }
383
384 #[tokio::test]
385 async fn patch_copy_simple() {
386 let mut val = parse_val!("{ test: 123, temp: true }");
387 let ops = parse_val!("[{ op: 'copy', path: '/temp', from: '/test' }]");
388 let res = parse_val!("{ test: 123, temp: 123 }");
389 val.patch(ops).unwrap();
390 assert_eq!(res, val);
391 }
392
393 #[tokio::test]
394 async fn patch_move_simple() {
395 let mut val = parse_val!("{ temp: true, some: 123 }");
396 let ops = parse_val!("[{ op: 'move', path: '/other', from: '/temp' }]");
397 let res = parse_val!("{ other: true, some: 123 }");
398 val.patch(ops).unwrap();
399 assert_eq!(res, val);
400 }
401
402 #[tokio::test]
403 async fn patch_test_simple() {
404 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
405 let ops = parse_val!(
406 "[{ op: 'remove', path: '/test/something' }, { op: 'test', path: '/temp', value: true }]"
407 );
408 let res = parse_val!("{ test: { other: 'test' }, temp: true }");
409 val.patch(ops).unwrap();
410 assert_eq!(res, val);
411 }
412
413 #[tokio::test]
414 async fn patch_add_embedded() {
415 let mut val = parse_val!("{ test: { other: null, something: 123 } }");
416 let ops = parse_val!("[{ op: 'add', path: '/temp/test', value: true }]");
417 let res = parse_val!("{ test: { other: null, something: 123 }, temp: { test: true } }");
418 val.patch(ops).unwrap();
419 assert_eq!(res, val);
420 }
421
422 #[tokio::test]
423 async fn patch_remove_embedded() {
424 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
425 let ops = parse_val!("[{ op: 'remove', path: '/test/other' }]");
426 let res = parse_val!("{ test: { something: 123 }, temp: true }");
427 val.patch(ops).unwrap();
428 assert_eq!(res, val);
429 }
430
431 #[tokio::test]
432 async fn patch_remove_array_index() {
433 let mut val = parse_val!("{ id: todo:1 }");
434 let add = parse_val!("[{ op: 'add', path: '/list', value: ['Item here'] }]");
435 let remove = parse_val!("[{ op: 'remove', path: '/list/0' }]");
436 let res = parse_val!("{ id: todo:1, list: [] }");
437 val.patch(add).unwrap();
438 val.patch(remove).unwrap();
439 assert_eq!(res, val);
440 }
441
442 #[tokio::test]
443 async fn patch_add_array_index_append_at_length() {
444 let mut val = parse_val!("{ list: ['a', 'b'] }");
446 let ops = parse_val!("[{ op: 'add', path: '/list/2', value: 'c' }]");
447 let res = parse_val!("{ list: ['a', 'b', 'c'] }");
448 val.patch(ops).unwrap();
449 assert_eq!(res, val);
450 }
451
452 #[tokio::test]
453 async fn patch_add_array_index_out_of_bounds_errors() {
454 let mut val = parse_val!("{ list: ['a', 'b'] }");
456 let ops = parse_val!("[{ op: 'add', path: '/list/5', value: 'c' }]");
457 let err = val.patch(ops).unwrap_err();
458 let msg = err.to_string();
459 assert!(
460 msg.contains("index 5 is out of bounds for array of length 2"),
461 "unexpected error message: {msg}"
462 );
463 assert_eq!(val, parse_val!("{ list: ['a', 'b'] }"));
465 }
466
467 #[tokio::test]
468 async fn patch_replace_embedded() {
469 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
470 let ops = parse_val!("[{ op: 'replace', path: '/test/other', value: 'text' }]");
471 let res = parse_val!("{ test: { other: 'text', something: 123 }, temp: true }");
472 val.patch(ops).unwrap();
473 assert_eq!(res, val);
474 }
475
476 #[tokio::test]
477 async fn patch_change_embedded() {
478 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
479 let ops = parse_val!(
480 "[{ op: 'change', path: '/test/other', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
481 );
482 let res = parse_val!("{ test: { other: 'text', something: 123 }, temp: true }");
483 val.patch(ops).unwrap();
484 assert_eq!(res, val);
485 }
486
487 #[tokio::test]
488 async fn patch_copy_embedded() {
489 let mut val = parse_val!("{ test: { other: null }, temp: 123 }");
490 let ops = parse_val!("[{ op: 'copy', path: '/test/other', from: '/temp' }]");
491 let res = parse_val!("{ test: { other: 123 }, temp: 123 }");
492 val.patch(ops).unwrap();
493 assert_eq!(res, val);
494 }
495
496 #[tokio::test]
497 async fn patch_move_embedded() {
498 let mut val = parse_val!("{ test: { other: ':3', some: 123 }}");
499 let ops = parse_val!("[{ op: 'move', path: '/temp', from: '/test/other' }]");
500 let res = parse_val!("{ test: { some: 123 }, temp: ':3' }");
501 val.patch(ops).unwrap();
502 assert_eq!(res, val);
503 }
504
505 #[tokio::test]
506 async fn patch_test_embedded() {
507 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
508 let ops = parse_val!(
509 "[{ op: 'remove', path: '/test/other' }, { op: 'test', path: '/test/something', value: 123 }]"
510 );
511 let res = parse_val!("{ test: { something: 123 }, temp: true }");
512 val.patch(ops).unwrap();
513 assert_eq!(res, val);
514 }
515
516 #[tokio::test]
517 async fn patch_change_invalid() {
518 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
520 let ops = parse_val!("[{ op: 'change', path: '/test/other', value: 'text' }]");
521 assert!(val.patch(ops).is_err());
522 }
523
524 #[tokio::test]
525 async fn patch_test_invalid() {
526 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
527 let should = val.clone();
528 let ops = parse_val!(
529 "[{ op: 'remove', path: '/test/other' }, { op: 'test', path: '/test/something', value: 'not same' }]"
530 );
531 assert!(val.patch(ops).is_err());
532 assert_eq!(val, should);
534 }
535
536 #[tokio::test]
537 async fn patch_change_root() {
538 let mut val = parse_val!("'Hello'");
540 let ops = parse_val!(
541 "[{ op: 'change', path: '', value: '@@ -1,5 +1,12 @@\n Hello\n+ there!\n' }]"
542 );
543 val.patch(ops).unwrap();
544 assert_eq!(val, parse_val!("'Hello there!'"));
545 }
546
547 #[tokio::test]
548 async fn patch_replace_root() {
549 let mut val = parse_val!("1");
550 let ops = parse_val!("[{ op: 'replace', path: '', value: 2 }]");
551 val.patch(ops).unwrap();
552 assert_eq!(val, parse_val!("2"));
553 }
554
555 #[tokio::test]
556 async fn patch_diff_roundtrip_string_root() {
557 let mut start = parse_val!("'Hello'");
558 let after = parse_val!("'Hello there!'");
559 let ops = Operation::operations_to_value(start.diff(&after));
560 start.patch(ops).unwrap();
561 assert_eq!(start, after);
562 }
563
564 #[tokio::test]
565 async fn patch_replace_array_element_leaf() {
566 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
567 let ops = parse_val!("[{ op: 'replace', path: '/details/0/isbn', value: 'z' }]");
568 let res = parse_val!("{ details: [{ isbn: 'z' }, { isbn: 'b' }] }");
569 val.patch(ops).unwrap();
570 assert_eq!(res, val);
571 }
572
573 #[tokio::test]
574 async fn patch_replace_whole_array_element() {
575 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
576 let ops = parse_val!("[{ op: 'replace', path: '/details/1', value: 'gone' }]");
577 let res = parse_val!("{ details: [{ isbn: 'a' }, 'gone'] }");
578 val.patch(ops).unwrap();
579 assert_eq!(res, val);
580 }
581
582 #[tokio::test]
583 async fn patch_replace_nested_array_index() {
584 let mut val = parse_val!("{ a: [[1, 2], [3, 4]] }");
585 let ops = parse_val!("[{ op: 'replace', path: '/a/0/1', value: 99 }]");
586 let res = parse_val!("{ a: [[1, 99], [3, 4]] }");
587 val.patch(ops).unwrap();
588 assert_eq!(res, val);
589 }
590
591 #[tokio::test]
592 async fn patch_change_array_element() {
593 let mut val = parse_val!("{ tags: ['test', 'keep'] }");
594 let ops = parse_val!(
595 "[{ op: 'change', path: '/tags/0', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
596 );
597 let res = parse_val!("{ tags: ['text', 'keep'] }");
598 val.patch(ops).unwrap();
599 assert_eq!(res, val);
600 }
601
602 #[tokio::test]
603 async fn patch_copy_from_array_element() {
604 let mut val = parse_val!("{ list: ['p', 'q'] }");
605 let ops = parse_val!("[{ op: 'copy', path: '/first', from: '/list/0' }]");
606 let res = parse_val!("{ list: ['p', 'q'], first: 'p' }");
607 val.patch(ops).unwrap();
608 assert_eq!(res, val);
609 }
610
611 #[tokio::test]
612 async fn patch_move_from_array_element() {
613 let mut val = parse_val!("{ list: ['p', 'q'] }");
614 let ops = parse_val!("[{ op: 'move', path: '/moved', from: '/list/0' }]");
615 let res = parse_val!("{ list: ['q'], moved: 'p' }");
616 val.patch(ops).unwrap();
617 assert_eq!(res, val);
618 }
619
620 #[tokio::test]
621 async fn patch_test_array_element() {
622 let mut val = parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }");
623 let ops = parse_val!("[{ op: 'test', path: '/rows/1/n', value: 2 }]");
624 val.patch(ops).unwrap();
625 assert_eq!(val, parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }"));
626
627 let mut val = parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }");
628 let ops = parse_val!("[{ op: 'test', path: '/rows/1/n', value: 1 }]");
629 assert!(val.patch(ops).is_err());
630 }
631
632 #[tokio::test]
633 async fn patch_add_intermediate_array_index() {
634 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
635 let ops = parse_val!("[{ op: 'add', path: '/details/0/title', value: 'T' }]");
636 let res = parse_val!("{ details: [{ isbn: 'a', title: 'T' }, { isbn: 'b' }] }");
637 val.patch(ops).unwrap();
638 assert_eq!(res, val);
639 }
640
641 #[tokio::test]
642 async fn patch_add_array_index_under_array_index() {
643 let mut val = parse_val!("{ a: [['x']] }");
646 let ops = parse_val!("[{ op: 'add', path: '/a/0/1', value: 'y' }]");
647 val.patch(ops).unwrap();
648 assert_eq!(val, parse_val!("{ a: [['x', 'y']] }"));
649
650 let mut val = parse_val!("{ a: [['x']] }");
651 let ops = parse_val!("[{ op: 'add', path: '/a/0/5', value: 'y' }]");
652 let err = val.patch(ops).unwrap_err();
653 assert!(
654 err.to_string().contains("index 5 is out of bounds for array of length 1"),
655 "unexpected error message: {err}"
656 );
657 assert_eq!(val, parse_val!("{ a: [['x']] }"));
658 }
659
660 #[tokio::test]
661 async fn patch_object_member_named_as_an_index() {
662 let mut val = parse_val!("{ m: { '0': 'zero', '1': 'one' } }");
665 let ops = parse_val!(
666 "[{ op: 'replace', path: '/m/0', value: 'Z' }, { op: 'remove', path: '/m/1' }]"
667 );
668 let res = parse_val!("{ m: { '0': 'Z' } }");
669 val.patch(ops).unwrap();
670 assert_eq!(res, val);
671 }
672
673 #[tokio::test]
674 async fn patch_object_member_not_spelled_as_an_index() {
675 let mut val =
678 parse_val!("{ m: { '007': 'bond', '7': 'seven', '+3': 'plus', '-1': 'neg' } }");
679 let ops = parse_val!(
680 "[{ op: 'remove', path: '/m/007' }, { op: 'remove', path: '/m/+3' }, { op: 'replace', path: '/m/-1', value: 'N' }]"
681 );
682 let res = parse_val!("{ m: { '7': 'seven', '-1': 'N' } }");
683 val.patch(ops).unwrap();
684 assert_eq!(res, val);
685 }
686
687 #[tokio::test]
688 async fn patch_copy_into_array_position_inserts() {
689 let mut val = parse_val!("{ list: ['a', 'c'], v: 'b' }");
692 let ops = parse_val!("[{ op: 'copy', path: '/list/1', from: '/v' }]");
693 let res = parse_val!("{ list: ['a', 'b', 'c'], v: 'b' }");
694 val.patch(ops).unwrap();
695 assert_eq!(res, val);
696 }
697
698 #[tokio::test]
699 async fn patch_move_within_one_array() {
700 let mut val = parse_val!("{ list: ['a', 'b', 'c'] }");
703 let ops = parse_val!("[{ op: 'move', path: '/list/2', from: '/list/0' }]");
704 let res = parse_val!("{ list: ['b', 'c', 'a'] }");
705 val.patch(ops).unwrap();
706 assert_eq!(res, val);
707 }
708
709 #[tokio::test]
710 async fn patch_move_into_array_position_inserts() {
711 let mut val = parse_val!("{ list: ['a', 'c'], v: 'b' }");
712 let ops = parse_val!("[{ op: 'move', path: '/list/1', from: '/v' }]");
713 let res = parse_val!("{ list: ['a', 'b', 'c'] }");
714 val.patch(ops).unwrap();
715 assert_eq!(res, val);
716 }
717
718 #[tokio::test]
719 async fn patch_append_token_appends_or_names_a_member() {
720 let mut val = parse_val!("{ list: ['a'], v: 'b' }");
723 let ops = parse_val!(
724 "[{ op: 'add', path: '/list/-', value: 'x' }, { op: 'copy', path: '/list/-', from: '/v' }]"
725 );
726 val.patch(ops).unwrap();
727 assert_eq!(val, parse_val!("{ list: ['a', 'x', 'b'], v: 'b' }"));
728 let mut val = parse_val!("{ m: { a: 1 } }");
730 let ops = parse_val!("[{ op: 'add', path: '/m/-', value: 2 }]");
731 val.patch(ops).unwrap();
732 assert_eq!(val, parse_val!("{ m: { a: 1, '-': 2 } }"));
733 }
734
735 #[tokio::test]
736 async fn patch_overwrite_needs_a_position_that_exists() {
737 for pointer in ["/list/9", "/list/2", "/list/-"] {
740 let mut val = parse_val!("{ list: ['a', 'b'] }");
741 let ops = parse_val!(&format!("[{{ op: 'replace', path: '{pointer}', value: 'z' }}]"));
742 let err = val.patch(ops).unwrap_err();
743 assert!(
744 err.to_string().contains("is out of bounds for array of length 2"),
745 "unexpected error for {pointer}: {err}"
746 );
747 assert_eq!(val, parse_val!("{ list: ['a', 'b'] }"));
748 }
749 }
750
751 #[tokio::test]
752 async fn patch_add_index_on_a_non_array_names_a_member() {
753 let mut val = parse_val!("{ m: { x: 1 } }");
756 let ops = parse_val!("[{ op: 'add', path: '/m/0', value: 5 }]");
757 val.patch(ops).unwrap();
758 assert_eq!(val, parse_val!("{ m: { x: 1, '0': 5 } }"));
759 }
760
761 #[tokio::test]
762 async fn patch_set_keeps_its_invariant() {
763 let mut val = parse_val!("{ s: { 'b', 'c', } }");
767 val.patch(parse_val!("[{ op: 'replace', path: '/s/0', value: 'z' }]")).unwrap();
768 assert_eq!(val, parse_val!("{ s: { 'c', 'z', } }"));
769
770 let mut val = parse_val!("{ s: { 'b', 'c', } }");
772 val.patch(parse_val!("[{ op: 'replace', path: '/s/0', value: 'c' }]")).unwrap();
773 assert_eq!(val, parse_val!("{ s: { 'c', } }"));
774
775 let mut val = parse_val!("{ s: { { n: 'zz' }, { n: 'aa' }, } }");
778 val.patch(parse_val!("[{ op: 'replace', path: '/s/0/n', value: 'zz' }]")).unwrap();
779 assert_eq!(val, parse_val!("{ s: { { n: 'zz' }, } }"));
780
781 let mut val = parse_val!("{ s: { [{ inner: { 'q', 'p', } }], } }");
783 val.patch(parse_val!("[{ op: 'replace', path: '/s/0/0/inner', value: { 'y', 'x', } }]"))
784 .unwrap();
785 assert_eq!(val, parse_val!("{ s: { [{ inner: { 'x', 'y', } }], } }"));
786 }
787
788 #[tokio::test]
789 async fn patch_position_under_a_broadcast_path_is_refused() {
790 for op in [
795 "{ op: 'replace', path: '/list/name/0', value: 'X' }",
796 "{ op: 'add', path: '/list/name/0', value: 'X' }",
797 "{ op: 'copy', path: '/list/name/0', from: '/v' }",
798 ] {
799 let mut val = parse_val!("{ list: [{ name: 'AA' }, { name: 'BB' }], v: 'X' }");
800 let err = val.patch(parse_val!(&format!("[{op}]"))).unwrap_err();
801 assert!(
802 err.to_string().contains("reaches every element of a sequence"),
803 "unexpected error for {op}: {err}"
804 );
805 assert_eq!(val, parse_val!("{ list: [{ name: 'AA' }, { name: 'BB' }], v: 'X' }"));
806 }
807 }
808
809 #[tokio::test]
810 async fn patch_move_keeps_its_source_when_the_destination_is_refused() {
811 let mut val = parse_val!("{ list: [{ name: 'AA' }], v: 'keep' }");
815 let ops = parse_val!("[{ op: 'move', path: '/list/name/0', from: '/v' }]");
816 assert!(val.patch(ops).is_err());
817 assert_eq!(val, parse_val!("{ list: [{ name: 'AA' }], v: 'keep' }"));
818 }
819
820 #[tokio::test]
821 async fn patch_destination_inside_the_source_is_refused() {
822 for op in [
825 "{ op: 'copy', path: '/a/-', from: '/a' }",
826 "{ op: 'copy', path: '/a/0/b', from: '/a' }",
827 "{ op: 'move', path: '/a/b', from: '/a' }",
828 ] {
829 let mut val = parse_val!("{ a: [1, 2] }");
830 let err = val.patch(parse_val!(&format!("[{op}]"))).unwrap_err();
831 assert!(
832 err.to_string().contains("that is not inside 'from'"),
833 "unexpected error for {op}: {err}"
834 );
835 assert_eq!(val, parse_val!("{ a: [1, 2] }"));
836 }
837 let mut val = parse_val!("{ a: [1, 2], ab: 'x' }");
839 val.patch(parse_val!("[{ op: 'copy', path: '/ab', from: '/a' }]")).unwrap();
840 assert_eq!(val, parse_val!("{ a: [1, 2], ab: [1, 2] }"));
841 }
842
843 #[tokio::test]
844 async fn patch_diff_roundtrip_array_element() {
845 for (a, b) in [
846 ("{ list: [1, 2, 3] }", "{ list: [1, 9, 3] }"),
847 ("{ list: ['aa', 'bb'] }", "{ list: ['aa', 'bx'] }"),
848 ("{ list: [{ n: 1 }, { n: 2 }] }", "{ list: [{ n: 1 }, { n: 7 }] }"),
849 ("{ list: [1] }", "{ list: [1, 2, 3] }"),
850 ("{ list: [1, 2, 3] }", "{ list: [1] }"),
851 ("{ list: [1, 2, 3, 4] }", "{ list: [9] }"),
852 ("{ list: [{ n: 1 }, { n: 2 }, { n: 3 }] }", "{ list: [{ n: 5 }] }"),
853 ] {
854 let mut start = parse_val!(a);
855 let after = parse_val!(b);
856 let ops = Operation::operations_to_value(start.diff(&after));
857 start.patch(ops).unwrap();
858 assert_eq!(start, after, "diff of {a} -> {b} did not round-trip");
859 }
860 }
861
862 #[tokio::test]
863 async fn patch_diff_roundtrip_number_root() {
864 let mut start = parse_val!("1");
865 let after = parse_val!("2");
866 let ops = Operation::operations_to_value(start.diff(&after));
867 start.patch(ops).unwrap();
868 assert_eq!(start, after);
869 }
870}