cjson_bindings/
cjson_utils.rs

1//! Safe Rust wrappers for cJSON_Utils library
2//!
3//! This module provides safe, idiomatic Rust interfaces over the cJSON_Utils C library,
4//! which implements RFC6901 (JSON Pointer), RFC6902 (JSON Patch), and RFC7386 (JSON Merge Patch).
5
6extern crate alloc;
7
8use alloc::ffi::CString;
9use alloc::string::String;
10use core::ffi::{CStr, c_char};
11
12use crate::cjson::{CJson, CJsonError, CJsonResult};
13use crate::cjson_ffi::cJSON;
14use crate::cjson_utils_ffi::*;
15
16/// JSON Pointer utilities (RFC6901)
17pub struct JsonPointer;
18
19impl JsonPointer {
20    /// Get a value from a JSON object using RFC6901 JSON Pointer syntax.
21    /// 
22    /// # Arguments
23    /// * `object` - The JSON object to search in
24    /// * `pointer` - The JSON Pointer string (e.g., "/foo/bar/0")
25    /// 
26    /// # Returns
27    /// A borrowed reference to the found item, or NotFound error
28    pub fn get(object: &CJson, pointer: &str) -> CJsonResult<CJsonRef> {
29        let c_pointer = CString::new(pointer).map_err(|_| CJsonError::InvalidUtf8)?;
30        let ptr = unsafe {
31            cJSONUtils_GetPointer(object.as_ptr() as *mut cJSON, c_pointer.as_ptr() as *const i8)
32        };
33        unsafe { CJsonRef::from_ptr(ptr) }.map_err(|_| CJsonError::NotFound)
34    }
35
36    /// Get a value from a JSON object using RFC6901 JSON Pointer syntax (case-sensitive).
37    /// 
38    /// # Arguments
39    /// * `object` - The JSON object to search in
40    /// * `pointer` - The JSON Pointer string (e.g., "/foo/bar/0")
41    /// 
42    /// # Returns
43    /// A borrowed reference to the found item, or NotFound error
44    pub fn get_case_sensitive(object: &CJson, pointer: &str) -> CJsonResult<CJsonRef> {
45        let c_pointer = CString::new(pointer).map_err(|_| CJsonError::InvalidUtf8)?;
46        let ptr = unsafe {
47            cJSONUtils_GetPointerCaseSensitive(
48                object.as_ptr() as *mut cJSON,
49                c_pointer.as_ptr() as *const i8,
50            )
51        };
52        unsafe { CJsonRef::from_ptr(ptr) }.map_err(|_| CJsonError::NotFound)
53    }
54
55    /// Find a JSON Pointer path from one object to a target value within it.
56    /// 
57    /// # Arguments
58    /// * `object` - The JSON object to search in
59    /// * `target` - The target value to find
60    /// 
61    /// # Returns
62    /// The JSON Pointer path as a String, or NotFound error
63    pub fn find_from_object_to(object: &CJson, target: &CJson) -> CJsonResult<String> {
64        let ptr = unsafe {
65            cJSONUtils_FindPointerFromObjectTo(object.as_ptr(), target.as_ptr())
66        };
67        if ptr.is_null() {
68            return Err(CJsonError::NotFound);
69        }
70        let path = unsafe { CStr::from_ptr(ptr as *const c_char).to_string_lossy().into_owned() };
71        unsafe { crate::cjson_ffi::cJSON_free(ptr as *mut core::ffi::c_void) };
72        Ok(path)
73    }
74}
75
76/// JSON Patch utilities (RFC6902)
77pub struct JsonPatch;
78
79impl JsonPatch {
80    /// Generate a JSON Patch (RFC6902) to transform 'from' into 'to'.
81    /// 
82    /// Note: This function modifies both 'from' and 'to' by sorting their keys.
83    /// 
84    /// # Arguments
85    /// * `from` - The original JSON object
86    /// * `to` - The target JSON object
87    /// 
88    /// # Returns
89    /// A new CJson object containing the patch operations
90    pub fn generate(from: &mut CJson, to: &mut CJson) -> CJsonResult<CJson> {
91        let ptr = unsafe {
92            cJSONUtils_GeneratePatches(from.as_mut_ptr(), to.as_mut_ptr())
93        };
94        unsafe { CJson::from_ptr(ptr) }
95    }
96
97    /// Generate a JSON Patch (RFC6902) to transform 'from' into 'to' (case-sensitive).
98    /// 
99    /// Note: This function modifies both 'from' and 'to' by sorting their keys.
100    /// 
101    /// # Arguments
102    /// * `from` - The original JSON object
103    /// * `to` - The target JSON object
104    /// 
105    /// # Returns
106    /// A new CJson object containing the patch operations
107    pub fn generate_case_sensitive(from: &mut CJson, to: &mut CJson) -> CJsonResult<CJson> {
108        let ptr = unsafe {
109            cJSONUtils_GeneratePatchesCaseSensitive(from.as_mut_ptr(), to.as_mut_ptr())
110        };
111        unsafe { CJson::from_ptr(ptr) }
112    }
113
114    /// Apply a JSON Patch (RFC6902) to an object.
115    /// 
116    /// # Arguments
117    /// * `object` - The JSON object to patch
118    /// * `patches` - The patch operations to apply
119    /// 
120    /// # Returns
121    /// Ok(()) on success, or an error
122    pub fn apply(object: &mut CJson, patches: &CJson) -> CJsonResult<()> {
123        let result = unsafe {
124            cJSONUtils_ApplyPatches(object.as_mut_ptr(), patches.as_ptr())
125        };
126        if result == 0 {
127            Ok(())
128        } else {
129            Err(CJsonError::InvalidOperation)
130        }
131    }
132
133    /// Apply a JSON Patch (RFC6902) to an object (case-sensitive).
134    /// 
135    /// # Arguments
136    /// * `object` - The JSON object to patch
137    /// * `patches` - The patch operations to apply
138    /// 
139    /// # Returns
140    /// Ok(()) on success, or an error
141    pub fn apply_case_sensitive(object: &mut CJson, patches: &CJson) -> CJsonResult<()> {
142        let result = unsafe {
143            cJSONUtils_ApplyPatchesCaseSensitive(object.as_mut_ptr(), patches.as_ptr())
144        };
145        if result == 0 {
146            Ok(())
147        } else {
148            Err(CJsonError::InvalidOperation)
149        }
150    }
151
152    /// Add a patch operation to a patches array.
153    /// 
154    /// # Arguments
155    /// * `array` - The array of patch operations
156    /// * `operation` - The operation type ("add", "remove", "replace", "move", "copy", "test")
157    /// * `path` - The JSON Pointer path
158    /// * `value` - The value for the operation (optional for some operations)
159    pub fn add_to_array(
160        array: &mut CJson,
161        operation: &str,
162        path: &str,
163        value: Option<&CJson>,
164    ) -> CJsonResult<()> {
165        if !array.is_array() {
166            return Err(CJsonError::TypeError);
167        }
168
169        let c_operation = CString::new(operation).map_err(|_| CJsonError::InvalidUtf8)?;
170        let c_path = CString::new(path).map_err(|_| CJsonError::InvalidUtf8)?;
171
172        let value_ptr = value.map(|v| v.as_ptr()).unwrap_or(core::ptr::null());
173
174        unsafe {
175            cJSONUtils_AddPatchToArray(
176                array.as_mut_ptr(),
177                c_operation.as_ptr() as *const i8,
178                c_path.as_ptr() as *const i8,
179                value_ptr,
180            );
181        }
182        Ok(())
183    }
184}
185
186/// JSON Merge Patch utilities (RFC7386)
187pub struct JsonMergePatch;
188
189impl JsonMergePatch {
190    /// Apply a JSON Merge Patch (RFC7386) to a target object.
191    /// 
192    /// # Arguments
193    /// * `target` - The JSON object to merge into
194    /// * `patch` - The merge patch to apply
195    /// 
196    /// # Returns
197    /// A new CJson object with the merged result
198    pub fn apply(target: &mut CJson, patch: &CJson) -> CJsonResult<CJson> {
199        let ptr = unsafe {
200            cJSONUtils_MergePatch(target.as_mut_ptr(), patch.as_ptr())
201        };
202        unsafe { CJson::from_ptr(ptr) }
203    }
204
205    /// Apply a JSON Merge Patch (RFC7386) to a target object (case-sensitive).
206    /// 
207    /// # Arguments
208    /// * `target` - The JSON object to merge into
209    /// * `patch` - The merge patch to apply
210    /// 
211    /// # Returns
212    /// A new CJson object with the merged result
213    pub fn apply_case_sensitive(target: &mut CJson, patch: &CJson) -> CJsonResult<CJson> {
214        let ptr = unsafe {
215            cJSONUtils_MergePatchCaseSensitive(target.as_mut_ptr(), patch.as_ptr())
216        };
217        unsafe { CJson::from_ptr(ptr) }
218    }
219
220    /// Generate a JSON Merge Patch to transform 'from' into 'to'.
221    /// 
222    /// Note: This function modifies both 'from' and 'to' by sorting their keys.
223    /// 
224    /// # Arguments
225    /// * `from` - The original JSON object
226    /// * `to` - The target JSON object
227    /// 
228    /// # Returns
229    /// A new CJson object containing the merge patch
230    pub fn generate(from: &mut CJson, to: &mut CJson) -> CJsonResult<CJson> {
231        let ptr = unsafe {
232            cJSONUtils_GenerateMergePatch(from.as_mut_ptr(), to.as_mut_ptr())
233        };
234        unsafe { CJson::from_ptr(ptr) }
235    }
236
237    /// Generate a JSON Merge Patch to transform 'from' into 'to' (case-sensitive).
238    /// 
239    /// Note: This function modifies both 'from' and 'to' by sorting their keys.
240    /// 
241    /// # Arguments
242    /// * `from` - The original JSON object
243    /// * `to` - The target JSON object
244    /// 
245    /// # Returns
246    /// A new CJson object containing the merge patch
247    pub fn generate_case_sensitive(from: &mut CJson, to: &mut CJson) -> CJsonResult<CJson> {
248        let ptr = unsafe {
249            cJSONUtils_GenerateMergePatchCaseSensitive(from.as_mut_ptr(), to.as_mut_ptr())
250        };
251        unsafe { CJson::from_ptr(ptr) }
252    }
253}
254
255/// Utility functions for JSON object manipulation
256pub struct JsonUtils;
257
258impl JsonUtils {
259    /// Sort object members alphabetically (case-insensitive).
260    /// 
261    /// # Arguments
262    /// * `object` - The JSON object to sort
263    pub fn sort_object(object: &mut CJson) -> CJsonResult<()> {
264        if !object.is_object() {
265            return Err(CJsonError::TypeError);
266        }
267        unsafe { cJSONUtils_SortObject(object.as_mut_ptr()) };
268        Ok(())
269    }
270
271    /// Sort object members alphabetically (case-sensitive).
272    /// 
273    /// # Arguments
274    /// * `object` - The JSON object to sort
275    pub fn sort_object_case_sensitive(object: &mut CJson) -> CJsonResult<()> {
276        if !object.is_object() {
277            return Err(CJsonError::TypeError);
278        }
279        unsafe { cJSONUtils_SortObjectCaseSensitive(object.as_mut_ptr()) };
280        Ok(())
281    }
282}
283
284/// Re-export CJsonRef for use with pointer operations
285pub use crate::cjson::CJsonRef;
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use crate::cjson::CJson;
291
292    #[test]
293    fn test_json_pointer_get() {
294        let json = r#"{"foo":{"bar":[1,2,3]}}"#;
295        let obj = CJson::parse(json).unwrap();
296        
297        let result = JsonPointer::get(&obj, "/foo/bar/1").unwrap();
298        assert_eq!(result.get_number_value().unwrap(), 2.0);
299    }
300
301    #[test]
302    fn test_json_pointer_get_case_sensitive() {
303        let json = r#"{"Foo":{"Bar":"test"}}"#;
304        let obj = CJson::parse(json).unwrap();
305        
306        let result = JsonPointer::get_case_sensitive(&obj, "/Foo/Bar").unwrap();
307        assert_eq!(result.get_string_value().unwrap(), "test");
308    }
309
310    #[test]
311    fn test_json_pointer_not_found() {
312        let json = r#"{"foo":"bar"}"#;
313        let obj = CJson::parse(json).unwrap();
314        
315        assert!(JsonPointer::get(&obj, "/nonexistent").is_err());
316    }
317
318    #[test]
319    fn test_json_patch_generate_and_apply() {
320        let from_json = r#"{"name":"John","age":30}"#;
321        let to_json = r#"{"name":"John","age":31,"city":"NYC"}"#;
322        
323        let mut from = CJson::parse(from_json).unwrap();
324        let mut to = CJson::parse(to_json).unwrap();
325        
326        let patches = JsonPatch::generate(&mut from, &mut to).unwrap();
327        assert!(patches.is_array());
328    }
329
330    #[test]
331    fn test_json_patch_apply() {
332        let obj_json = r#"{"name":"John","age":30}"#;
333        let patch_json = r#"[{"op":"replace","path":"/age","value":31}]"#;
334        
335        let mut obj = CJson::parse(obj_json).unwrap();
336        let patches = CJson::parse(patch_json).unwrap();
337        
338        JsonPatch::apply(&mut obj, &patches).unwrap();
339        
340        let age = obj.get_object_item("age").unwrap();
341        assert_eq!(age.get_number_value().unwrap(), 31.0);
342    }
343
344    #[test]
345    fn test_json_merge_patch_apply() {
346        let target_json = r#"{"name":"John","age":30}"#;
347        let patch_json = r#"{"age":31,"city":"NYC"}"#;
348        
349        let mut target = CJson::parse(target_json).unwrap();
350        let patch = CJson::parse(patch_json).unwrap();
351        
352        let result = JsonMergePatch::apply(&mut target, &patch).unwrap();
353        // target ownership is consumed by apply, don't use it anymore
354        core::mem::forget(target); // Prevent double free
355        
356        let age = result.get_object_item("age").unwrap();
357        assert_eq!(age.get_number_value().unwrap(), 31.0);
358        
359        let city = result.get_object_item("city").unwrap();
360        assert_eq!(city.get_string_value().unwrap(), "NYC");
361    }
362
363    #[test]
364    fn test_json_merge_patch_generate() {
365        let from_json = r#"{"name":"John","age":30}"#;
366        let to_json = r#"{"name":"John","age":31}"#;
367        
368        let mut from = CJson::parse(from_json).unwrap();
369        let mut to = CJson::parse(to_json).unwrap();
370        
371        let patch = JsonMergePatch::generate(&mut from, &mut to).unwrap();
372        assert!(patch.is_object());
373    }
374
375    #[test]
376    fn test_json_utils_sort_object() {
377        let json = r#"{"z":"last","a":"first","m":"middle"}"#;
378        let mut obj = CJson::parse(json).unwrap();
379        
380        JsonUtils::sort_object(&mut obj).unwrap();
381        
382        // After sorting, the object should still be valid
383        assert!(obj.is_object());
384        assert!(obj.has_object_item("a"));
385        assert!(obj.has_object_item("m"));
386        assert!(obj.has_object_item("z"));
387    }
388
389    #[test]
390    fn test_json_utils_sort_object_case_sensitive() {
391        let json = r#"{"Z":"last","a":"first","M":"middle"}"#;
392        let mut obj = CJson::parse(json).unwrap();
393        
394        JsonUtils::sort_object_case_sensitive(&mut obj).unwrap();
395        
396        assert!(obj.is_object());
397        assert!(obj.has_object_item("a"));
398        assert!(obj.has_object_item("M"));
399        assert!(obj.has_object_item("Z"));
400    }
401
402    #[test]
403    fn test_pointer_find_from_object_to() {
404        let json = r#"{"foo":{"bar":"test"}}"#;
405        let obj = CJson::parse(json).unwrap();
406        
407        // Note: This creates a new CJson from the reference for testing
408        let target_owned = CJson::parse(r#"{"bar":"test"}"#).unwrap();
409        
410        // This might not work exactly as expected due to pointer comparison
411        // but tests the API
412        let _ = JsonPointer::find_from_object_to(&obj, &target_owned);
413    }
414
415    #[test]
416    fn test_json_patch_add_to_array() {
417        let mut patches = CJson::create_array().unwrap();
418        
419        let value = CJson::create_string("test").unwrap();
420        JsonPatch::add_to_array(&mut patches, "add", "/foo", Some(&value)).unwrap();
421        
422        assert!(patches.is_array());
423        assert_eq!(patches.get_array_size().unwrap(), 1);
424    }
425
426    #[test]
427    fn test_complex_pointer_path() {
428        let json = r#"{"users":[{"name":"Alice","age":25},{"name":"Bob","age":30}]}"#;
429        let obj = CJson::parse(json).unwrap();
430        
431        let result = JsonPointer::get(&obj, "/users/0/name").unwrap();
432        assert_eq!(result.get_string_value().unwrap(), "Alice");
433        
434        let result = JsonPointer::get(&obj, "/users/1/age").unwrap();
435        assert_eq!(result.get_number_value().unwrap(), 30.0);
436    }
437
438    #[test]
439    fn test_merge_patch_null_removal() {
440        let target_json = r#"{"name":"John","age":30,"city":"NYC"}"#;
441        let patch_json = r#"{"city":null}"#;
442        
443        let mut target = CJson::parse(target_json).unwrap();
444        let patch = CJson::parse(patch_json).unwrap();
445        
446        let result = JsonMergePatch::apply(&mut target, &patch).unwrap();
447        core::mem::forget(target); // Prevent double free
448        
449        // City should be removed
450        assert!(result.get_object_item("city").is_err());
451        assert!(result.has_object_item("name"));
452        assert!(result.has_object_item("age"));
453    }
454}