json-eval-rs 0.0.125

High-performance JSON Logic evaluator with schema validation and dependency tracking. Built on blazing-fast Rust engine.
Documentation
#![cfg(feature = "ffi")]

use json_eval_rs::ffi::*;
use serde_json::json;
use std::ffi::CString;

#[test]
fn test_ffi_methods_parity() {
    let schema = json!({
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "options": ["A", "B"]
            },
            "items": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": { "type": "string" }
                    }
                },
                "$layout": {
                    "type": "VerticalLayout",
                    "elements": [
                        { "$ref": "#/items/properties/name" }
                    ]
                }
            },
            "form": {
                "$layout": {
                    "type": "VerticalLayout",
                    "elements": [
                        { "$ref": "name" }
                    ]
                }
            }
        }
    });

    let schema_str = CString::new(serde_json::to_string(&schema).unwrap()).unwrap();

    unsafe {
        // 1. json_eval_new
        let handle = json_eval_new(schema_str.as_ptr(), std::ptr::null(), std::ptr::null());
        assert!(!handle.is_null(), "Handle should not be null");

        // 2. json_eval_evaluate
        let data = json!({
            "items": [{"name": "Item 1"}]
        });
        let data_str = CString::new(serde_json::to_string(&data).unwrap()).unwrap();
        let result = json_eval_evaluate(
            handle,
            data_str.as_ptr(),
            std::ptr::null(),
            std::ptr::null(),
        );
        assert!(
            result.success,
            "Evaluation should succeed: {}",
            if result.error.is_null() {
                "none"
            } else {
                std::ffi::CStr::from_ptr(result.error).to_str().unwrap()
            }
        );
        json_eval_free_result(result);

        // 3. json_eval_get_evaluated_schema_resolved
        let result = json_eval_get_evaluated_schema_resolved(handle);
        assert!(
            result.success,
            "Get evaluated schema resolved should succeed"
        );
        assert!(result.data_len > 0, "Should return data");
        let schema_bytes = std::slice::from_raw_parts(result.data_ptr, result.data_len);
        let schema_val: serde_json::Value = serde_json::from_slice(schema_bytes).unwrap();
        // Check if $fullpath is present in the layout elements (resolved)
        assert!(
            schema_val
                .pointer("/properties/form/$layout/elements/0/$fullpath")
                .is_some(),
            "$fullpath should be present in resolved layout"
        );
        assert_eq!(
            schema_val
                .pointer("/properties/form/$layout/elements/0/$fullpath")
                .and_then(|v| v.as_str()),
            Some("properties.name")
        );
        json_eval_free_result(result);

        // 4. json_eval_get_evaluated_schema_resolved_msgpack
        let result = json_eval_get_evaluated_schema_resolved_msgpack(handle);
        assert!(
            result.success,
            "Get resolved evaluated schema MessagePack should succeed"
        );
        let resolved_msgpack = std::slice::from_raw_parts(result.data_ptr, result.data_len);
        let resolved_msgpack_value: serde_json::Value = rmp_serde::from_slice(resolved_msgpack)
            .expect("resolved MessagePack must decode into JSON value");
        assert_eq!(resolved_msgpack_value, schema_val);
        json_eval_free_result(result);

        // 5. json_eval_get_resolved_layout
        let result = json_eval_get_resolved_layout(handle);
        assert!(result.success, "Get resolved layout should succeed");
        assert!(result.data_len > 0, "Should return layout entries");
        json_eval_free_result(result);

        // 6. json_eval_get_field_options
        let field_path = CString::new("properties.name").unwrap();
        let result = json_eval_get_field_options(handle, field_path.as_ptr());
        assert!(result.success, "Get field options should succeed");
        json_eval_free_result(result);

        // 7. Subform methods parity
        let subform_path = CString::new("#/items/0").unwrap();

        // 7a. json_eval_get_resolved_layout_subform
        let result = json_eval_get_resolved_layout_subform(handle, subform_path.as_ptr());
        assert!(result.success, "Get resolved layout subform should succeed");
        json_eval_free_result(result);

        // 7b. json_eval_get_evaluated_schema_resolved_subform
        let result = json_eval_get_evaluated_schema_resolved_subform(handle, subform_path.as_ptr());
        assert!(
            result.success,
            "Get evaluated schema resolved subform should succeed"
        );
        assert!(result.data_len > 0);
        let subform_schema_bytes = std::slice::from_raw_parts(result.data_ptr, result.data_len);
        let subform_schema: serde_json::Value =
            serde_json::from_slice(subform_schema_bytes).unwrap();
        // Check for $fullpath in subform layout
        // In the subform instance, the schema is wrapped in the root key (items)
        assert!(
            subform_schema
                .pointer("/items/$layout/elements/0/$fullpath")
                .is_some(),
            "$fullpath should be present in subform resolved layout"
        );
        json_eval_free_result(result);

        // 8. Validate with include_subforms parity
        let v_schema = json!({
            "type": "object",
            "properties": {
                "title": {
                    "type": "string",
                    "title": "Document Title",
                    "rules": { "required": { "value": true, "message": "Title is required" } }
                },
                "contacts": {
                    "type": "array",
                    "items": {
                        "properties": {
                            "phone": {
                                "type": "string",
                                "rules": { "required": { "value": true, "message": "Phone is required" } }
                            }
                        }
                    }
                }
            }
        });
        let v_schema_str = CString::new(serde_json::to_string(&v_schema).unwrap()).unwrap();
        let v_handle = json_eval_new(v_schema_str.as_ptr(), std::ptr::null(), std::ptr::null());
        let v_data_str = CString::new(r#"{"title":"","contacts":[{"phone":""}]}"#).unwrap();

        // 8a. include_subforms = false
        let res_without = json_eval_validate(
            v_handle,
            v_data_str.as_ptr(),
            std::ptr::null(),
            false,
            false,
        );
        assert!(res_without.success);
        let res_without_val: serde_json::Value = serde_json::from_slice(
            std::slice::from_raw_parts(res_without.data_ptr, res_without.data_len),
        )
        .unwrap();
        assert_eq!(res_without_val["has_error"], true);
        assert!(res_without_val["error"]["title"].is_object());
        assert!(res_without_val["error"]["contacts.0.phone"].is_null());
        json_eval_free_result(res_without);

        // 8b. include_subforms = true
        let res_with =
            json_eval_validate(v_handle, v_data_str.as_ptr(), std::ptr::null(), false, true);
        assert!(res_with.success);
        let res_with_val: serde_json::Value = serde_json::from_slice(std::slice::from_raw_parts(
            res_with.data_ptr,
            res_with.data_len,
        ))
        .unwrap();
        assert_eq!(res_with_val["has_error"], true);
        assert!(res_with_val["error"]["title"].is_object());
        assert_eq!(
            res_with_val["error"]["title"]["data"]["title"],
            "Document Title"
        );
        assert_eq!(res_with_val["error"]["title"]["data"]["required"], true);
        assert!(res_with_val["error"]["contacts.0.phone"].is_object());
        assert_eq!(
            res_with_val["error"]["contacts.0.phone"]["code"],
            "contacts.0.phone.required"
        );
        assert_eq!(
            res_with_val["error"]["contacts.0.phone"]["data"]["required"],
            true
        );
        json_eval_free_result(res_with);

        json_eval_free(v_handle);

        // 9. Cleanup
        json_eval_free(handle);
    }
}