1mod errors;
30mod macros;
31
32use json_patch::patch;
33#[doc(no_inline)]
36pub use json_patch::{
37 AddOperation,
38 CopyOperation,
39 MoveOperation,
40 Patch,
41 PatchOperation,
42 RemoveOperation,
43 ReplaceOperation,
44 TestOperation,
45};
46#[doc(no_inline)]
47use jsonptr::index::Index;
48use jsonptr::Token;
49pub use jsonptr::{
50 Pointer,
51 PointerBuf,
52};
53use serde_json::{
54 json,
55 Value,
56};
57
58pub use crate::errors::PatchError;
59
60pub mod prelude {
61 pub use super::{
62 add_operation,
63 copy_operation,
64 escape,
65 format_ptr,
66 move_operation,
67 patch_ext,
68 remove_operation,
69 replace_operation,
70 test_operation,
71 AddOperation,
72 CopyOperation,
73 MoveOperation,
74 Patch,
75 PatchError,
76 PatchOperation,
77 Pointer,
78 PointerBuf,
79 RemoveOperation,
80 ReplaceOperation,
81 TestOperation,
82 };
83}
84
85#[derive(Debug, Clone, Copy)]
87enum PatchMode {
88 Error,
89 Create,
90 Skip,
91}
92
93pub fn add_operation(path: PointerBuf, value: Value) -> PatchOperation {
94 PatchOperation::Add(AddOperation { path, value })
95}
96
97pub fn copy_operation(from: PointerBuf, path: PointerBuf) -> PatchOperation {
98 PatchOperation::Copy(CopyOperation { from, path })
99}
100
101pub fn move_operation(from: PointerBuf, path: PointerBuf) -> PatchOperation {
102 PatchOperation::Move(MoveOperation { from, path })
103}
104
105pub fn remove_operation(path: PointerBuf) -> PatchOperation {
106 PatchOperation::Remove(RemoveOperation { path })
107}
108
109pub fn replace_operation(path: PointerBuf, value: Value) -> PatchOperation {
110 PatchOperation::Replace(ReplaceOperation { path, value })
111}
112
113pub fn test_operation(path: PointerBuf, value: Value) -> PatchOperation {
114 PatchOperation::Test(TestOperation { path, value })
115}
116
117pub fn escape(input: &str) -> String {
118 Token::new(input).encoded().into()
119}
120
121pub fn patch_ext(obj: &mut Value, p: PatchOperation) -> Result<(), PatchError> {
122 match p {
123 PatchOperation::Add(op) => add_or_replace(obj, &op.path, &op.value, false)?,
124 PatchOperation::Remove(op) => remove(obj, &op.path)?,
125 PatchOperation::Replace(op) => add_or_replace(obj, &op.path, &op.value, true)?,
126 x => patch(obj, &[x])?,
127 }
128 Ok(())
129}
130
131fn add_or_replace(obj: &mut Value, path: &PointerBuf, value: &Value, replace: bool) -> Result<(), PatchError> {
132 let Some((subpath, tail)) = path.split_back() else {
133 return Ok(());
134 };
135
136 let mode = if replace { PatchMode::Error } else { PatchMode::Create };
139 for v in patch_ext_helper(subpath, obj, mode)? {
140 match v {
141 Value::Object(map) => {
142 let key = tail.decoded().into();
143 if replace && !map.contains_key(&key) {
144 return Err(PatchError::TargetDoesNotExist(path.as_str().into()));
145 }
146 map.insert(key, value.clone());
147 },
148 Value::Array(vec) => match tail.to_index()? {
149 Index::Num(idx) => {
150 vec.get(idx).ok_or(PatchError::OutOfBounds(idx))?;
151 if replace {
152 vec[idx] = value.clone();
153 } else {
154 vec.insert(idx, value.clone());
155 }
156 },
157 Index::Next => {
158 vec.push(value.clone());
159 },
160 },
161 _ => {
162 return Err(PatchError::UnexpectedType(path.as_str().into()));
163 },
164 }
165 }
166
167 Ok(())
168}
169
170fn remove(obj: &mut Value, path: &PointerBuf) -> Result<(), PatchError> {
171 let Some((subpath, key)) = path.split_back() else {
172 return Ok(());
173 };
174
175 for v in patch_ext_helper(subpath, obj, PatchMode::Skip)? {
176 v.as_object_mut()
177 .ok_or(PatchError::UnexpectedType(subpath.as_str().into()))?
178 .remove(key.decoded().as_ref());
179 }
180
181 Ok(())
182}
183
184fn patch_ext_helper<'a>(
187 path: &Pointer,
188 value: &'a mut Value,
189 mode: PatchMode,
190) -> Result<Vec<&'a mut Value>, PatchError> {
191 let Some(idx) = path.as_str().find("/*") else {
192 if path.resolve(value).is_err() {
193 match mode {
194 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
195 PatchMode::Create => {
196 path.assign(value, json!({}))?;
197 },
198 PatchMode::Skip => return Ok(vec![]),
199 }
200 }
201 return Ok(vec![path.resolve_mut(value)?]);
202 };
203
204 let (head, cons) = path.split_at(idx).unwrap();
206 let mut res = vec![];
207
208 if head.resolve(value).is_err() {
212 match mode {
213 PatchMode::Error => return Err(PatchError::TargetDoesNotExist(path.as_str().into())),
214 PatchMode::Create => {
215 path.assign(value, json!([]))?;
216 },
217 PatchMode::Skip => return Ok(vec![]),
218 }
219 }
220 let next_array_val =
221 head.resolve_mut(value)?.as_array_mut().ok_or(PatchError::UnexpectedType(head.as_str().into()))?;
222 for v in next_array_val {
223 if let Some((_, c)) = cons.split_front() {
224 res.extend(patch_ext_helper(c, v, mode)?);
225 } else {
226 res.push(v);
227 }
228 }
229 Ok(res)
230}
231
232#[cfg(test)]
233mod tests {
234 use assertables::*;
235 use rstest::*;
236
237 use super::*;
238 use crate as json_patch_ext; #[fixture]
241 fn data() -> Value {
242 json!({
243 "foo": [
244 {"baz": {"buzz": 0}},
245 {"baz": {"quzz": 1}},
246 {"baz": {"fixx": 2}},
247 ],
248 })
249 }
250
251 #[rstest]
252 fn test_patch_ext_add(mut data: Value) {
253 let path = format_ptr!("/foo/*/baz/buzz");
254 let res = patch_ext(&mut data, add_operation(path, json!(42)));
255 assert_ok!(res);
256 assert_eq!(
257 data,
258 json!({
259 "foo": [
260 {"baz": {"buzz": 42 }},
261 {"baz": {"quzz": 1, "buzz": 42}},
262 {"baz": {"fixx": 2, "buzz": 42}},
263 ],
264 })
265 );
266 }
267
268 #[rstest]
269 fn test_patch_ext_add_escaped() {
270 let path = format_ptr!("/foo/bar/{}", escape("testing.sh/baz"));
271 let mut data = json!({});
272 let res = patch_ext(&mut data, add_operation(path, json!(42)));
273 assert_ok!(res);
274 assert_eq!(data, json!({"foo": {"bar": {"testing.sh/baz": 42}}}));
275 }
276
277 #[rstest]
278 fn test_patch_ext_replace(mut data: Value) {
279 let path = format_ptr!("/foo/*/baz");
280 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
281 assert_ok!(res);
282 assert_eq!(
283 data,
284 json!({
285 "foo": [
286 {"baz": 42},
287 {"baz": 42},
288 {"baz": 42},
289 ],
290 })
291 );
292 }
293
294 #[rstest]
295 fn test_patch_ext_replace_err(mut data: Value) {
296 let path = format_ptr!("/foo/*/baz/buzz");
297 let res = patch_ext(&mut data, replace_operation(path, json!(42)));
298 println!("{data:?}");
299 assert_err!(res);
300 }
301
302
303 #[rstest]
304 fn test_patch_ext_remove(mut data: Value) {
305 let path = format_ptr!("/foo/*/baz/quzz");
306 let res = patch_ext(&mut data, remove_operation(path));
307 assert_ok!(res);
308 assert_eq!(
309 data,
310 json!({
311 "foo": [
312 {"baz": {"buzz": 0}},
313 {"baz": {}},
314 {"baz": {"fixx": 2}},
315 ],
316 })
317 );
318 }
319}