1use std::collections::HashSet;
4
5use serde_json::Value;
6
7use crate::functions::{Function, custom_error};
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12defn!(JsonPatchFn, vec![arg!(any), arg!(array)], None);
18
19impl Function for JsonPatchFn {
20 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
21 self.signature.validate(args, ctx)?;
22
23 let mut result = args[0].clone();
25
26 let patch: json_patch::Patch = serde_json::from_value(args[1].clone())
27 .map_err(|e| custom_error(ctx, &format!("Invalid JSON Patch format: {}", e)))?;
28
29 json_patch::patch(&mut result, &patch)
30 .map_err(|e| custom_error(ctx, &format!("Failed to apply patch: {}", e)))?;
31
32 Ok(result)
33 }
34}
35
36defn!(JsonMergePatchFn, vec![arg!(any), arg!(any)], None);
42
43impl Function for JsonMergePatchFn {
44 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
45 self.signature.validate(args, ctx)?;
46
47 let mut result = args[0].clone();
48 json_patch::merge(&mut result, &args[1]);
49
50 Ok(result)
51 }
52}
53
54defn!(JsonDiffFn, vec![arg!(any), arg!(any)], None);
60
61impl Function for JsonDiffFn {
62 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
63 self.signature.validate(args, ctx)?;
64
65 let patch = json_patch::diff(&args[0], &args[1]);
66
67 let patch_json = serde_json::to_value(&patch)
68 .map_err(|e| custom_error(ctx, &format!("Failed to serialize patch: {}", e)))?;
69
70 Ok(patch_json)
71 }
72}
73
74pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
76 register_if_enabled(runtime, "json_patch", enabled, Box::new(JsonPatchFn::new()));
77 register_if_enabled(
78 runtime,
79 "json_merge_patch",
80 enabled,
81 Box::new(JsonMergePatchFn::new()),
82 );
83 register_if_enabled(runtime, "json_diff", enabled, Box::new(JsonDiffFn::new()));
84}
85
86#[cfg(test)]
87mod tests {
88 use crate::Runtime;
89 use serde_json::json;
90
91 fn setup_runtime() -> Runtime {
92 Runtime::builder()
93 .with_standard()
94 .with_all_extensions()
95 .build()
96 }
97
98 #[test]
99 fn test_json_patch_add() {
100 let runtime = setup_runtime();
101 let data = json!({"doc": {"a": 1}, "patch": [{"op": "add", "path": "/b", "value": 2}]});
102 let expr = runtime.compile("json_patch(doc, patch)").unwrap();
103 let result = expr.search(&data).unwrap();
104 let obj = result.as_object().unwrap();
105 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
106 assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
107 }
108
109 #[test]
110 fn test_json_patch_remove() {
111 let runtime = setup_runtime();
112 let data = json!({"doc": {"a": 1, "b": 2}, "patch": [{"op": "remove", "path": "/b"}]});
113 let expr = runtime.compile("json_patch(doc, patch)").unwrap();
114 let result = expr.search(&data).unwrap();
115 let obj = result.as_object().unwrap();
116 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
117 assert!(obj.get("b").is_none());
118 }
119
120 #[test]
121 fn test_json_patch_replace() {
122 let runtime = setup_runtime();
123 let data =
124 json!({"doc": {"a": 1}, "patch": [{"op": "replace", "path": "/a", "value": 99}]});
125 let expr = runtime.compile("json_patch(doc, patch)").unwrap();
126 let result = expr.search(&data).unwrap();
127 let obj = result.as_object().unwrap();
128 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 99);
129 }
130
131 #[test]
132 fn test_json_patch_multiple_ops() {
133 let runtime = setup_runtime();
134 let data = json!({
135 "doc": {"a": 1},
136 "patch": [
137 {"op": "add", "path": "/b", "value": 2},
138 {"op": "replace", "path": "/a", "value": 10}
139 ]
140 });
141 let expr = runtime.compile("json_patch(doc, patch)").unwrap();
142 let result = expr.search(&data).unwrap();
143 let obj = result.as_object().unwrap();
144 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 10);
145 assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
146 }
147
148 #[test]
149 fn test_json_merge_patch_simple() {
150 let runtime = setup_runtime();
151 let data = json!({"doc": {"a": 1, "b": 2}, "patch": {"b": 3, "c": 4}});
152 let expr = runtime.compile("json_merge_patch(doc, patch)").unwrap();
153 let result = expr.search(&data).unwrap();
154 let obj = result.as_object().unwrap();
155 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
156 assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 3);
157 assert_eq!(obj.get("c").unwrap().as_f64().unwrap() as i64, 4);
158 }
159
160 #[test]
161 fn test_json_merge_patch_remove_with_null() {
162 let runtime = setup_runtime();
163 let data = json!({"doc": {"a": 1, "b": 2}, "patch": {"b": null}});
164 let expr = runtime.compile("json_merge_patch(doc, patch)").unwrap();
165 let result = expr.search(&data).unwrap();
166 let obj = result.as_object().unwrap();
167 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
168 assert!(obj.get("b").is_none());
169 }
170
171 #[test]
172 fn test_json_merge_patch_nested() {
173 let runtime = setup_runtime();
174 let data = json!({"doc": {"a": {"x": 1}}, "patch": {"a": {"y": 2}}});
175 let expr = runtime.compile("json_merge_patch(doc, patch)").unwrap();
176 let result = expr.search(&data).unwrap();
177 let obj = result.as_object().unwrap();
178 let a = obj.get("a").unwrap().as_object().unwrap();
179 assert_eq!(a.get("x").unwrap().as_f64().unwrap() as i64, 1);
180 assert_eq!(a.get("y").unwrap().as_f64().unwrap() as i64, 2);
181 }
182
183 #[test]
184 fn test_json_diff_add() {
185 let runtime = setup_runtime();
186 let data = json!({"a": {"x": 1}, "b": {"x": 1, "y": 2}});
187 let expr = runtime.compile("json_diff(a, b)").unwrap();
188 let result = expr.search(&data).unwrap();
189 let arr = result.as_array().unwrap();
190 assert_eq!(arr.len(), 1);
191 let op = arr[0].as_object().unwrap();
192 assert_eq!(op.get("op").unwrap().as_str().unwrap(), "add");
193 assert_eq!(op.get("path").unwrap().as_str().unwrap(), "/y");
194 }
195
196 #[test]
197 fn test_json_diff_remove() {
198 let runtime = setup_runtime();
199 let data = json!({"a": {"x": 1, "y": 2}, "b": {"x": 1}});
200 let expr = runtime.compile("json_diff(a, b)").unwrap();
201 let result = expr.search(&data).unwrap();
202 let arr = result.as_array().unwrap();
203 assert_eq!(arr.len(), 1);
204 let op = arr[0].as_object().unwrap();
205 assert_eq!(op.get("op").unwrap().as_str().unwrap(), "remove");
206 assert_eq!(op.get("path").unwrap().as_str().unwrap(), "/y");
207 }
208
209 #[test]
210 fn test_json_diff_replace() {
211 let runtime = setup_runtime();
212 let data = json!({"a": {"x": 1}, "b": {"x": 2}});
213 let expr = runtime.compile("json_diff(a, b)").unwrap();
214 let result = expr.search(&data).unwrap();
215 let arr = result.as_array().unwrap();
216 assert_eq!(arr.len(), 1);
217 let op = arr[0].as_object().unwrap();
218 assert_eq!(op.get("op").unwrap().as_str().unwrap(), "replace");
219 assert_eq!(op.get("path").unwrap().as_str().unwrap(), "/x");
220 }
221
222 #[test]
223 fn test_json_diff_no_changes() {
224 let runtime = setup_runtime();
225 let data = json!({"a": {"x": 1}, "b": {"x": 1}});
226 let expr = runtime.compile("json_diff(a, b)").unwrap();
227 let result = expr.search(&data).unwrap();
228 let arr = result.as_array().unwrap();
229 assert_eq!(arr.len(), 0);
230 }
231
232 #[test]
233 fn test_json_diff_roundtrip() {
234 let runtime = setup_runtime();
236 let data = json!({"a": {"x": 1}, "b": {"x": 2, "y": 3}});
237
238 let diff_expr = runtime.compile("json_diff(a, b)").unwrap();
240 let diff_result = diff_expr.search(&data).unwrap();
241
242 let data_with_patch = json!({
244 "doc": {"x": 1},
245 "patch": diff_result
246 });
247 let patch_expr = runtime.compile("json_patch(doc, patch)").unwrap();
248 let patched = patch_expr.search(&data_with_patch).unwrap();
249
250 let obj = patched.as_object().unwrap();
252 assert_eq!(obj.get("x").unwrap().as_f64().unwrap() as i64, 2);
253 assert_eq!(obj.get("y").unwrap().as_f64().unwrap() as i64, 3);
254 }
255}