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 for operation in Operation::value_to_operations(ops)
165 .map_err(Error::InvalidPatch)
166 .map_err(anyhow::Error::new)?
167 {
168 match operation {
169 Operation::Add {
171 path,
172 value,
173 } => {
174 let names_position = path.last().is_some_and(|last| {
180 last.as_str() == "-" || Operation::array_index(last.as_str()).is_some()
181 });
182 if !names_position
183 && matches!(locate(&mut this, &path), Located::Found(Value::Array(_)))
184 {
185 this.inc(&Operation::path_to_parts(&path), value)?;
186 } else {
187 write(&mut this, &path, value, Write::Insert)?;
188 }
189 }
190 Operation::Remove {
192 path,
193 } => {
194 let path = Operation::path_to_parts(&path);
195 this.cut(&path);
196 }
197 Operation::Replace {
199 path,
200 value,
201 } => write(&mut this, &path, value, Write::Overwrite)?,
202 Operation::Change {
204 path,
205 value,
206 } => {
207 let parts = Operation::path_to_parts(&path);
208 if let Value::String(p) = value
209 && let Value::String(v) = this.pick(&parts)
210 {
211 let dmp = dmp::new();
212 let pch = dmp.patch_from_text(p.into_string()).map_err(|e| {
213 Error::InvalidPatch(PatchError {
214 message: format!("{e:?}"),
215 })
216 })?;
217 let (txt, _) = dmp.patch_apply(&pch, v.as_str()).map_err(|e| {
218 Error::InvalidPatch(PatchError {
219 message: format!("{e:?}"),
220 })
221 })?;
222 let txt = txt.into_iter().collect::<String>();
223 write(&mut this, &path, Value::from(txt), Write::Overwrite)?;
224 }
225 }
226 Operation::Copy {
228 path,
229 from,
230 } => {
231 let val = this.pick(&Operation::path_to_parts(&from));
232 write(&mut this, &path, val, Write::Insert)?;
233 }
234 Operation::Move {
236 path,
237 from,
238 } => {
239 let from = Operation::path_to_parts(&from);
244 let val = this.pick(&from);
245 this.cut(&from);
246 write(&mut this, &path, val, Write::Insert)?;
247 }
248 Operation::Test {
250 path,
251 value,
252 } => {
253 let path = Operation::path_to_parts(&path);
254 let val = this.pick(&path);
255 ensure!(
256 value == val,
257 Error::PatchTest {
258 expected: value.to_sql(),
259 got: val.to_sql(),
260 }
261 );
262 }
263 }
264 }
265 normalize_sets(&mut this);
266 *self = this;
267 Ok(())
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use crate::expr::Operation;
275 use crate::syn;
276
277 macro_rules! parse_val {
278 ($input:expr) => {
279 crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
280 };
281 }
282
283 #[tokio::test]
284 async fn patch_add_simple() {
285 let mut val = parse_val!("{ test: { other: null, something: 123 } }");
286 let ops = parse_val!("[{ op: 'add', path: '/temp', value: true }]");
287 let res = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
288 val.patch(ops).unwrap();
289 assert_eq!(res, val);
290 }
291
292 #[tokio::test]
293 async fn patch_remove_simple() {
294 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
295 let ops = parse_val!("[{ op: 'remove', path: '/temp' }]");
296 let res = parse_val!("{ test: { other: null, something: 123 } }");
297 val.patch(ops).unwrap();
298 assert_eq!(res, val);
299 }
300
301 #[tokio::test]
302 async fn patch_replace_simple() {
303 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
304 let ops = parse_val!("[{ op: 'replace', path: '/temp', value: 'text' }]");
305 let res = parse_val!("{ test: { other: null, something: 123 }, temp: 'text' }");
306 val.patch(ops).unwrap();
307 assert_eq!(res, val);
308 }
309
310 #[tokio::test]
311 async fn patch_change_simple() {
312 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: 'test' }");
313 let ops = parse_val!(
314 "[{ op: 'change', path: '/temp', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
315 );
316 let res = parse_val!("{ test: { other: null, something: 123 }, temp: 'text' }");
317 val.patch(ops).unwrap();
318 assert_eq!(res, val);
319 }
320
321 #[tokio::test]
322 async fn patch_copy_simple() {
323 let mut val = parse_val!("{ test: 123, temp: true }");
324 let ops = parse_val!("[{ op: 'copy', path: '/temp', from: '/test' }]");
325 let res = parse_val!("{ test: 123, temp: 123 }");
326 val.patch(ops).unwrap();
327 assert_eq!(res, val);
328 }
329
330 #[tokio::test]
331 async fn patch_move_simple() {
332 let mut val = parse_val!("{ temp: true, some: 123 }");
333 let ops = parse_val!("[{ op: 'move', path: '/other', from: '/temp' }]");
334 let res = parse_val!("{ other: true, some: 123 }");
335 val.patch(ops).unwrap();
336 assert_eq!(res, val);
337 }
338
339 #[tokio::test]
340 async fn patch_test_simple() {
341 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
342 let ops = parse_val!(
343 "[{ op: 'remove', path: '/test/something' }, { op: 'test', path: '/temp', value: true }]"
344 );
345 let res = parse_val!("{ test: { other: 'test' }, temp: true }");
346 val.patch(ops).unwrap();
347 assert_eq!(res, val);
348 }
349
350 #[tokio::test]
351 async fn patch_add_embedded() {
352 let mut val = parse_val!("{ test: { other: null, something: 123 } }");
353 let ops = parse_val!("[{ op: 'add', path: '/temp/test', value: true }]");
354 let res = parse_val!("{ test: { other: null, something: 123 }, temp: { test: true } }");
355 val.patch(ops).unwrap();
356 assert_eq!(res, val);
357 }
358
359 #[tokio::test]
360 async fn patch_remove_embedded() {
361 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
362 let ops = parse_val!("[{ op: 'remove', path: '/test/other' }]");
363 let res = parse_val!("{ test: { something: 123 }, temp: true }");
364 val.patch(ops).unwrap();
365 assert_eq!(res, val);
366 }
367
368 #[tokio::test]
369 async fn patch_remove_array_index() {
370 let mut val = parse_val!("{ id: todo:1 }");
371 let add = parse_val!("[{ op: 'add', path: '/list', value: ['Item here'] }]");
372 let remove = parse_val!("[{ op: 'remove', path: '/list/0' }]");
373 let res = parse_val!("{ id: todo:1, list: [] }");
374 val.patch(add).unwrap();
375 val.patch(remove).unwrap();
376 assert_eq!(res, val);
377 }
378
379 #[tokio::test]
380 async fn patch_add_array_index_append_at_length() {
381 let mut val = parse_val!("{ list: ['a', 'b'] }");
383 let ops = parse_val!("[{ op: 'add', path: '/list/2', value: 'c' }]");
384 let res = parse_val!("{ list: ['a', 'b', 'c'] }");
385 val.patch(ops).unwrap();
386 assert_eq!(res, val);
387 }
388
389 #[tokio::test]
390 async fn patch_add_array_index_out_of_bounds_errors() {
391 let mut val = parse_val!("{ list: ['a', 'b'] }");
393 let ops = parse_val!("[{ op: 'add', path: '/list/5', value: 'c' }]");
394 let err = val.patch(ops).unwrap_err();
395 let msg = err.to_string();
396 assert!(
397 msg.contains("index 5 is out of bounds for array of length 2"),
398 "unexpected error message: {msg}"
399 );
400 assert_eq!(val, parse_val!("{ list: ['a', 'b'] }"));
402 }
403
404 #[tokio::test]
405 async fn patch_replace_embedded() {
406 let mut val = parse_val!("{ test: { other: null, something: 123 }, temp: true }");
407 let ops = parse_val!("[{ op: 'replace', path: '/test/other', value: 'text' }]");
408 let res = parse_val!("{ test: { other: 'text', something: 123 }, temp: true }");
409 val.patch(ops).unwrap();
410 assert_eq!(res, val);
411 }
412
413 #[tokio::test]
414 async fn patch_change_embedded() {
415 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
416 let ops = parse_val!(
417 "[{ op: 'change', path: '/test/other', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
418 );
419 let res = parse_val!("{ test: { other: 'text', something: 123 }, temp: true }");
420 val.patch(ops).unwrap();
421 assert_eq!(res, val);
422 }
423
424 #[tokio::test]
425 async fn patch_copy_embedded() {
426 let mut val = parse_val!("{ test: { other: null }, temp: 123 }");
427 let ops = parse_val!("[{ op: 'copy', path: '/test/other', from: '/temp' }]");
428 let res = parse_val!("{ test: { other: 123 }, temp: 123 }");
429 val.patch(ops).unwrap();
430 assert_eq!(res, val);
431 }
432
433 #[tokio::test]
434 async fn patch_move_embedded() {
435 let mut val = parse_val!("{ test: { other: ':3', some: 123 }}");
436 let ops = parse_val!("[{ op: 'move', path: '/temp', from: '/test/other' }]");
437 let res = parse_val!("{ test: { some: 123 }, temp: ':3' }");
438 val.patch(ops).unwrap();
439 assert_eq!(res, val);
440 }
441
442 #[tokio::test]
443 async fn patch_test_embedded() {
444 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
445 let ops = parse_val!(
446 "[{ op: 'remove', path: '/test/other' }, { op: 'test', path: '/test/something', value: 123 }]"
447 );
448 let res = parse_val!("{ test: { something: 123 }, temp: true }");
449 val.patch(ops).unwrap();
450 assert_eq!(res, val);
451 }
452
453 #[tokio::test]
454 async fn patch_change_invalid() {
455 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
457 let ops = parse_val!("[{ op: 'change', path: '/test/other', value: 'text' }]");
458 assert!(val.patch(ops).is_err());
459 }
460
461 #[tokio::test]
462 async fn patch_test_invalid() {
463 let mut val = parse_val!("{ test: { other: 'test', something: 123 }, temp: true }");
464 let should = val.clone();
465 let ops = parse_val!(
466 "[{ op: 'remove', path: '/test/other' }, { op: 'test', path: '/test/something', value: 'not same' }]"
467 );
468 assert!(val.patch(ops).is_err());
469 assert_eq!(val, should);
471 }
472
473 #[tokio::test]
474 async fn patch_change_root() {
475 let mut val = parse_val!("'Hello'");
477 let ops = parse_val!(
478 "[{ op: 'change', path: '', value: '@@ -1,5 +1,12 @@\n Hello\n+ there!\n' }]"
479 );
480 val.patch(ops).unwrap();
481 assert_eq!(val, parse_val!("'Hello there!'"));
482 }
483
484 #[tokio::test]
485 async fn patch_replace_root() {
486 let mut val = parse_val!("1");
487 let ops = parse_val!("[{ op: 'replace', path: '', value: 2 }]");
488 val.patch(ops).unwrap();
489 assert_eq!(val, parse_val!("2"));
490 }
491
492 #[tokio::test]
493 async fn patch_diff_roundtrip_string_root() {
494 let mut start = parse_val!("'Hello'");
495 let after = parse_val!("'Hello there!'");
496 let ops = Operation::operations_to_value(start.diff(&after));
497 start.patch(ops).unwrap();
498 assert_eq!(start, after);
499 }
500
501 #[tokio::test]
502 async fn patch_replace_array_element_leaf() {
503 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
504 let ops = parse_val!("[{ op: 'replace', path: '/details/0/isbn', value: 'z' }]");
505 let res = parse_val!("{ details: [{ isbn: 'z' }, { isbn: 'b' }] }");
506 val.patch(ops).unwrap();
507 assert_eq!(res, val);
508 }
509
510 #[tokio::test]
511 async fn patch_replace_whole_array_element() {
512 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
513 let ops = parse_val!("[{ op: 'replace', path: '/details/1', value: 'gone' }]");
514 let res = parse_val!("{ details: [{ isbn: 'a' }, 'gone'] }");
515 val.patch(ops).unwrap();
516 assert_eq!(res, val);
517 }
518
519 #[tokio::test]
520 async fn patch_replace_nested_array_index() {
521 let mut val = parse_val!("{ a: [[1, 2], [3, 4]] }");
522 let ops = parse_val!("[{ op: 'replace', path: '/a/0/1', value: 99 }]");
523 let res = parse_val!("{ a: [[1, 99], [3, 4]] }");
524 val.patch(ops).unwrap();
525 assert_eq!(res, val);
526 }
527
528 #[tokio::test]
529 async fn patch_change_array_element() {
530 let mut val = parse_val!("{ tags: ['test', 'keep'] }");
531 let ops = parse_val!(
532 "[{ op: 'change', path: '/tags/0', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
533 );
534 let res = parse_val!("{ tags: ['text', 'keep'] }");
535 val.patch(ops).unwrap();
536 assert_eq!(res, val);
537 }
538
539 #[tokio::test]
540 async fn patch_copy_from_array_element() {
541 let mut val = parse_val!("{ list: ['p', 'q'] }");
542 let ops = parse_val!("[{ op: 'copy', path: '/first', from: '/list/0' }]");
543 let res = parse_val!("{ list: ['p', 'q'], first: 'p' }");
544 val.patch(ops).unwrap();
545 assert_eq!(res, val);
546 }
547
548 #[tokio::test]
549 async fn patch_move_from_array_element() {
550 let mut val = parse_val!("{ list: ['p', 'q'] }");
551 let ops = parse_val!("[{ op: 'move', path: '/moved', from: '/list/0' }]");
552 let res = parse_val!("{ list: ['q'], moved: 'p' }");
553 val.patch(ops).unwrap();
554 assert_eq!(res, val);
555 }
556
557 #[tokio::test]
558 async fn patch_test_array_element() {
559 let mut val = parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }");
560 let ops = parse_val!("[{ op: 'test', path: '/rows/1/n', value: 2 }]");
561 val.patch(ops).unwrap();
562 assert_eq!(val, parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }"));
563
564 let mut val = parse_val!("{ rows: [{ n: 1 }, { n: 2 }] }");
565 let ops = parse_val!("[{ op: 'test', path: '/rows/1/n', value: 1 }]");
566 assert!(val.patch(ops).is_err());
567 }
568
569 #[tokio::test]
570 async fn patch_add_intermediate_array_index() {
571 let mut val = parse_val!("{ details: [{ isbn: 'a' }, { isbn: 'b' }] }");
572 let ops = parse_val!("[{ op: 'add', path: '/details/0/title', value: 'T' }]");
573 let res = parse_val!("{ details: [{ isbn: 'a', title: 'T' }, { isbn: 'b' }] }");
574 val.patch(ops).unwrap();
575 assert_eq!(res, val);
576 }
577
578 #[tokio::test]
579 async fn patch_add_array_index_under_array_index() {
580 let mut val = parse_val!("{ a: [['x']] }");
583 let ops = parse_val!("[{ op: 'add', path: '/a/0/1', value: 'y' }]");
584 val.patch(ops).unwrap();
585 assert_eq!(val, parse_val!("{ a: [['x', 'y']] }"));
586
587 let mut val = parse_val!("{ a: [['x']] }");
588 let ops = parse_val!("[{ op: 'add', path: '/a/0/5', value: 'y' }]");
589 let err = val.patch(ops).unwrap_err();
590 assert!(
591 err.to_string().contains("index 5 is out of bounds for array of length 1"),
592 "unexpected error message: {err}"
593 );
594 assert_eq!(val, parse_val!("{ a: [['x']] }"));
595 }
596
597 #[tokio::test]
598 async fn patch_object_member_named_as_an_index() {
599 let mut val = parse_val!("{ m: { '0': 'zero', '1': 'one' } }");
602 let ops = parse_val!(
603 "[{ op: 'replace', path: '/m/0', value: 'Z' }, { op: 'remove', path: '/m/1' }]"
604 );
605 let res = parse_val!("{ m: { '0': 'Z' } }");
606 val.patch(ops).unwrap();
607 assert_eq!(res, val);
608 }
609
610 #[tokio::test]
611 async fn patch_object_member_not_spelled_as_an_index() {
612 let mut val =
615 parse_val!("{ m: { '007': 'bond', '7': 'seven', '+3': 'plus', '-1': 'neg' } }");
616 let ops = parse_val!(
617 "[{ op: 'remove', path: '/m/007' }, { op: 'remove', path: '/m/+3' }, { op: 'replace', path: '/m/-1', value: 'N' }]"
618 );
619 let res = parse_val!("{ m: { '7': 'seven', '-1': 'N' } }");
620 val.patch(ops).unwrap();
621 assert_eq!(res, val);
622 }
623
624 #[tokio::test]
625 async fn patch_copy_into_array_position_inserts() {
626 let mut val = parse_val!("{ list: ['a', 'c'], v: 'b' }");
629 let ops = parse_val!("[{ op: 'copy', path: '/list/1', from: '/v' }]");
630 let res = parse_val!("{ list: ['a', 'b', 'c'], v: 'b' }");
631 val.patch(ops).unwrap();
632 assert_eq!(res, val);
633 }
634
635 #[tokio::test]
636 async fn patch_move_within_one_array() {
637 let mut val = parse_val!("{ list: ['a', 'b', 'c'] }");
640 let ops = parse_val!("[{ op: 'move', path: '/list/2', from: '/list/0' }]");
641 let res = parse_val!("{ list: ['b', 'c', 'a'] }");
642 val.patch(ops).unwrap();
643 assert_eq!(res, val);
644 }
645
646 #[tokio::test]
647 async fn patch_move_into_array_position_inserts() {
648 let mut val = parse_val!("{ list: ['a', 'c'], v: 'b' }");
649 let ops = parse_val!("[{ op: 'move', path: '/list/1', from: '/v' }]");
650 let res = parse_val!("{ list: ['a', 'b', 'c'] }");
651 val.patch(ops).unwrap();
652 assert_eq!(res, val);
653 }
654
655 #[tokio::test]
656 async fn patch_append_token_appends_or_names_a_member() {
657 let mut val = parse_val!("{ list: ['a'], v: 'b' }");
660 let ops = parse_val!(
661 "[{ op: 'add', path: '/list/-', value: 'x' }, { op: 'copy', path: '/list/-', from: '/v' }]"
662 );
663 val.patch(ops).unwrap();
664 assert_eq!(val, parse_val!("{ list: ['a', 'x', 'b'], v: 'b' }"));
665 let mut val = parse_val!("{ m: { a: 1 } }");
667 let ops = parse_val!("[{ op: 'add', path: '/m/-', value: 2 }]");
668 val.patch(ops).unwrap();
669 assert_eq!(val, parse_val!("{ m: { a: 1, '-': 2 } }"));
670 }
671
672 #[tokio::test]
673 async fn patch_overwrite_needs_a_position_that_exists() {
674 for pointer in ["/list/9", "/list/2", "/list/-"] {
677 let mut val = parse_val!("{ list: ['a', 'b'] }");
678 let ops = parse_val!(&format!("[{{ op: 'replace', path: '{pointer}', value: 'z' }}]"));
679 let err = val.patch(ops).unwrap_err();
680 assert!(
681 err.to_string().contains("is out of bounds for array of length 2"),
682 "unexpected error for {pointer}: {err}"
683 );
684 assert_eq!(val, parse_val!("{ list: ['a', 'b'] }"));
685 }
686 }
687
688 #[tokio::test]
689 async fn patch_add_index_on_a_non_array_names_a_member() {
690 let mut val = parse_val!("{ m: { x: 1 } }");
693 let ops = parse_val!("[{ op: 'add', path: '/m/0', value: 5 }]");
694 val.patch(ops).unwrap();
695 assert_eq!(val, parse_val!("{ m: { x: 1, '0': 5 } }"));
696 }
697
698 #[tokio::test]
699 async fn patch_set_keeps_its_invariant() {
700 let mut val = parse_val!("{ s: { 'b', 'c', } }");
704 val.patch(parse_val!("[{ op: 'replace', path: '/s/0', value: 'z' }]")).unwrap();
705 assert_eq!(val, parse_val!("{ s: { 'c', 'z', } }"));
706
707 let mut val = parse_val!("{ s: { 'b', 'c', } }");
709 val.patch(parse_val!("[{ op: 'replace', path: '/s/0', value: 'c' }]")).unwrap();
710 assert_eq!(val, parse_val!("{ s: { 'c', } }"));
711
712 let mut val = parse_val!("{ s: { { n: 'zz' }, { n: 'aa' }, } }");
715 val.patch(parse_val!("[{ op: 'replace', path: '/s/0/n', value: 'zz' }]")).unwrap();
716 assert_eq!(val, parse_val!("{ s: { { n: 'zz' }, } }"));
717
718 let mut val = parse_val!("{ s: { [{ inner: { 'q', 'p', } }], } }");
720 val.patch(parse_val!("[{ op: 'replace', path: '/s/0/0/inner', value: { 'y', 'x', } }]"))
721 .unwrap();
722 assert_eq!(val, parse_val!("{ s: { [{ inner: { 'x', 'y', } }], } }"));
723 }
724
725 #[tokio::test]
726 async fn patch_position_under_a_broadcast_path_is_refused() {
727 for op in [
732 "{ op: 'replace', path: '/list/name/0', value: 'X' }",
733 "{ op: 'add', path: '/list/name/0', value: 'X' }",
734 "{ op: 'copy', path: '/list/name/0', from: '/v' }",
735 ] {
736 let mut val = parse_val!("{ list: [{ name: 'AA' }, { name: 'BB' }], v: 'X' }");
737 let err = val.patch(parse_val!(&format!("[{op}]"))).unwrap_err();
738 assert!(
739 err.to_string().contains("reaches every element of a sequence"),
740 "unexpected error for {op}: {err}"
741 );
742 assert_eq!(val, parse_val!("{ list: [{ name: 'AA' }, { name: 'BB' }], v: 'X' }"));
743 }
744 }
745
746 #[tokio::test]
747 async fn patch_move_keeps_its_source_when_the_destination_is_refused() {
748 let mut val = parse_val!("{ list: [{ name: 'AA' }], v: 'keep' }");
752 let ops = parse_val!("[{ op: 'move', path: '/list/name/0', from: '/v' }]");
753 assert!(val.patch(ops).is_err());
754 assert_eq!(val, parse_val!("{ list: [{ name: 'AA' }], v: 'keep' }"));
755 }
756
757 #[tokio::test]
758 async fn patch_destination_inside_the_source_is_refused() {
759 for op in [
762 "{ op: 'copy', path: '/a/-', from: '/a' }",
763 "{ op: 'copy', path: '/a/0/b', from: '/a' }",
764 "{ op: 'move', path: '/a/b', from: '/a' }",
765 ] {
766 let mut val = parse_val!("{ a: [1, 2] }");
767 let err = val.patch(parse_val!(&format!("[{op}]"))).unwrap_err();
768 assert!(
769 err.to_string().contains("that is not inside 'from'"),
770 "unexpected error for {op}: {err}"
771 );
772 assert_eq!(val, parse_val!("{ a: [1, 2] }"));
773 }
774 let mut val = parse_val!("{ a: [1, 2], ab: 'x' }");
776 val.patch(parse_val!("[{ op: 'copy', path: '/ab', from: '/a' }]")).unwrap();
777 assert_eq!(val, parse_val!("{ a: [1, 2], ab: [1, 2] }"));
778 }
779
780 #[tokio::test]
781 async fn patch_diff_roundtrip_array_element() {
782 for (a, b) in [
783 ("{ list: [1, 2, 3] }", "{ list: [1, 9, 3] }"),
784 ("{ list: ['aa', 'bb'] }", "{ list: ['aa', 'bx'] }"),
785 ("{ list: [{ n: 1 }, { n: 2 }] }", "{ list: [{ n: 1 }, { n: 7 }] }"),
786 ("{ list: [1] }", "{ list: [1, 2, 3] }"),
787 ("{ list: [1, 2, 3] }", "{ list: [1] }"),
788 ("{ list: [1, 2, 3, 4] }", "{ list: [9] }"),
789 ("{ list: [{ n: 1 }, { n: 2 }, { n: 3 }] }", "{ list: [{ n: 5 }] }"),
790 ] {
791 let mut start = parse_val!(a);
792 let after = parse_val!(b);
793 let ops = Operation::operations_to_value(start.diff(&after));
794 start.patch(ops).unwrap();
795 assert_eq!(start, after, "diff of {a} -> {b} did not round-trip");
796 }
797 }
798
799 #[tokio::test]
800 async fn patch_diff_roundtrip_number_root() {
801 let mut start = parse_val!("1");
802 let after = parse_val!("2");
803 let ops = Operation::operations_to_value(start.diff(&after));
804 start.patch(ops).unwrap();
805 assert_eq!(start, after);
806 }
807}