|
| 1 | +# AGFS WASM FFI |
| 2 | + |
| 3 | +A safe, high-level Rust SDK for building AGFS filesystem plugins in WebAssembly. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +This library provides a complete abstraction layer for developing AGFS plugins in Rust/WASM. It handles all the low-level details of the WASM interface and provides a clean, type-safe API. |
| 8 | + |
| 9 | +## Features |
| 10 | + |
| 11 | +- **Safe Abstractions**: Minimal unsafe code, all hidden behind safe interfaces |
| 12 | +- **Easy to Use**: Simply implement a trait and export with a macro |
| 13 | +- **Type-Safe**: Strong typing for all filesystem operations |
| 14 | +- **Host FS Access**: Access to the host filesystem from WASM |
| 15 | +- **HTTP Client**: Make HTTP requests to external services |
| 16 | +- **Flexible**: Support for both read-only and read-write filesystems |
| 17 | + |
| 18 | +## Quick Start |
| 19 | + |
| 20 | +Add this to your `Cargo.toml`: |
| 21 | + |
| 22 | +```toml |
| 23 | +[dependencies] |
| 24 | +agfs-wasm-ffi = { path = "../agfs-wasm-ffi" } |
| 25 | + |
| 26 | +[lib] |
| 27 | +crate-type = ["cdylib"] |
| 28 | + |
| 29 | +[profile.release] |
| 30 | +opt-level = "z" |
| 31 | +lto = true |
| 32 | +codegen-units = 1 |
| 33 | +panic = "abort" |
| 34 | +``` |
| 35 | + |
| 36 | +## Example: Read-Only Filesystem |
| 37 | + |
| 38 | +```rust |
| 39 | +use agfs_wasm_ffi::prelude::*; |
| 40 | + |
| 41 | +#[derive(Default)] |
| 42 | +struct HelloFS; |
| 43 | + |
| 44 | +impl ReadOnlyFileSystem for HelloFS { |
| 45 | + fn name(&self) -> &str { |
| 46 | + "hellofs" |
| 47 | + } |
| 48 | + |
| 49 | + fn readme(&self) -> &str { |
| 50 | + "A simple Hello World filesystem" |
| 51 | + } |
| 52 | + |
| 53 | + fn read(&self, path: &str, _offset: i64, _size: i64) -> Result<Vec<u8>> { |
| 54 | + match path { |
| 55 | + "/hello.txt" => Ok(b"Hello, World!\n".to_vec()), |
| 56 | + _ => Err(Error::NotFound), |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + fn stat(&self, path: &str) -> Result<FileInfo> { |
| 61 | + match path { |
| 62 | + "/" => Ok(FileInfo::dir("", 0o755)), |
| 63 | + "/hello.txt" => Ok(FileInfo::file("hello.txt", 14, 0o644)), |
| 64 | + _ => Err(Error::NotFound), |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + fn readdir(&self, path: &str) -> Result<Vec<FileInfo>> { |
| 69 | + match path { |
| 70 | + "/" => Ok(vec![FileInfo::file("hello.txt", 14, 0o644)]), |
| 71 | + _ => Err(Error::NotFound), |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +export_plugin!(HelloFS); |
| 77 | +``` |
| 78 | + |
| 79 | +## Example: Read-Write Filesystem |
| 80 | + |
| 81 | +```rust |
| 82 | +use agfs_wasm_ffi::prelude::*; |
| 83 | +use std::collections::HashMap; |
| 84 | +use std::cell::RefCell; |
| 85 | + |
| 86 | +#[derive(Default)] |
| 87 | +struct MemFS { |
| 88 | + files: RefCell<HashMap<String, Vec<u8>>>, |
| 89 | +} |
| 90 | + |
| 91 | +impl FileSystem for MemFS { |
| 92 | + fn name(&self) -> &str { |
| 93 | + "memfs" |
| 94 | + } |
| 95 | + |
| 96 | + fn read(&self, path: &str, offset: i64, size: i64) -> Result<Vec<u8>> { |
| 97 | + let files = self.files.borrow(); |
| 98 | + let data = files.get(path).ok_or(Error::NotFound)?; |
| 99 | + // ... handle offset and size |
| 100 | + Ok(data.clone()) |
| 101 | + } |
| 102 | + |
| 103 | + fn write(&mut self, path: &str, data: &[u8]) -> Result<Vec<u8>> { |
| 104 | + self.files.borrow_mut().insert(path.to_string(), data.to_vec()); |
| 105 | + Ok(vec![]) |
| 106 | + } |
| 107 | + |
| 108 | + // ... implement other methods |
| 109 | +} |
| 110 | + |
| 111 | +export_plugin!(MemFS); |
| 112 | +``` |
| 113 | + |
| 114 | +## Host Filesystem Access |
| 115 | + |
| 116 | +Access the host filesystem from your WASM plugin: |
| 117 | + |
| 118 | +```rust |
| 119 | +use agfs_wasm_ffi::prelude::*; |
| 120 | + |
| 121 | +// Read from host filesystem |
| 122 | +let data = HostFS::read("/path/on/host/file.txt", 0, -1)?; |
| 123 | + |
| 124 | +// Write to host filesystem |
| 125 | +HostFS::write("/path/on/host/output.txt", b"Hello")?; |
| 126 | + |
| 127 | +// Get file info |
| 128 | +let info = HostFS::stat("/path/on/host/file.txt")?; |
| 129 | + |
| 130 | +// List directory |
| 131 | +let entries = HostFS::readdir("/path/on/host/dir")?; |
| 132 | +``` |
| 133 | + |
| 134 | +## HTTP Client |
| 135 | + |
| 136 | +Make HTTP requests from your WASM plugin: |
| 137 | + |
| 138 | +```rust |
| 139 | +use agfs_wasm_ffi::prelude::*; |
| 140 | + |
| 141 | +// Simple GET request |
| 142 | +let response = Http::get("https://api.example.com/data")?; |
| 143 | +let text = response.text()?; |
| 144 | + |
| 145 | +// POST with JSON |
| 146 | +let data = serde_json::json!({"key": "value"}); |
| 147 | +let response = Http::post_json("https://api.example.com/submit", &data)?; |
| 148 | + |
| 149 | +// Custom request |
| 150 | +let response = Http::request( |
| 151 | + HttpRequest::post("https://api.example.com/upload") |
| 152 | + .header("Authorization", "Bearer token") |
| 153 | + .body_str("data") |
| 154 | + .timeout(60) |
| 155 | +)?; |
| 156 | + |
| 157 | +// Parse JSON response |
| 158 | +#[derive(Deserialize)] |
| 159 | +struct ApiResponse { |
| 160 | + status: String, |
| 161 | +} |
| 162 | + |
| 163 | +let api_response: ApiResponse = response.json()?; |
| 164 | +``` |
| 165 | + |
| 166 | +## API Reference |
| 167 | + |
| 168 | +### Traits |
| 169 | + |
| 170 | +- **`ReadOnlyFileSystem`**: Implement for read-only filesystems |
| 171 | + - Required: `name()`, `read()`, `stat()`, `readdir()` |
| 172 | + - Optional: `readme()`, `initialize()` |
| 173 | + |
| 174 | +- **`FileSystem`**: Implement for read-write filesystems |
| 175 | + - All ReadOnlyFileSystem methods |
| 176 | + - Additional: `write()`, `create()`, `mkdir()`, `remove()`, etc. |
| 177 | + |
| 178 | +### Types |
| 179 | + |
| 180 | +- **`FileInfo`**: File metadata (name, size, mode, timestamps) |
| 181 | +- **`Error`**: Filesystem errors (NotFound, PermissionDenied, etc.) |
| 182 | +- **`Config`**: Plugin configuration passed during initialization |
| 183 | +- **`HttpRequest`**: HTTP request builder |
| 184 | +- **`HttpResponse`**: HTTP response with status, headers, body |
| 185 | + |
| 186 | +### Macros |
| 187 | + |
| 188 | +- **`export_plugin!(Type)`**: Export your filesystem as a WASM plugin |
| 189 | + |
| 190 | +## Building |
| 191 | + |
| 192 | +Build your WASM plugin: |
| 193 | + |
| 194 | +```bash |
| 195 | +cargo build --release --target wasm32-unknown-unknown |
| 196 | +``` |
| 197 | + |
| 198 | +Optimize with `wasm-opt` (optional): |
| 199 | + |
| 200 | +```bash |
| 201 | +wasm-opt -Oz target/wasm32-unknown-unknown/release/your_plugin.wasm \ |
| 202 | + -o optimized.wasm |
| 203 | +``` |
| 204 | + |
| 205 | +## Examples |
| 206 | + |
| 207 | +See the `examples/` directory for complete examples: |
| 208 | + |
| 209 | +- **hellofs-wasm**: Simple read-only filesystem with host FS access |
| 210 | +- **hackernewsfs-wasm**: Fetches Hacker News stories via HTTP |
| 211 | + |
| 212 | +## License |
| 213 | + |
| 214 | +Apache-2.0 |
0 commit comments