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		let operations = Operation::value_to_operations(ops)
163			.map_err(Error::InvalidPatch)
164			.map_err(anyhow::Error::new)?;
165
166		// SECURITY: applying an operation walks `Value::put` / `cut` / `pick` once
167		// per path segment, and those are natively recursive with no `Stk`. A
168		// JSON-Pointer path is split from an arbitrary string with no length
169		// bound, so a path of a few thousand segments overflows the host stack and
170		// aborts the process (`panic = "abort"`) while the value is being built -
171		// before `check_value_depth`, which inspects a finished value, could ever
172		// see it. Bound the path to the value depth cap here, matching the depth a
173		// patch could otherwise build, so an over-long path is rejected up front.
174		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		// Loop over the patch operations and apply them
184		for operation in operations {
185			match operation {
186				// Add a value
187				Operation::Add {
188					path,
189					value,
190				} => {
191					// A last token naming a position points inside a sequence.
192					// Anything else points at the location itself, and one
193					// holding an array is appended to rather than replaced:
194					// this dialect reads `add` at an array-valued field as a
195					// push.
196					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				// Remove a value at the specified path
208				Operation::Remove {
209					path,
210				} => {
211					let path = Operation::path_to_parts(&path);
212					this.cut(&path);
213				}
214				// Replace a value at the specified path
215				Operation::Replace {
216					path,
217					value,
218				} => write(&mut this, &path, value, Write::Overwrite)?,
219				// Modify a string at the specified path
220				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				// Copy a value from one field to another
244				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				// Move a value from one field to another
252				Operation::Move {
253					path,
254					from,
255				} => {
256					// RFC 6902 §4.4 defines a `move` as a `remove` at `from`
257					// followed by an `add` at `path`. The source goes first so
258					// that a destination inside the same array counts positions
259					// in the array the removal left behind.
260					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				// Test whether a value matches another value
266				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		// Everything ok
285		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	// Applying a patch op walks `put` / `cut` / `pick` once per path segment, so a
310	// JSON-Pointer path longer than the value depth cap is rejected before it can
311	// overflow the stack. The path is split from an arbitrary string, so its
312	// length is caller-controlled.
313	#[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		// Failed closed: a rejected patch leaves the value untouched.
325		assert_eq!(val, before);
326	}
327
328	// The `from` pointer of copy/move drives the same recursion through `pick`, so
329	// it is bounded on the same footing as `path`.
330	#[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	// A path exactly at the cap still applies: its recursion is bounded and safe,
343	// and the resulting value sits at the same depth the storage guard allows.
344	#[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		// RFC 6902 §4.1: an index equal to the array length appends.
445		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		// RFC 6902 §4.1: an index greater than the array length is invalid.
455		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		// The value must be unchanged when a patch op fails.
464		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		// See https://github.com/surrealdb/surrealdb/issues/2001
519		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		// It is important to test if patches applied even if test operation fails
533		assert_eq!(val, should);
534	}
535
536	#[tokio::test]
537	async fn patch_change_root() {
538		// Issue 7239: empty-path patch ops must target the root value.
539		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		// The bounds check applies to the last token once the intermediate
644		// index has selected the array it counts against.
645		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		// An index-spelled token names the member of that spelling on an
663		// object, for every operation.
664		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		// `007`, `+3` and `-1` are member names, not indexes: RFC 6901 §4
676		// spells an index as `0` or `[1-9][0-9]*` and nothing else.
677		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		// RFC 6902 §4.5: a `copy` destination is an `add`, so a position in an
690		// array is one the value takes rather than one it overwrites.
691		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		// The source is removed before the destination is written, so the
701		// position counts against the array the removal left behind.
702		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		// `-` names the position past the last element of an array, for every
721		// operation whose destination is an `add`.
722		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		// Against an object it is an ordinary member name.
729		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		// A `replace` reaching past the last element of an array changes
738		// nothing, so it is refused rather than reported as applied.
739		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		// The parent holds no array, so the token is a member name and the
754		// parent keeps everything else it holds.
755		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		// A set is sorted and deduplicated. A write reaching one by position
764		// lands on the element at that position and the set is rebuilt around
765		// it, so what is stored is always a set again.
766		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		// A write that makes two elements equal leaves one of them.
771		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		// A pointer reaching through an element to a member of it re-sorts the
776		// set around the element it changed.
777		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		// A set nested inside an array inside a set is rebuilt too.
782		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		// `/list/name` reaches the `name` of every element rather than one
791		// location, so a position counted against it names nothing to write.
792		// Applying it to each element in turn would write the same value into
793		// all of them, which is the corruption a position is meant to avoid.
794		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		// The source is removed before the destination is written, so a
812		// destination that cannot be written must fail the whole patch rather
813		// than report success having dropped the value.
814		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		// The destination lies inside the value being read, so each application
823		// would nest another snapshot of the source inside itself.
824		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		// A pointer that merely shares a prefix is not inside the source.
838		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}