Skip to main content

cjson_binding/
cjson_utils.rs

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