use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::c_char;
use crate::{
dispatch::Action,
engine_core::EngineCore,
ir::{ast_to_ir_node, IRNode},
lifecycle::ModuleInstance,
reconcile::Patch,
wasm::shared::{format_parse_errors, render_subtree_into, NodeIdIndex},
};
use super::ffi::{ActionPayload, ModuleConfig, SparseStateUpdate};
thread_local! {
static ENGINE: RefCell<Option<WasiEngine>> = RefCell::new(None);
static PATCH_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
static ACTION_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
static IMPORT_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
static ERROR_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
static PORTABLE_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
}
struct WasiEngine {
core: EngineCore,
node_id_index: NodeIdIndex,
active_action_scope: Option<String>,
}
impl WasiEngine {
fn new() -> Self {
Self {
core: EngineCore::new(),
node_id_index: NodeIdIndex::new(),
active_action_scope: None,
}
}
fn emit_patches(&mut self, mut patches: Vec<Patch>) {
EngineCore::filter_spurious_removes(&mut patches);
self.node_id_index.index_creates(&patches, &self.core);
emit_patches_internal(&patches);
}
}
#[no_mangle]
pub extern "C" fn wasi_alloc(size: usize) -> *mut u8 {
let mut buf = Vec::with_capacity(size);
let ptr = buf.as_mut_ptr();
std::mem::forget(buf);
ptr
}
#[no_mangle]
pub extern "C" fn wasi_free(ptr: *mut u8, size: usize) {
if !ptr.is_null() {
unsafe {
let _ = Vec::from_raw_parts(ptr, 0, size);
}
}
}
#[no_mangle]
pub extern "C" fn wasi_strlen(ptr: *const c_char) -> usize {
if ptr.is_null() {
return 0;
}
unsafe { CStr::from_ptr(ptr).to_bytes().len() }
}
fn set_last_error(msg: &str) {
ERROR_BUFFER.with(|buf| {
*buf.borrow_mut() = msg.as_bytes().to_vec();
});
}
fn fail(code: i32, msg: &str) -> i32 {
set_last_error(msg);
code
}
#[no_mangle]
pub extern "C" fn hypen_get_last_error_len() -> usize {
ERROR_BUFFER.with(|buf| buf.borrow().len())
}
#[no_mangle]
pub extern "C" fn hypen_get_last_error(out_ptr: *mut u8, out_len: usize) -> usize {
ERROR_BUFFER.with(|buf| {
let error = buf.borrow();
let copy_len = error.len().min(out_len);
if copy_len > 0 && !out_ptr.is_null() {
unsafe {
std::ptr::copy_nonoverlapping(error.as_ptr(), out_ptr, copy_len);
}
}
copy_len
})
}
#[no_mangle]
pub extern "C" fn hypen_clear_last_error() {
ERROR_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_init() -> i32 {
ENGINE.with(|engine| {
*engine.borrow_mut() = Some(WasiEngine::new());
});
ERROR_BUFFER.with(|buf| buf.borrow_mut().clear());
0
}
#[no_mangle]
pub extern "C" fn hypen_destroy() {
ENGINE.with(|engine| {
*engine.borrow_mut() = None;
});
PATCH_BUFFER.with(|buf| buf.borrow_mut().clear());
ACTION_BUFFER.with(|buf| buf.borrow_mut().clear());
ERROR_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_get_revision() -> u64 {
ENGINE.with(|engine| {
engine
.borrow()
.as_ref()
.map(|e| e.core.revision)
.unwrap_or(0)
})
}
#[no_mangle]
pub extern "C" fn hypen_render_source(source_ptr: *const u8, source_len: usize) -> i32 {
let source = match ptr_to_str(source_ptr, source_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_render_source: invalid UTF-8 source pointer"),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => {
return fail(
2,
"hypen_render_source: engine not initialized (call hypen_init first)",
)
}
};
match hypen_parser::parse_document(source) {
Ok(doc) => {
store_pending_imports(&doc.imports);
if let Some(component) = doc.components.first() {
let ir_node = ast_to_ir_node(component);
render_internal(engine, &ir_node);
}
0
}
Err(errs) => {
let msg = format!(
"hypen_render_source: parse error: {}",
format_parse_errors(&errs)
);
fail(3, &msg)
}
}
})
}
#[no_mangle]
pub extern "C" fn hypen_render_into(
source_ptr: *const u8,
source_len: usize,
parent_id_ptr: *const u8,
parent_id_len: usize,
state_ptr: *const u8,
state_len: usize,
) -> i32 {
let source = match ptr_to_str(source_ptr, source_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_render_into: invalid UTF-8 source pointer"),
};
let parent_id_str = match ptr_to_str(parent_id_ptr, parent_id_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_render_into: invalid UTF-8 parent_id pointer"),
};
let state: serde_json::Value = if state_len == 0 {
serde_json::Value::Null
} else {
match ptr_to_str(state_ptr, state_len) {
Ok(s) => serde_json::from_str(s).unwrap_or(serde_json::Value::Null),
Err(_) => serde_json::Value::Null,
}
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => {
return fail(
2,
"hypen_render_into: engine not initialized (call hypen_init first)",
)
}
};
let doc = match hypen_parser::parse_document(source) {
Ok(d) => d,
Err(errs) => {
let msg = format!(
"hypen_render_into: parse error: {}",
format_parse_errors(&errs)
);
return fail(3, &msg);
}
};
store_pending_imports(&doc.imports);
let component = match doc.components.first() {
Some(c) => c,
None => return fail(3, "hypen_render_into: document contains no components"),
};
let ir_node = ast_to_ir_node(component);
let expanded = engine.core.component_registry.expand_ir_node(&ir_node);
let parent_id = match engine.node_id_index.lookup(parent_id_str) {
Some(id) => id,
None => {
return fail(
4,
&format!(
"hypen_render_into: parent node '{}' not found in tree",
parent_id_str
),
)
}
};
let mut patches = Vec::new();
render_subtree_into(&mut engine.core, parent_id, &expanded, &state, &mut patches);
engine.emit_patches(patches);
0
})
}
fn render_internal(engine: &mut WasiEngine, ir_node: &IRNode) {
let patches = engine.core.render_ir_node(ir_node);
engine.emit_patches(patches);
}
#[no_mangle]
pub extern "C" fn hypen_update_state(patch_ptr: *const u8, patch_len: usize) -> i32 {
let patch_str = match ptr_to_str(patch_ptr, patch_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_update_state: invalid UTF-8 patch pointer"),
};
let patch: serde_json::Value = match serde_json::from_str(patch_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_update_state: invalid JSON: {}", e)),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_update_state: engine not initialized"),
};
let scope = engine.active_action_scope.take();
if engine.core.update_state(scope.as_deref(), patch) {
render_dirty_internal(engine);
}
0
})
}
fn update_module_state_internal(
engine: &mut WasiEngine,
name: &str,
patch: serde_json::Value,
) -> i32 {
let canonical = name.to_lowercase();
if !engine.core.modules.contains_key(&canonical) {
return fail(
4,
&format!("update_module_state: module '{}' not found", name),
);
}
if engine.core.update_state(Some(&canonical), patch) {
render_dirty_internal(engine);
}
0
}
#[no_mangle]
pub extern "C" fn hypen_update_module_state(config_ptr: *const u8, config_len: usize) -> i32 {
let config_str = match ptr_to_str(config_ptr, config_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_update_module_state: invalid UTF-8 config pointer"),
};
#[derive(serde::Deserialize)]
struct ModuleStateUpdate {
name: String,
state: serde_json::Value,
}
let update: ModuleStateUpdate = match serde_json::from_str(config_str) {
Ok(v) => v,
Err(e) => {
return fail(
2,
&format!("hypen_update_module_state: invalid JSON: {}", e),
)
}
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_update_module_state: engine not initialized"),
};
update_module_state_internal(engine, &update.name, update.state)
})
}
#[no_mangle]
pub extern "C" fn hypen_update_state_sparse(update_ptr: *const u8, update_len: usize) -> i32 {
let update_str = match ptr_to_str(update_ptr, update_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_update_state_sparse: invalid UTF-8 update pointer"),
};
let update: SparseStateUpdate = match serde_json::from_str(update_str) {
Ok(v) => v,
Err(e) => {
return fail(
2,
&format!("hypen_update_state_sparse: invalid JSON: {}", e),
)
}
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_update_state_sparse: engine not initialized"),
};
let scope = engine.active_action_scope.take();
if engine
.core
.update_state_sparse(scope.as_deref(), &update.paths, &update.values)
{
render_dirty_internal(engine);
}
0
})
}
fn render_dirty_internal(engine: &mut WasiEngine) {
let patches = engine.core.render_dirty();
if !patches.is_empty() {
engine.emit_patches(patches);
}
}
#[no_mangle]
pub extern "C" fn hypen_set_context(
name_ptr: *const u8,
name_len: usize,
data_ptr: *const u8,
data_len: usize,
) -> i32 {
let name = match ptr_to_str(name_ptr, name_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_set_context: invalid UTF-8 name pointer"),
};
let data_str = match ptr_to_str(data_ptr, data_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_set_context: invalid UTF-8 data pointer"),
};
let data: serde_json::Value = match serde_json::from_str(data_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_set_context: invalid JSON: {}", e)),
};
ENGINE.with(|cell| {
let mut engine = cell.borrow_mut();
let engine = match engine.as_mut() {
Some(e) => e,
None => {
return fail(
3,
"hypen_set_context: engine not initialized (call hypen_init first)",
)
}
};
engine.core.set_context(name, data);
render_dirty_internal(engine);
0
})
}
#[no_mangle]
pub extern "C" fn hypen_remove_context(name_ptr: *const u8, name_len: usize) -> i32 {
let name = match ptr_to_str(name_ptr, name_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_remove_context: invalid UTF-8 name pointer"),
};
ENGINE.with(|cell| {
let mut engine = cell.borrow_mut();
let engine = match engine.as_mut() {
Some(e) => e,
None => {
return fail(
2,
"hypen_remove_context: engine not initialized (call hypen_init first)",
)
}
};
engine.core.remove_context(name);
render_dirty_internal(engine);
0
})
}
#[no_mangle]
pub extern "C" fn hypen_set_module(config_ptr: *const u8, config_len: usize) -> i32 {
let config_str = match ptr_to_str(config_ptr, config_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_set_module: invalid UTF-8 config pointer"),
};
let config: ModuleConfig = match serde_json::from_str(config_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_set_module: invalid JSON: {}", e)),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_set_module: engine not initialized"),
};
let instance = ModuleInstance::from_config(
&config.name,
config.actions,
config.state_keys,
config.initial_state,
);
engine.core.set_module(instance);
0
})
}
#[no_mangle]
pub extern "C" fn hypen_register_module(config_ptr: *const u8, config_len: usize) -> i32 {
let config_str = match ptr_to_str(config_ptr, config_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_register_module: invalid UTF-8 config pointer"),
};
let config: ModuleConfig = match serde_json::from_str(config_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_register_module: invalid JSON: {}", e)),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_register_module: engine not initialized"),
};
let instance = ModuleInstance::from_config(
&config.name,
config.actions,
config.state_keys,
config.initial_state,
);
engine.core.register_module(config.name, instance);
0
})
}
#[no_mangle]
pub extern "C" fn hypen_register_action(name_ptr: *const u8, name_len: usize) -> i32 {
let name = match ptr_to_str(name_ptr, name_len) {
Ok(s) => s.to_string(),
Err(_) => return fail(1, "hypen_register_action: invalid UTF-8 name pointer"),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
if let Some(e) = engine_ref.as_mut() {
e.core.registered_actions.push(name);
0
} else {
fail(2, "hypen_register_action: engine not initialized")
}
})
}
#[no_mangle]
pub extern "C" fn hypen_dispatch_action(action_ptr: *const u8, action_len: usize) -> i32 {
let action_str = match ptr_to_str(action_ptr, action_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_dispatch_action: invalid UTF-8 action pointer"),
};
let action_payload: ActionPayload = match serde_json::from_str(action_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_dispatch_action: invalid JSON: {}", e)),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(3, "hypen_dispatch_action: engine not initialized"),
};
engine.active_action_scope = engine.core.action_scope_for(&action_payload.name);
if engine
.core
.registered_actions
.contains(&action_payload.name)
{
let action = Action::new(&action_payload.name).with_payload(action_payload.payload);
ACTION_BUFFER.with(|buf| {
if let Ok(json) = serde_json::to_vec(&action) {
*buf.borrow_mut() = json;
}
});
} else if let Some(ds_action) = engine
.core
.build_data_source_action(&action_payload.name, action_payload.payload)
{
ACTION_BUFFER.with(|buf| {
if let Ok(json) = serde_json::to_vec(&ds_action) {
*buf.borrow_mut() = json;
}
});
}
0
})
}
#[no_mangle]
pub extern "C" fn hypen_register_primitive(name_ptr: *const u8, name_len: usize) -> i32 {
let name = match ptr_to_str(name_ptr, name_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_register_primitive: invalid UTF-8 name pointer"),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
if let Some(e) = engine_ref.as_mut() {
e.core.component_registry.register_primitive(name);
0
} else {
fail(2, "hypen_register_primitive: engine not initialized")
}
})
}
#[no_mangle]
pub extern "C" fn hypen_register_default_primitives() -> i32 {
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
if let Some(e) = engine_ref.as_mut() {
e.core.component_registry.register_default_primitives();
0
} else {
fail(
2,
"hypen_register_default_primitives: engine not initialized",
)
}
})
}
#[no_mangle]
pub extern "C" fn hypen_register_component(
name_ptr: *const u8,
name_len: usize,
source_ptr: *const u8,
source_len: usize,
path_ptr: *const u8,
path_len: usize,
) -> i32 {
let name = match ptr_to_str(name_ptr, name_len) {
Ok(s) => s.to_string(),
Err(_) => return fail(1, "hypen_register_component: invalid UTF-8 name pointer"),
};
let source = match ptr_to_str(source_ptr, source_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_register_component: invalid UTF-8 source pointer"),
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s.to_string(),
Err(_) => return fail(1, "hypen_register_component: invalid UTF-8 path pointer"),
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
let engine = match engine_ref.as_mut() {
Some(e) => e,
None => return fail(2, "hypen_register_component: engine not initialized"),
};
match hypen_parser::parse_component(source) {
Ok(component_spec) => {
let ir_node = ast_to_ir_node(&component_spec);
let ir_element = match ir_node {
IRNode::Element(e) => e,
_ => {
return fail(
3,
"hypen_register_component: component root must be an element",
)
}
};
let is_module =
component_spec.declaration_type == hypen_parser::DeclarationType::Module;
let module_name = if is_module {
Some(component_spec.name.to_lowercase())
} else {
None
};
let mut component =
crate::ir::Component::new(name, move |_props| ir_element.clone())
.with_source_path(&path);
if is_module {
component.is_module = true;
component.module_name = module_name;
}
engine.core.component_registry.register(component);
0
}
Err(errs) => {
let msg = format!(
"hypen_register_component: parse error: {}",
format_parse_errors(&errs)
);
fail(3, &msg)
}
}
})
}
#[no_mangle]
pub extern "C" fn hypen_register_resources(json_ptr: *const u8, json_len: usize) -> i32 {
let json_str = match ptr_to_str(json_ptr, json_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_register_resources: invalid UTF-8 JSON pointer"),
};
let map: indexmap::IndexMap<String, String> = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(e) => {
return fail(
2,
&format!(
"hypen_register_resources: invalid JSON (expected {{name: svg}} map): {}",
e
),
)
}
};
ENGINE.with(|engine| {
let mut engine_ref = engine.borrow_mut();
if let Some(e) = engine_ref.as_mut() {
e.core.register_resources(map);
0
} else {
fail(3, "hypen_register_resources: engine not initialized")
}
})
}
#[no_mangle]
pub extern "C" fn hypen_get_patches_len() -> usize {
PATCH_BUFFER.with(|buf| buf.borrow().len())
}
#[no_mangle]
pub extern "C" fn hypen_get_patches(out_ptr: *mut u8, out_len: usize) -> usize {
PATCH_BUFFER.with(|buf| {
let patches = buf.borrow();
let copy_len = patches.len().min(out_len);
if copy_len > 0 && !out_ptr.is_null() {
unsafe {
std::ptr::copy_nonoverlapping(patches.as_ptr(), out_ptr, copy_len);
}
}
copy_len
})
}
#[no_mangle]
pub extern "C" fn hypen_clear_patches() {
PATCH_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_get_action_len() -> usize {
ACTION_BUFFER.with(|buf| buf.borrow().len())
}
#[no_mangle]
pub extern "C" fn hypen_get_action(out_ptr: *mut u8, out_len: usize) -> usize {
ACTION_BUFFER.with(|buf| {
let action = buf.borrow();
let copy_len = action.len().min(out_len);
if copy_len > 0 && !out_ptr.is_null() {
unsafe {
std::ptr::copy_nonoverlapping(action.as_ptr(), out_ptr, copy_len);
}
}
copy_len
})
}
#[no_mangle]
pub extern "C" fn hypen_clear_action() {
ACTION_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_get_pending_imports_len() -> usize {
IMPORT_BUFFER.with(|buf| buf.borrow().len())
}
#[no_mangle]
pub extern "C" fn hypen_get_pending_imports(out_ptr: *mut u8, out_len: usize) -> usize {
IMPORT_BUFFER.with(|buf| {
let imports = buf.borrow();
let copy_len = imports.len().min(out_len);
if copy_len > 0 && !out_ptr.is_null() {
unsafe {
std::ptr::copy_nonoverlapping(imports.as_ptr(), out_ptr, copy_len);
}
}
copy_len
})
}
#[no_mangle]
pub extern "C" fn hypen_clear_pending_imports() {
IMPORT_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_clear_tree() {
ENGINE.with(|engine| {
if let Some(e) = engine.borrow_mut().as_mut() {
e.core.tree.clear();
}
});
}
#[no_mangle]
pub extern "C" fn hypen_parse_to_json(source_ptr: *const u8, source_len: usize) -> i32 {
let source = match ptr_to_str(source_ptr, source_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_parse_to_json: invalid UTF-8 source pointer"),
};
match hypen_parser::parse_component(source) {
Ok(component) => match serde_json::to_vec_pretty(&component) {
Ok(json) => {
PATCH_BUFFER.with(|buf| *buf.borrow_mut() = json);
0
}
Err(e) => fail(
2,
&format!("hypen_parse_to_json: JSON serialization error: {}", e),
),
},
Err(errs) => {
let msg = format!(
"hypen_parse_to_json: parse error: {}",
format_parse_errors(&errs)
);
fail(3, &msg)
}
}
}
fn ptr_to_str<'a>(ptr: *const u8, len: usize) -> Result<&'a str, ()> {
if ptr.is_null() {
if len == 0 {
return Ok("");
}
return Err(());
}
unsafe {
let slice = std::slice::from_raw_parts(ptr, len);
std::str::from_utf8(slice).map_err(|_| ())
}
}
fn emit_patches_internal(patches: &[Patch]) {
if let Ok(json) = serde_json::to_vec(patches) {
PATCH_BUFFER.with(|buf| *buf.borrow_mut() = json);
}
}
fn store_pending_imports(imports: &[hypen_parser::ImportStatement]) {
if imports.is_empty() {
IMPORT_BUFFER.with(|buf| buf.borrow_mut().clear());
return;
}
let import_infos: Vec<serde_json::Value> = imports
.iter()
.map(|imp| {
let (source_path, source_type) = match &imp.source {
hypen_parser::ImportSource::Local(p) => (p.as_str(), "local"),
hypen_parser::ImportSource::Url(u) => (u.as_str(), "url"),
};
serde_json::json!({
"names": imp.imported_names(),
"source_path": source_path,
"source_type": source_type,
})
})
.collect();
if let Ok(json) = serde_json::to_vec(&import_infos) {
IMPORT_BUFFER.with(|buf| *buf.borrow_mut() = json);
}
}
fn write_portable_result(bytes: Vec<u8>) {
PORTABLE_BUFFER.with(|buf| *buf.borrow_mut() = bytes);
}
#[no_mangle]
pub extern "C" fn hypen_discover_routers(src_ptr: *const u8, src_len: usize) -> i32 {
let src = match ptr_to_str(src_ptr, src_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_discover_routers: invalid UTF-8 source pointer"),
};
let doc = match hypen_parser::parse_document(src) {
Ok(d) => d,
Err(e) => {
let msg = crate::wasm::shared::format_parse_errors(&e);
return fail(2, &format!("hypen_discover_routers: parse error: {msg}"));
}
};
let mut routers = Vec::new();
for component in &doc.components {
let ir = crate::ir::ast_to_ir_node(component);
routers.extend(crate::ir::discover_routers(&ir));
}
match serde_json::to_vec(&routers) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_discover_routers: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_diff_paths(
old_ptr: *const u8,
old_len: usize,
new_ptr: *const u8,
new_len: usize,
) -> i32 {
let old_str = match ptr_to_str(old_ptr, old_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_diff_paths: invalid UTF-8 old pointer"),
};
let new_str = match ptr_to_str(new_ptr, new_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_diff_paths: invalid UTF-8 new pointer"),
};
let old_val: serde_json::Value = match serde_json::from_str(old_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_diff_paths: bad old JSON: {e}")),
};
let new_val: serde_json::Value = match serde_json::from_str(new_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_diff_paths: bad new JSON: {e}")),
};
let entries: Vec<serde_json::Value> = crate::portable::diff_paths(&old_val, &new_val)
.into_iter()
.map(|e| serde_json::json!({ "path": e.path, "value": e.new_value }))
.collect();
match serde_json::to_vec(&entries) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_diff_paths: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_match_path(
pattern_ptr: *const u8,
pattern_len: usize,
path_ptr: *const u8,
path_len: usize,
) -> i32 {
let pattern = match ptr_to_str(pattern_ptr, pattern_len) {
Ok(s) => s,
Err(_) => {
return fail(
1,
"hypen_portable_match_path: invalid UTF-8 pattern pointer",
)
}
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_match_path: invalid UTF-8 path pointer"),
};
let json = match crate::portable::match_path(pattern, path) {
Some(m) => {
let params: serde_json::Map<String, serde_json::Value> = m
.params
.into_iter()
.map(|(k, v)| (k, serde_json::Value::String(v)))
.collect();
serde_json::json!({ "matched": true, "params": params })
}
None => serde_json::json!({ "matched": false, "params": {} }),
};
match serde_json::to_vec(&json) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_match_path: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_session_step(
state_ptr: *const u8,
state_len: usize,
event_ptr: *const u8,
event_len: usize,
) -> i32 {
let state_str = match ptr_to_str(state_ptr, state_len) {
Ok(s) => s,
Err(_) => {
return fail(
1,
"hypen_portable_session_step: invalid UTF-8 state pointer",
)
}
};
let event_str = match ptr_to_str(event_ptr, event_len) {
Ok(s) => s,
Err(_) => {
return fail(
1,
"hypen_portable_session_step: invalid UTF-8 event pointer",
)
}
};
let state: crate::portable::SessionState = match serde_json::from_str(state_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_session_step: bad state: {e}")),
};
let event: crate::portable::SessionEvent = match serde_json::from_str(event_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_session_step: bad event: {e}")),
};
let effect = crate::portable::session_step(&state, &event);
match serde_json::to_vec(&effect) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_session_step: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_get_portable_result_len() -> usize {
PORTABLE_BUFFER.with(|buf| buf.borrow().len())
}
#[no_mangle]
pub extern "C" fn hypen_get_portable_result(out_ptr: *mut u8, out_len: usize) -> usize {
PORTABLE_BUFFER.with(|buf| {
let src = buf.borrow();
let copy_len = src.len().min(out_len);
if copy_len > 0 && !out_ptr.is_null() {
unsafe {
std::ptr::copy_nonoverlapping(src.as_ptr(), out_ptr, copy_len);
}
}
copy_len
})
}
#[no_mangle]
pub extern "C" fn hypen_clear_portable_result() {
PORTABLE_BUFFER.with(|buf| buf.borrow_mut().clear());
}
#[no_mangle]
pub extern "C" fn hypen_portable_path_get(
value_ptr: *const u8,
value_len: usize,
path_ptr: *const u8,
path_len: usize,
) -> i32 {
let value_str = match ptr_to_str(value_ptr, value_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_get: invalid UTF-8 value pointer"),
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_get: invalid UTF-8 path pointer"),
};
let v: serde_json::Value = match serde_json::from_str(value_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_path_get: bad JSON: {e}")),
};
let out = crate::portable::path_get(&v, path).unwrap_or(serde_json::Value::Null);
match serde_json::to_vec(&out) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_path_get: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_path_has(
value_ptr: *const u8,
value_len: usize,
path_ptr: *const u8,
path_len: usize,
) -> i32 {
let value_str = match ptr_to_str(value_ptr, value_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_has: invalid UTF-8 value pointer"),
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_has: invalid UTF-8 path pointer"),
};
let v: serde_json::Value = match serde_json::from_str(value_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_path_has: bad JSON: {e}")),
};
let out = if crate::portable::path_has(&v, path) {
"true"
} else {
"false"
};
write_portable_result(out.as_bytes().to_vec());
0
}
#[no_mangle]
pub extern "C" fn hypen_portable_path_set(
value_ptr: *const u8,
value_len: usize,
path_ptr: *const u8,
path_len: usize,
new_value_ptr: *const u8,
new_value_len: usize,
) -> i32 {
let value_str = match ptr_to_str(value_ptr, value_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_set: invalid UTF-8 value pointer"),
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_set: invalid UTF-8 path pointer"),
};
let new_value_str = match ptr_to_str(new_value_ptr, new_value_len) {
Ok(s) => s,
Err(_) => {
return fail(
1,
"hypen_portable_path_set: invalid UTF-8 new-value pointer",
)
}
};
let mut v: serde_json::Value = match serde_json::from_str(value_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_path_set: bad value JSON: {e}")),
};
let nv: serde_json::Value = match serde_json::from_str(new_value_str) {
Ok(v) => v,
Err(e) => {
return fail(
2,
&format!("hypen_portable_path_set: bad new-value JSON: {e}"),
)
}
};
crate::portable::path_set(&mut v, path, nv);
match serde_json::to_vec(&v) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_path_set: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_path_delete(
value_ptr: *const u8,
value_len: usize,
path_ptr: *const u8,
path_len: usize,
) -> i32 {
let value_str = match ptr_to_str(value_ptr, value_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_delete: invalid UTF-8 value pointer"),
};
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_path_delete: invalid UTF-8 path pointer"),
};
let mut v: serde_json::Value = match serde_json::from_str(value_str) {
Ok(v) => v,
Err(e) => return fail(2, &format!("hypen_portable_path_delete: bad JSON: {e}")),
};
let removed = crate::portable::path_delete(&mut v, path);
let out = serde_json::json!({ "json": v, "removed": removed });
match serde_json::to_vec(&out) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_path_delete: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_encode_uri_component(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
let input = match ptr_to_str(input_ptr, input_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_encode_uri_component: invalid UTF-8"),
};
write_portable_result(crate::portable::encode_uri_component(input).into_bytes());
0
}
#[no_mangle]
pub extern "C" fn hypen_portable_decode_uri_component(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
let input = match ptr_to_str(input_ptr, input_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_decode_uri_component: invalid UTF-8"),
};
write_portable_result(crate::portable::decode_uri_component(input).into_bytes());
0
}
#[no_mangle]
pub extern "C" fn hypen_portable_parse_query(input_ptr: *const u8, input_len: usize) -> i32 {
let input = match ptr_to_str(input_ptr, input_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_parse_query: invalid UTF-8"),
};
let (path, query) = crate::portable::parse_query(input);
let out = serde_json::json!({ "path": path, "query": query });
match serde_json::to_vec(&out) {
Ok(bytes) => {
write_portable_result(bytes);
0
}
Err(e) => fail(3, &format!("hypen_portable_parse_query: serialise: {e}")),
}
}
#[no_mangle]
pub extern "C" fn hypen_portable_build_url(
path_ptr: *const u8,
path_len: usize,
query_ptr: *const u8,
query_len: usize,
) -> i32 {
let path = match ptr_to_str(path_ptr, path_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_build_url: invalid UTF-8 path"),
};
let query_str = match ptr_to_str(query_ptr, query_len) {
Ok(s) => s,
Err(_) => return fail(1, "hypen_portable_build_url: invalid UTF-8 query"),
};
let map: std::collections::BTreeMap<String, String> = match serde_json::from_str(query_str) {
Ok(m) => m,
Err(e) => return fail(2, &format!("hypen_portable_build_url: bad query JSON: {e}")),
};
write_portable_result(crate::portable::build_url(path, &map).into_bytes());
0
}