Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ jobs:
run: cargo clippy --all-features --all-targets -- -D warnings
- name: Check
run: cargo check --release --all --all-features
- name: Test
run: cargo test --all --features mock-ffi
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ serde_json = "1.0"
[features]
default = ["serde"]
serde = ["dep:serde"]
mock-ffi = []
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,23 @@ cargo build --release --target wasm32-wasip1 --example llm-mcp
| [httpbin](./examples/httpbin.rs) | HTTP to query anything from httpbin | ✅ | ✅ |
| [llm](./examples/llm.rs) | LLM to chat with `Llama-3.1-8B-Instruct-q4f32_1-MLC` and `SmolLM2-1.7B-Instruct-q4f16_1-MLC` models | ✅ | ✅ |
| [llm-mcp](./examples/llm-mcp.rs) | LLM with MCP (Model Control Protocol) demonstrating tool integration using SSE endpoints | ✅ | ✅ |


## Testing

The SDK uses FFI (Foreign Function Interface) calls that are only available in the Blockless WASM runtime environment.
To run tests without host runtime, use the `mock-ffi` feature which provides mock implementations:

```bash
cargo test --all --features mock-ffi
```

This feature enables mock implementations of all FFI functions, allowing you to:
- Test SDK struct creation and configuration
- Test error handling logic
- Verify API contracts without needing the runtime
- Run unit tests in CI/CD pipelines

Note:
- The mocks return predictable test data and don't perform actual network requests or system calls.
- Only one implementation of the FFI functions is allowed to be mocked.
56 changes: 56 additions & 0 deletions src/cgi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::CGIErrorKind;
use json::{object::Object, JsonValue};
use std::fmt::{Debug, Display};

#[cfg(not(feature = "mock-ffi"))]
#[link(wasm_import_module = "blockless_cgi")]
extern "C" {
#[link_name = "cgi_open"]
Expand All @@ -27,6 +28,61 @@ extern "C" {
pub(crate) fn cgi_list_read(handle: u32, buf: *mut u8, buf_len: u32, num: *mut u32) -> u32;
}

#[cfg(feature = "mock-ffi")]
#[allow(unused_variables)]
mod mock_ffi {
pub unsafe extern "C" fn cgi_open(
_opts: *const u8,
_opts_len: u32,
cgi_handle: *mut u32,
) -> u32 {
unimplemented!()
}

pub unsafe extern "C" fn cgi_stdout_read(
_handle: u32,
buf: *mut u8,
buf_len: u32,
num: *mut u32,
) -> u32 {
unimplemented!()
}

pub unsafe extern "C" fn cgi_stderr_read(
_handle: u32,
buf: *mut u8,
buf_len: u32,
num: *mut u32,
) -> u32 {
unimplemented!()
}

#[allow(dead_code)]
pub unsafe extern "C" fn cgi_stdin_write(
_handle: u32,
_buf: *const u8,
buf_len: u32,
num: *mut u32,
) -> u32 {
unimplemented!()
}

pub unsafe fn cgi_close(_handle: u32) -> u32 {
unimplemented!()
}

pub unsafe fn cgi_list_exec(cgi_handle: *mut u32) -> u32 {
unimplemented!()
}

pub unsafe fn cgi_list_read(_handle: u32, buf: *mut u8, buf_len: u32, num: *mut u32) -> u32 {
unimplemented!()
}
}

#[cfg(feature = "mock-ffi")]
use mock_ffi::*;

#[derive(Debug)]
pub struct CGIExtensions {
pub file_name: String,
Expand Down
39 changes: 39 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::error::HttpErrorKind;
use json::JsonValue;
use std::{cmp::Ordering, collections::BTreeMap};

#[cfg(not(feature = "mock-ffi"))]
#[link(wasm_import_module = "blockless_http")]
extern "C" {
#[link_name = "http_req"]
Expand Down Expand Up @@ -31,6 +32,44 @@ extern "C" {
pub(crate) fn http_close(handle: u32) -> u32;
}

#[cfg(feature = "mock-ffi")]
#[allow(unused_variables)]
mod mock_ffi {

pub unsafe fn http_open(
_url: *const u8,
_url_len: u32,
_opts: *const u8,
_opts_len: u32,
fd: *mut u32,
status: *mut u32,
) -> u32 {
unimplemented!()
}

pub unsafe fn http_read_header(
_handle: u32,
_header: *const u8,
_header_len: u32,
buf: *mut u8,
buf_len: u32,
num: *mut u32,
) -> u32 {
unimplemented!()
}

pub unsafe fn http_read_body(_handle: u32, buf: *mut u8, buf_len: u32, num: *mut u32) -> u32 {
unimplemented!()
}

pub unsafe fn http_close(_handle: u32) -> u32 {
unimplemented!()
}
}

#[cfg(feature = "mock-ffi")]
use mock_ffi::*;

type Handle = u32;
type ExitCode = u32;

Expand Down
65 changes: 65 additions & 0 deletions src/llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{str::FromStr, string::ToString};
type Handle = u32;
type ExitCode = u8;

#[cfg(not(feature = "mock-ffi"))]
#[link(wasm_import_module = "blockless_llm")]
extern "C" {
fn llm_set_model_request(h: *mut Handle, model_ptr: *const u8, model_len: u8) -> ExitCode;
Expand Down Expand Up @@ -34,6 +35,70 @@ extern "C" {
fn llm_close(h: Handle) -> ExitCode;
}

#[cfg(feature = "mock-ffi")]
#[allow(unused_variables)]
mod mock_ffi {
use super::*;

pub unsafe fn llm_set_model_request(
h: *mut Handle,
_model_ptr: *const u8,
_model_len: u8,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_get_model_response(
_h: Handle,
buf: *mut u8,
buf_len: u8,
bytes_written: *mut u8,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_set_model_options_request(
_h: Handle,
_options_ptr: *const u8,
_options_len: u16,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_get_model_options(
_h: Handle,
buf: *mut u8,
buf_len: u16,
bytes_written: *mut u16,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_prompt_request(
_h: Handle,
_prompt_ptr: *const u8,
_prompt_len: u16,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_read_prompt_response(
_h: Handle,
buf: *mut u8,
buf_len: u16,
bytes_written: *mut u16,
) -> ExitCode {
unimplemented!()
}

pub unsafe fn llm_close(_h: Handle) -> ExitCode {
unimplemented!()
}
}

#[cfg(feature = "mock-ffi")]
use mock_ffi::*;

#[derive(Debug, Clone)]
pub enum Models {
Llama321BInstruct(Option<String>),
Expand Down
16 changes: 16 additions & 0 deletions src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[cfg(not(feature = "mock-ffi"))]
#[link(wasm_import_module = "blockless_memory")]
extern "C" {
#[link_name = "memory_read"]
Expand All @@ -6,6 +7,21 @@ extern "C" {
pub(crate) fn env_var_read(buf: *mut u8, len: u32, num: *mut u32) -> u32;
}

#[cfg(feature = "mock-ffi")]
#[allow(unused_variables)]
mod mock_ffi {
pub unsafe fn memory_read(buf: *mut u8, len: u32, num: *mut u32) -> u32 {
unimplemented!()
}

pub unsafe fn env_var_read(buf: *mut u8, len: u32, num: *mut u32) -> u32 {
unimplemented!()
}
}

#[cfg(feature = "mock-ffi")]
use mock_ffi::*;

pub fn read_stdin(buf: &mut [u8]) -> std::io::Result<u32> {
let mut len = 0;
let errno = unsafe { memory_read(buf.as_mut_ptr(), buf.len() as _, &mut len) };
Expand Down
15 changes: 15 additions & 0 deletions src/socket.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::SocketErrorKind;

#[cfg(not(feature = "mock-ffi"))]
#[link(wasm_import_module = "blockless_socket")]
extern "C" {
#[link_name = "create_tcp_bind_socket"]
Expand All @@ -10,6 +11,20 @@ extern "C" {
) -> u32;
}

#[cfg(feature = "mock-ffi")]
mod mock_ffi {
pub unsafe fn create_tcp_bind_socket_native(
_addr: *const u8,
_addr_len: u32,
_fd: *mut u32,
) -> u32 {
unimplemented!()
}
}

#[cfg(feature = "mock-ffi")]
use mock_ffi::*;

pub fn create_tcp_bind_socket(addr: &str) -> Result<u32, SocketErrorKind> {
unsafe {
let addr_ptr = addr.as_ptr();
Expand Down
Loading