Skip to main content

surrealdb_expr/val/value/
patch.rs

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/// What a write does with the position its pointer's last reference token
9/// names inside an array.
10#[derive(Clone, Copy)]
11enum Write {
12	/// The value takes that position and the elements from it onwards move up
13	/// one. RFC 6902 §4.1 gives this to `add`, and §4.4 and §4.5 define the
14	/// destination of `move` and `copy` as an `add`, so all three share it.
15	Insert,
16	/// The value replaces the element occupying that position, which is what
17	/// `replace` and `change` mean.
18	Overwrite,
19}
20
21/// The one location a JSON Pointer's reference tokens lead to.
22enum Located<'a> {
23	/// The value stored there.
24	Found(&'a mut Value),
25	/// Nothing along the pointer holds a value yet.
26	Absent,
27	/// Every element of a sequence answers to the pointer, because a token
28	/// naming no position was followed through one. Such a pointer describes a
29	/// value in each element rather than one location, so a position counted
30	/// against it names something different in every element.
31	Many,
32}
33
34/// Follows `path` one container at a time to the single location it names.
35///
36/// Only steps whose meaning is unambiguous are taken: the member of an object,
37/// and the element at a position in an array. Reaching a sequence by a token
38/// that names no position stops the walk, because from there the pointer
39/// spreads across every element — the difference between selecting an element
40/// and broadcasting over all of them, which is what the untyped write path
41/// does with the same tokens.
42fn 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
64/// Restores the sort-and-dedup invariant of every set in `value`.
65///
66/// A write reaches a set element by the position it happens to sort into and
67/// leaves it where it was, so the set can come out unordered or holding two
68/// equal elements. Storage encodes a set as an indexed set and will not decode
69/// that shape, which costs the whole record rather than the one field, so the
70/// invariant is re-established over the finished value before it is committed.
71/// Sets nested inside an element are rebuilt first, since an element's own
72/// ordering decides where it sorts in the set holding it.
73fn 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
86/// Writes `value` at the location `path` points at.
87///
88/// The last reference token is resolved against whatever holds the parent
89/// location, because only that says what the token names. RFC 6901 §4 spells
90/// a position in an array as `0` or `[1-9][0-9]*`, and RFC 6902 adds `-` for
91/// the position one past the last element; against anything else — an object,
92/// or a location that holds nothing yet — every token is a member name.
93///
94/// A position must name somewhere the value can land, so it is bounded by the
95/// array: one past the last element for an insert, which is the append `-`
96/// also spells, and the last element itself for an overwrite. Writing outside
97/// that is refused rather than dropped, because a pointer that reaches no
98/// element would otherwise report success having changed nothing.
99///
100/// A position is refused outright when the parent pointer reaches every
101/// element of a sequence instead of one location: counting a position against
102/// each element in turn writes the same value into all of them, which is not
103/// what the pointer asks for.
104fn 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	// A token only needs the container to disambiguate it when it could name a
110	// position at all; `-` names the position past the last element.
111	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			// The parent holds no array, so the token is a member name.
152			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		// Create a new object for testing and patching
163		// Loop over the patch operations and apply them
164		for operation in Operation::value_to_operations(ops)
165			.map_err(Error::InvalidPatch)
166			.map_err(anyhow::Error::new)?
167		{
168			match operation {
169				// Add a value
170				Operation::Add {
171					path,
172					value,
173				} => {
174					// A last token naming a position points inside a sequence.
175					// Anything else points at the location itself, and one
176					// holding an array is appended to rather than replaced:
177					// this dialect reads `add` at an array-valued field as a
178					// push.
179					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				// Remove a value at the specified path
191				Operation::Remove {
192					path,
193				} => {
194					let path = Operation::path_to_parts(&path);
195					this.cut(&path);
196				}
197				// Replace a value at the specified path
198				Operation::Replace {
199					path,
200					value,
201				} => write(&mut this, &path, value, Write::Overwrite)?,
202				// Modify a string at the specified path
203				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				// Copy a value from one field to another
227				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				// Move a value from one field to another
235				Operation::Move {
236					path,
237					from,
238				} => {
239					// RFC 6902 §4.4 defines a `move` as a `remove` at `from`
240					// followed by an `add` at `path`. The source goes first so
241					// that a destination inside the same array counts positions
242					// in the array the removal left behind.
243					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				// Test whether a value matches another value
249				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		// Everything ok
268		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		// RFC 6902 §4.1: an index equal to the array length appends.
382		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		// RFC 6902 §4.1: an index greater than the array length is invalid.
392		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		// The value must be unchanged when a patch op fails.
401		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		// See https://github.com/surrealdb/surrealdb/issues/2001
456		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		// It is important to test if patches applied even if test operation fails
470		assert_eq!(val, should);
471	}
472
473	#[tokio::test]
474	async fn patch_change_root() {
475		// Issue 7239: empty-path patch ops must target the root value.
476		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		// The bounds check applies to the last token once the intermediate
581		// index has selected the array it counts against.
582		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		// An index-spelled token names the member of that spelling on an
600		// object, for every operation.
601		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		// `007`, `+3` and `-1` are member names, not indexes: RFC 6901 §4
613		// spells an index as `0` or `[1-9][0-9]*` and nothing else.
614		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		// RFC 6902 §4.5: a `copy` destination is an `add`, so a position in an
627		// array is one the value takes rather than one it overwrites.
628		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		// The source is removed before the destination is written, so the
638		// position counts against the array the removal left behind.
639		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		// `-` names the position past the last element of an array, for every
658		// operation whose destination is an `add`.
659		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		// Against an object it is an ordinary member name.
666		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		// A `replace` reaching past the last element of an array changes
675		// nothing, so it is refused rather than reported as applied.
676		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		// The parent holds no array, so the token is a member name and the
691		// parent keeps everything else it holds.
692		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		// A set is sorted and deduplicated. A write reaching one by position
701		// lands on the element at that position and the set is rebuilt around
702		// it, so what is stored is always a set again.
703		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		// A write that makes two elements equal leaves one of them.
708		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		// A pointer reaching through an element to a member of it re-sorts the
713		// set around the element it changed.
714		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		// A set nested inside an array inside a set is rebuilt too.
719		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		// `/list/name` reaches the `name` of every element rather than one
728		// location, so a position counted against it names nothing to write.
729		// Applying it to each element in turn would write the same value into
730		// all of them, which is the corruption a position is meant to avoid.
731		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		// The source is removed before the destination is written, so a
749		// destination that cannot be written must fail the whole patch rather
750		// than report success having dropped the value.
751		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		// The destination lies inside the value being read, so each application
760		// would nest another snapshot of the source inside itself.
761		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		// A pointer that merely shares a prefix is not inside the source.
775		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}