Skip to content

Conversation

Copy link

Copilot AI commented Dec 1, 2025

  • Explore repository structure and understand existing code
  • Create src/ffi/mod.rs - module declaration for FFI
  • Create src/ffi/wasm.rs - C FFI interface with all required functions
  • Update src/lib.rs - add ffi module, comment out wasm-bindgen code
  • Update Cargo.toml - update features and make wasm-bindgen optional
  • Create docs/WASM_FFI.md - complete documentation
  • Test WASM build with wasm32-unknown-unknown target
  • Test WASM build with wasm32-wasip1 target
  • Run code review (no issues found)
  • Run CodeQL security check (no alerts)
  • Add C FFI WASM builds to GitHub Actions CI workflow
  • Remove wasm-bindgen and Python builds from CI (keep only Rust and C FFI WASM)
  • Fix test_clear_buffers test to avoid parallel test interference
  • Simplify API with packed pointer (single function call, no need for get_output_ptr)
Original prompt

Context

The json-logic-rs crate currently uses wasm-bindgen with JsValue types for WASM support, which limits it to JavaScript environments. We need to convert it to support WASM through a C-style FFI interface to allow multiple languages (Java/Chicory, JavaScript, Python, Go, .NET) to use the same WASM module.

Current State

  • The crate uses wasm-bindgen with JsValue types
  • Located in src/lib.rs under #[cfg(feature = "wasm")] (lines 20-57)
  • Current interface: pub fn apply(value: JsValue, data: JsValue) -> Result<JsValue, JsValue>
  • Core function: pub fn apply(value: &Value, data: &Value) -> Result<Value, Error> in src/lib.rs (lines 87-90)

Requirements

1. Create C FFI Interface Module

Create a new file src/ffi/wasm.rs with the following requirements:

Memory Model:

  • Use static mutable buffers for result and error storage
  • Accept JSON strings as input via raw pointers and length
  • Return result length (positive) or error length (negative)
  • Provide separate functions to get result/error pointers
  • Buffer size: Use 1MB (1024 * 1024 bytes) for both result and error buffers

Required Functions:

#[no_mangle]
pub extern "C" fn apply_json_logic(
    logic_ptr: *const u8,
    logic_len: i32,
    data_ptr: *const u8,
    data_len: i32,
) -> i32
  • Takes two JSON strings via pointer + length
  • Returns positive i32 for success (result length) or negative i32 for error (error length as negative)
  • Must handle all errors gracefully (JSON parsing errors, JsonLogic evaluation errors)
  • Never panic across FFI boundary
#[no_mangle]
pub extern "C" fn get_result_ptr() -> *const u8
  • Returns pointer to result buffer
#[no_mangle]
pub extern "C" fn get_error_ptr() -> *const u8
  • Returns pointer to error buffer

Additional Helper Functions:

#[no_mangle]
pub extern "C" fn get_buffer_size() -> i32
  • Returns the maximum buffer size (1MB)
#[no_mangle]
pub extern "C" fn clear_buffers()
  • Clears both result and error buffers (sets all bytes to 0)

Error Handling:

  • Catch JSON parsing errors from serde_json::from_str
  • Catch JsonLogic evaluation errors from crate::apply
  • Return error messages as JSON strings: {"error": "error message here"}
  • Validate that input pointers are not null
  • Use std::slice::from_raw_parts for pointer → slice conversion
  • Use safe UTF-8 conversion with proper error handling
  • If result/error exceeds buffer size, truncate and indicate in the error message

Safety Considerations:

  • Use unsafe blocks appropriately with clear comments
  • Validate all pointer inputs
  • Ensure no buffer overflows
  • Handle UTF-8 encoding/decoding properly
  • Never panic - use Result types and convert all errors to error returns

2. Update Module Structure

Create src/ffi/mod.rs:

#[cfg(target_arch = "wasm32")]
pub mod wasm;

Update src/lib.rs:

  • Add module declaration: pub mod ffi;
  • Comment out or remove the existing javascript_iface module (lines 20-57)
  • Keep the core apply function unchanged (lines 87-90)
  • Add a comment explaining the change

3. Update Cargo.toml

Library configuration:

[lib]
crate-type = ["cdylib", "lib"]

Features:

[features]
default = []
wasm = []  # For future use if needed
openfeature = []  # For custom operators later
cmdline = ["anyhow", "clap"]
python = ["cpython"]

Dependencies:

  • Make wasm-bindgen optional (keep it but mark as optional)
  • Keep all other dependencies as-is

4. Documentation

Create a new file docs/WASM_FFI.md with:

  • Explanation of the FFI interface
  • Memory model diagram
  • Example usage pattern from different languages
  • Build instructions for both wasm32-unknown-unknown and wasm32-wasi
  • Error handling guidelines
  • Buffer size limitations

Add build commands:

# For JavaScript, Java/Chicory (no system dependencies)
cargo build --target wasm32-unknown-unknown --release

# For Go, .NET, Python (with WASI support)
cargo build --target wasm32-wasi --release

5. Testing Recommendations

Add comments in the code suggesting test cases:

  • Test with valid JSON logic and data
  • Test with invalid JSON in logic parameter
  • Test with invalid JSON in data parameter
  • Test with logic that causes evaluation errors
  • Test with very large results that might exceed buffer
  • Test with null/empty inputs
  • Test buffer clearing functionality

Expected Outcome

After implementation:

  1. src/ffi/wasm.rs - Complete C FFI interface with all required functions
  2. src/ffi/mod.rs - Module declaration
  3. src/lib.rs - Updated with ffi module, wasm-bindgen code commented out
  4. Cargo.toml - Updated features and optional dependencies
  5. docs/WASM_FFI.md - Complete documentation
  6. All changes compile successfully for both WASM targets
  7. The core apply function remains unchanged and continues to work

Implementation Notes

  • The static buffers should use std::sync::Mutex ...

This pull request was created as a result of the following prompt from Copilot chat.

Context

The json-logic-rs crate currently uses wasm-bindgen with JsValue types for WASM support, which limits it to JavaScript environments. We need to convert it to support WASM through a C-style FFI interface to allow multiple languages (Java/Chicory, JavaScript, Python, Go, .NET) to use the same WASM module.

Current State

  • The crate uses wasm-bindgen with JsValue types
  • Located in src/lib.rs under #[cfg(feature = "wasm")] (lines 20-57)
  • Current interface: pub fn apply(value: JsValue, data: JsValue) -> Result<JsValue, JsValue>
  • Core function: pub fn apply(value: &Value, data: &Value) -> Result<Value, Error> in src/lib.rs (lines 87-90)

Requirements

1. Create C FFI Interface Module

Create a new file src/ffi/wasm.rs with the following requirements:

Memory Model:

  • Use static mutable buffers for result and error storage
  • Accept JSON strings as input via raw pointers and length
  • Return result length (positive) or error length (negative)
  • Provide separate functions to get result/error pointers
  • Buffer size: Use 1MB (1024 * 1024 bytes) for both result and error buffers

Required Functions:

#[no_mangle]
pub extern "C" fn apply_json_logic(
    logic_ptr: *const u8,
    logic_len: i32,
    data_ptr: *const u8,
    data_len: i32,
) -> i32
  • Takes two JSON strings via pointer + length
  • Returns positive i32 for success (result length) or negative i32 for error (error length as negative)
  • Must handle all errors gracefully (JSON parsing errors, JsonLogic evaluation errors)
  • Never panic across FFI boundary
#[no_mangle]
pub extern "C" fn get_result_ptr() -> *const u8
  • Returns pointer to result buffer
#[no_mangle]
pub extern "C" fn get_error_ptr() -> *const u8
  • Returns pointer to error buffer

Additional Helper Functions:

#[no_mangle]
pub extern "C" fn get_buffer_size() -> i32
  • Returns the maximum buffer size (1MB)
#[no_mangle]
pub extern "C" fn clear_buffers()
  • Clears both result and error buffers (sets all bytes to 0)

Error Handling:

  • Catch JSON parsing errors from serde_json::from_str
  • Catch JsonLogic evaluation errors from crate::apply
  • Return error messages as JSON strings: {"error": "error message here"}
  • Validate that input pointers are not null
  • Use std::slice::from_raw_parts for pointer → slice conversion
  • Use safe UTF-8 conversion with proper error handling
  • If result/error exceeds buffer size, truncate and indicate in the error message

Safety Considerations:

  • Use unsafe blocks appropriately with clear comments
  • Validate all pointer inputs
  • Ensure no buffer overflows
  • Handle UTF-8 encoding/decoding properly
  • Never panic - use Result types and convert all errors to error returns

2. Update Module Structure

Create src/ffi/mod.rs:

#[cfg(target_arch = "wasm32")]
pub mod wasm;

Update src/lib.rs:

  • Add module declaration: pub mod ffi;
  • Comment out or remove the existing javascript_iface module (lines 20-57)
  • Keep the core apply function unchanged (lines 87-90)
  • Add a comment explaining the change

3. Update Cargo.toml

Library configuration:

[lib]
crate-type = ["cdylib", "lib"]

Features:

[features]
default = []
wasm = []  # For future use if needed
openfeature = []  # For custom operators later
cmdline = ["anyhow", "clap"]
python = ["cpython"]

Dependencies:

  • Make wasm-bindgen optional (keep it but mark as optional)
  • Keep all other dependencies as-is

4. Documentation

Create a new file docs/WASM_FFI.md with:

  • Explanation of the FFI interface
  • Memory model diagram
  • Example usage pattern from different languages
  • Build instructions for both wasm32-unknown-unknown and wasm32-wasi
  • Error handling guidelines
  • Buffer size limitations

Add build commands:

# For JavaScript, Java/Chicory (no system dependencies)
cargo build --target wasm32-unknown-unknown --release

# For Go, .NET, Python (with WASI support)
cargo build --target wasm32-wasi --release

5. Testing Recommendations

Add comments in the code suggesting test cases:

  • Test with valid JSON logic and data
  • Test with invalid JSON in logic parameter
  • Test with invalid JSON in data parameter
  • Test with logic that causes evaluation errors
  • Test with very large results that might exceed buffer
  • Test with null/empty inputs
  • Test buffer clearing functionality

Expected Outcome

After implementation:

  1. src/ffi/wasm.rs - Complete C FFI interface with all required functions
  2. src/ffi/mod.rs - Module declaration
  3. src/lib.rs - Updated with ffi module, wasm-bindgen code commented out
  4. Cargo.toml - Updated features and optional dependencies
  5. docs/WASM_FFI.md - Complete documentation
  6. All changes compile successfully for both WASM targets
  7. The core apply function remains unchanged and continues to work

Implementation Notes

  • The static buffers should use std::sync::Mutex for thread safety (even though WASM is typically single-threaded)
  • Use lazy_static crate or once_cell for static buffer initialization if needed
  • Ensure proper error messages are descriptive and follow JSON format
  • Consider adding version information function for debugging
  • All unsafe code must have safety comments explaining why it's safe

Example Usage Pattern (Reference)

1. Allocate memory in WASM for logic JSON string
2. Write logic JSON to that memory
3. Allocate memory for data JSON string
4. Write data JSON to that memory
5. Call apply_json_logic(logic_ptr, logic_len, data_ptr, data_len)
6. If result >= 0: call get_result_ptr() and read result_len bytes
7. If result < 0: call get_error_ptr() and abs(result) bytes

Constraints

  • Must work with wasm32-unknown-unknown target (no std IO, no system calls)
  • Must also work with wasm32-wasi target
  • Keep the core JsonLogic implementation (apply function) unchanged
  • Only create the FFI boundary layer
  • No threading, no file operations
  • JSON serialization must handle all JsonLogic value types

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
Copilot AI changed the title [WIP] Create C FFI interface for wasm support Add C FFI interface for WASM to support multiple languages Dec 1, 2025
Copilot AI requested a review from aepfli December 1, 2025 12:32
@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot is this also building the wasm modules via github actions?

Copy link
Author

Copilot AI commented Dec 1, 2025

@copilot is this also building the wasm modules via github actions?

The current CI workflow (.github/workflows/ci.yml) still builds WASM modules using wasm-pack with the old wasm-bindgen approach (line 192-199).

For the new C FFI interface, you would need to add additional build steps using:

cargo build --target wasm32-unknown-unknown --release  # for JS, Java/Chicory
cargo build --target wasm32-wasip1 --release           # for Go, .NET, Python  

The artifacts would be in target/wasm32-unknown-unknown/release/jsonlogic_rs.wasm and target/wasm32-wasip1/release/jsonlogic_rs.wasm.

Would you like me to add these build steps to the CI workflow?

@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot yes please add them

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
Copy link
Author

Copilot AI commented Dec 1, 2025

@copilot yes please add them

Added in b150a22. The CI workflow now builds both WASM targets (wasm32-unknown-unknown and wasm32-wasip1) and uploads them as separate artifacts.

@aepfli aepfli marked this pull request as ready for review December 1, 2025 13:20
@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot trigger the ci please

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
Copy link
Author

Copilot AI commented Dec 1, 2025

@copilot trigger the ci please

CI triggered with commit 0cb6385.

@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot currently we do not need the other builds except the ones we added. can you remove the other builds for wasm-bind and python. furthermore the build seems to be failing

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
Copy link
Author

Copilot AI commented Dec 1, 2025

@copilot currently we do not need the other builds except the ones we added. can you remove the other builds for wasm-bind and python. furthermore the build seems to be failing

Simplified the CI workflow in d1a75a7. Removed wasm-bindgen/wasm-pack builds, Python builds (sdist, wheels), and NPM publishing. The CI now only runs Rust tests and builds the C FFI WASM modules for both targets (wasm32-unknown-unknown and wasm32-wasip1).

@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot ```

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
@aepfli
Copy link

aepfli commented Dec 1, 2025

@copilot i think it would be best to use a packed pointer for apply_json_logic so we only need to call this method. everything returned could be a json object, even for errors, this would make the api really simple

Co-authored-by: aepfli <9987394+aepfli@users.noreply.github.com>
Copy link
Author

Copilot AI commented Dec 1, 2025

@copilot i think it would be best to use a packed pointer for apply_json_logic so we only need to call this method. everything returned could be a json object, even for errors, this would make the a...

Implemented in 37eff13. The API now uses a packed pointer approach - apply_json_logic returns a single i64 where high 32 bits = output pointer, low 32 bits = output length. No separate get_output_ptr() call needed. All outputs (results and errors) are valid JSON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants