#![allow(unsafe_code)]
use serde_json::Value;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::sync::Arc;
use webui_handler::plugin::fast_v2::FastV2HydrationPlugin;
use webui_handler::plugin::fast_v3::FastV3HydrationPlugin;
use webui_handler::plugin::webui::WebUIHydrationPlugin;
use webui_handler::{
BoundaryDescriptor, BoundaryInstanceId, BoundaryKey, BoundaryMode, Protocol, RenderOptions,
SessionOptions, StreamStep, StreamingSession, WebUIHandler,
};
#[allow(non_camel_case_types)]
pub type webui_protocol_t = c_void;
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
fn set_last_error(msg: impl Into<String>) {
let mut bytes = msg.into().into_bytes();
if let Some(nul_pos) = bytes.iter().position(|byte| *byte == 0) {
bytes.truncate(nul_pos);
}
let c_string = unsafe { CString::from_vec_unchecked(bytes) };
LAST_ERROR.with(|cell| {
cell.replace(Some(c_string));
});
}
fn clear_last_error() {
LAST_ERROR.with(|cell| {
cell.replace(None);
});
}
struct HandlerContext {
handler: Arc<WebUIHandler>,
nonce: Option<String>,
}
struct ProtocolContext {
protocol: Arc<Protocol>,
}
webui_handler::define_string_response_writer!(StringResponseWriter, content);
#[no_mangle]
pub extern "C" fn webui_last_error() -> *const c_char {
LAST_ERROR.with(|cell| {
let borrow = cell.borrow();
match borrow.as_ref() {
Some(c_str) => c_str.as_ptr(),
None => std::ptr::null(),
}
})
}
#[no_mangle]
pub extern "C" fn webui_handler_create() -> *mut c_void {
let handler = WebUIHandler::new();
let context = Box::new(HandlerContext {
handler: Arc::new(handler),
nonce: None,
});
Box::into_raw(context) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn webui_handler_create_with_plugin(plugin_id: *const c_char) -> *mut c_void {
clear_last_error();
let handler = if plugin_id.is_null() {
WebUIHandler::new()
} else {
match CStr::from_ptr(plugin_id).to_str() {
Ok("fast" | "fast-v2") => {
WebUIHandler::with_plugin(|| Box::new(FastV2HydrationPlugin::new()))
}
Ok("fast-v3") => WebUIHandler::with_plugin(|| Box::new(FastV3HydrationPlugin::new())),
Ok("webui") => WebUIHandler::with_plugin(|| Box::new(WebUIHydrationPlugin::new())),
Ok(unknown) => {
set_last_error(format!(
"unknown plugin: {unknown}. Use \"webui\", \"fast-v3\", \"fast-v2\", or \"fast\"."
));
return std::ptr::null_mut();
}
Err(e) => {
set_last_error(format!("invalid UTF-8 in plugin_id: {e}"));
return std::ptr::null_mut();
}
}
};
let context = Box::new(HandlerContext {
handler: Arc::new(handler),
nonce: None,
});
Box::into_raw(context) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn webui_handler_destroy(handler_ptr: *mut c_void) {
if !handler_ptr.is_null() {
let _ = Box::from_raw(handler_ptr as *mut HandlerContext);
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_protocol_create(
protocol_data: *const u8,
protocol_len: usize,
) -> *mut webui_protocol_t {
clear_last_error();
if protocol_data.is_null() {
set_last_error("protocol_data is null");
return std::ptr::null_mut();
}
match std::panic::catch_unwind(|| {
let bytes = unsafe { std::slice::from_raw_parts(protocol_data, protocol_len) };
match Protocol::from_protobuf(bytes) {
Ok(protocol) => Box::into_raw(Box::new(ProtocolContext {
protocol: Arc::new(protocol),
})) as *mut webui_protocol_t,
Err(error) => {
set_last_error(format!("failed to parse protobuf protocol: {error}"));
std::ptr::null_mut()
}
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_protocol_create");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_protocol_destroy(protocol_ptr: *mut webui_protocol_t) {
if !protocol_ptr.is_null() {
let _ = unsafe { Box::from_raw(protocol_ptr as *mut ProtocolContext) };
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_handler_set_nonce(handler_ptr: *mut c_void, nonce: *const c_char) {
clear_last_error();
if handler_ptr.is_null() {
set_last_error("handler_ptr is null");
return;
}
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let context = &mut *(handler_ptr as *mut HandlerContext);
if nonce.is_null() {
context.nonce = None;
} else {
match CStr::from_ptr(nonce).to_str() {
Ok(s) => context.nonce = Some(s.to_string()),
Err(e) => {
set_last_error(format!("invalid UTF-8 in nonce: {e}"));
}
}
}
})) {
Ok(()) => {}
Err(_) => {
set_last_error("panic in webui_handler_set_nonce");
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_handler_render(
handler_ptr: *mut c_void,
protocol_ptr: *const webui_protocol_t,
data_json: *const c_char,
entry_id: *const c_char,
request_path: *const c_char,
) -> *mut c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if handler_ptr.is_null()
|| protocol_ptr.is_null()
|| data_json.is_null()
|| entry_id.is_null()
|| request_path.is_null()
{
set_last_error("one or more required arguments are null");
return std::ptr::null_mut();
}
let context = unsafe { &*(handler_ptr as *const HandlerContext) };
let protocol_context = unsafe { &*(protocol_ptr as *const ProtocolContext) };
unsafe {
render_decoded_protocol(
context,
&protocol_context.protocol,
data_json,
entry_id,
request_path,
)
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_handler_render");
std::ptr::null_mut()
}
}
}
unsafe fn render_decoded_protocol(
context: &HandlerContext,
protocol: &Protocol,
data_json: *const c_char,
entry_id: *const c_char,
request_path: *const c_char,
) -> *mut c_char {
let data_str = match unsafe { CStr::from_ptr(data_json) }.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("invalid UTF-8 in data_json: {e}"));
return std::ptr::null_mut();
}
};
let entry_str = match unsafe { CStr::from_ptr(entry_id) }.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("invalid UTF-8 in entry_id: {e}"));
return std::ptr::null_mut();
}
};
let path_str = match unsafe { CStr::from_ptr(request_path) }.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("invalid UTF-8 in request_path: {e}"));
return std::ptr::null_mut();
}
};
let data: Value = match serde_json::from_str(data_str) {
Ok(d) => d,
Err(e) => {
set_last_error(format!("failed to parse data JSON: {e}"));
return std::ptr::null_mut();
}
};
let mut options = RenderOptions::new(entry_str, path_str);
if let Some(ref nonce) = context.nonce {
options = options.with_nonce(nonce);
}
let mut writer = StringResponseWriter::with_capacity(0);
match context
.handler
.render(protocol, &data, &options, &mut writer)
{
Ok(_) => match CString::new(writer.content) {
Ok(s) => s.into_raw(),
Err(e) => {
set_last_error(format!("rendered output contains interior NUL byte: {e}"));
std::ptr::null_mut()
}
},
Err(e) => {
set_last_error(format!("render failed: {e}"));
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_protocol_render_partial(
protocol_ptr: *const webui_protocol_t,
state_json: *const c_char,
entry_id: *const c_char,
request_path: *const c_char,
inventory_hex: *const c_char,
) -> *mut c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if protocol_ptr.is_null()
|| state_json.is_null()
|| entry_id.is_null()
|| request_path.is_null()
|| inventory_hex.is_null()
{
set_last_error("one or more required arguments are null");
return std::ptr::null_mut();
}
let protocol_context = unsafe { &*(protocol_ptr as *const ProtocolContext) };
let state_str = match unsafe { CStr::from_ptr(state_json) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in state_json: {error}"));
return std::ptr::null_mut();
}
};
let entry_str = match unsafe { CStr::from_ptr(entry_id) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in entry_id: {error}"));
return std::ptr::null_mut();
}
};
let request_path_str = match unsafe { CStr::from_ptr(request_path) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in request_path: {error}"));
return std::ptr::null_mut();
}
};
let inventory_str = match unsafe { CStr::from_ptr(inventory_hex) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in inventory_hex: {error}"));
return std::ptr::null_mut();
}
};
let output = match protocol_context.protocol.render_partial(
state_str,
entry_str,
request_path_str,
inventory_str,
) {
Ok(value) => value,
Err(error) => {
set_last_error(format!("render_partial failed: {error}"));
return std::ptr::null_mut();
}
};
match CString::new(output) {
Ok(value) => value.into_raw(),
Err(error) => {
set_last_error(format!("JSON output contains interior NUL byte: {error}"));
std::ptr::null_mut()
}
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_protocol_render_partial");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_protocol_render_component_templates(
protocol_ptr: *const webui_protocol_t,
component_tags_json: *const c_char,
inventory_hex: *const c_char,
) -> *mut c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if protocol_ptr.is_null() || component_tags_json.is_null() || inventory_hex.is_null() {
set_last_error("one or more required arguments are null");
return std::ptr::null_mut();
}
let protocol_context = unsafe { &*(protocol_ptr as *const ProtocolContext) };
let tags_str = match unsafe { CStr::from_ptr(component_tags_json) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in component_tags_json: {error}"));
return std::ptr::null_mut();
}
};
let tags: Vec<String> = match serde_json::from_str(tags_str) {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid tags JSON: {error}"));
return std::ptr::null_mut();
}
};
let tag_refs: Vec<&str> = tags.iter().map(String::as_str).collect();
let inventory_str = match unsafe { CStr::from_ptr(inventory_hex) }.to_str() {
Ok(value) => value,
Err(error) => {
set_last_error(format!("invalid UTF-8 in inventory_hex: {error}"));
return std::ptr::null_mut();
}
};
let result = match protocol_context
.protocol
.render_component_templates(&tag_refs, inventory_str)
{
Ok(value) => value,
Err(error) => {
set_last_error(format!("render_component_templates failed: {error}"));
return std::ptr::null_mut();
}
};
match CString::new(result.to_string()) {
Ok(value) => value.into_raw(),
Err(error) => {
set_last_error(format!("JSON output contains interior NUL byte: {error}"));
std::ptr::null_mut()
}
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_protocol_render_component_templates");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_free(string_ptr: *mut c_char) {
if !string_ptr.is_null() {
let _ = CString::from_raw(string_ptr);
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_protocol_tokens(
protocol_ptr: *const webui_protocol_t,
) -> *mut c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if protocol_ptr.is_null() {
set_last_error("protocol_ptr is null");
return std::ptr::null_mut();
}
let protocol_context = unsafe { &*(protocol_ptr as *const ProtocolContext) };
let joined = protocol_context.protocol.tokens().join("\n");
match CString::new(joined) {
Ok(value) => value.into_raw(),
Err(error) => {
set_last_error(format!("token string contains null byte: {error}"));
std::ptr::null_mut()
}
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_protocol_tokens");
std::ptr::null_mut()
}
}
}
#[allow(non_camel_case_types)]
pub type webui_streaming_session_t = c_void;
#[allow(non_camel_case_types)]
pub type webui_streaming_step_t = c_void;
#[allow(non_camel_case_types)]
pub type webui_boundary_mode_t = u32;
pub const WEBUI_BOUNDARY_MODE_FINAL: webui_boundary_mode_t = 0;
pub const WEBUI_BOUNDARY_MODE_UPDATABLE: webui_boundary_mode_t = 1;
#[allow(non_camel_case_types)]
pub type webui_boundary_key_type_t = u32;
pub const WEBUI_BOUNDARY_KEY_NONE: webui_boundary_key_type_t = 0;
pub const WEBUI_BOUNDARY_KEY_STRING: webui_boundary_key_type_t = 1;
pub const WEBUI_BOUNDARY_KEY_NUMBER: webui_boundary_key_type_t = 2;
struct StreamingSessionContext {
session: StreamingSession,
}
struct StreamingStepContext {
step: StreamStep,
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_create(
handler_ptr: *mut c_void,
protocol_ptr: *const webui_protocol_t,
entry_id: *const c_char,
request_path: *const c_char,
) -> *mut webui_streaming_session_t {
clear_last_error();
match std::panic::catch_unwind(|| {
if handler_ptr.is_null()
|| protocol_ptr.is_null()
|| entry_id.is_null()
|| request_path.is_null()
{
set_last_error("one or more required arguments are null");
return std::ptr::null_mut();
}
let handler_context = unsafe { &*(handler_ptr as *const HandlerContext) };
let protocol_context = unsafe { &*(protocol_ptr as *const ProtocolContext) };
let Some(entry) = (unsafe { utf8_arg(entry_id, "entry_id") }) else {
return std::ptr::null_mut();
};
let Some(path) = (unsafe { utf8_arg(request_path, "request_path") }) else {
return std::ptr::null_mut();
};
let mut options = SessionOptions::new(entry, path);
options.nonce = handler_context.nonce.clone();
match StreamingSession::new(
Arc::clone(&handler_context.handler),
Arc::clone(&protocol_context.protocol),
options,
) {
Ok(session) => Box::into_raw(Box::new(StreamingSessionContext { session }))
as *mut webui_streaming_session_t,
Err(error) => {
set_last_error(error.to_string());
std::ptr::null_mut()
}
}
}) {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("panic in webui_streaming_session_create");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_destroy(
session_ptr: *mut webui_streaming_session_t,
) {
clear_last_error();
if session_ptr.is_null() {
return;
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
drop(unsafe { Box::from_raw(session_ptr as *mut StreamingSessionContext) });
}));
if result.is_err() {
set_last_error("panic in webui_streaming_session_destroy");
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_start(
session_ptr: *mut webui_streaming_session_t,
state_json: *const c_char,
) -> *mut webui_streaming_step_t {
clear_last_error();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if session_ptr.is_null() || state_json.is_null() {
set_last_error("session_ptr and state_json must not be null");
return std::ptr::null_mut();
}
let context = unsafe { &mut *(session_ptr as *mut StreamingSessionContext) };
let Some(state) = (unsafe { streaming_json_arg(state_json, "state_json") }) else {
return std::ptr::null_mut();
};
match context.session.start(&state) {
Ok(step) => owned_streaming_step(step),
Err(error) => {
set_last_error(error.to_string());
std::ptr::null_mut()
}
}
})) {
Ok(step) => step,
Err(_) => {
set_last_error("panic in webui_streaming_session_start");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_resume(
session_ptr: *mut webui_streaming_session_t,
instance_id: u32,
state_json: *const c_char,
mode: webui_boundary_mode_t,
) -> *mut webui_streaming_step_t {
clear_last_error();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if session_ptr.is_null() || state_json.is_null() {
set_last_error("session_ptr and state_json must not be null");
return std::ptr::null_mut();
}
let Some(mode) = streaming_boundary_mode(mode) else {
return std::ptr::null_mut();
};
let context = unsafe { &mut *(session_ptr as *mut StreamingSessionContext) };
let Some(state) = (unsafe { streaming_json_arg(state_json, "state_json") }) else {
return std::ptr::null_mut();
};
match context
.session
.resume(BoundaryInstanceId::from_raw(instance_id), &state, mode)
{
Ok(step) => owned_streaming_step(step),
Err(error) => {
set_last_error(error.to_string());
std::ptr::null_mut()
}
}
})) {
Ok(step) => step,
Err(_) => {
set_last_error("panic in webui_streaming_session_resume");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_advance(
session_ptr: *mut webui_streaming_session_t,
) -> *mut webui_streaming_step_t {
clear_last_error();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if session_ptr.is_null() {
set_last_error("session_ptr must not be null");
return std::ptr::null_mut();
}
let context = unsafe { &mut *(session_ptr as *mut StreamingSessionContext) };
match context.session.advance() {
Ok(step) => owned_streaming_step(step),
Err(error) => {
set_last_error(error.to_string());
std::ptr::null_mut()
}
}
})) {
Ok(step) => step,
Err(_) => {
set_last_error("panic in webui_streaming_session_advance");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_session_update(
session_ptr: *mut webui_streaming_session_t,
instance_id: u32,
patch_json: *const c_char,
out_len: *mut usize,
) -> *mut c_char {
clear_last_error();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if session_ptr.is_null() || patch_json.is_null() || out_len.is_null() {
set_last_error("session_ptr, patch_json, and out_len must not be null");
return std::ptr::null_mut();
}
let context = unsafe { &mut *(session_ptr as *mut StreamingSessionContext) };
let Some(patch) = (unsafe { streaming_json_arg(patch_json, "patch_json") }) else {
return std::ptr::null_mut();
};
let bytes = match context
.session
.update(BoundaryInstanceId::from_raw(instance_id), &patch)
{
Ok(bytes) => bytes,
Err(error) => {
set_last_error(error.to_string());
return std::ptr::null_mut();
}
};
let length = bytes.len();
match CString::new(bytes) {
Ok(chunk) => {
unsafe { *out_len = length };
chunk.into_raw()
}
Err(error) => {
set_last_error(format!(
"streaming update contains an interior NUL byte: {error}"
));
std::ptr::null_mut()
}
}
})) {
Ok(bytes) => bytes,
Err(_) => {
set_last_error("panic in webui_streaming_session_update");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_destroy(step_ptr: *mut webui_streaming_step_t) {
clear_last_error();
if step_ptr.is_null() {
return;
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
drop(unsafe { Box::from_raw(step_ptr as *mut StreamingStepContext) });
}));
if result.is_err() {
set_last_error("panic in webui_streaming_step_destroy");
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_bytes(
step_ptr: *const webui_streaming_step_t,
out_len: *mut usize,
) -> *const u8 {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_len.is_null() {
set_last_error("step_ptr and out_len must not be null");
return std::ptr::null();
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
unsafe { *out_len = context.step.bytes.len() };
context.step.bytes.as_ptr()
}) {
Ok(bytes) => bytes,
Err(_) => {
set_last_error("panic in webui_streaming_step_bytes");
std::ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_done(
step_ptr: *const webui_streaming_step_t,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() {
set_last_error("step_ptr is null");
return false;
}
unsafe { (*(step_ptr as *const StreamingStepContext)).step.done }
}) {
Ok(done) => done,
Err(_) => {
set_last_error("panic in webui_streaming_step_done");
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_has_boundary(
step_ptr: *const webui_streaming_step_t,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() {
set_last_error("step_ptr is null");
return false;
}
unsafe {
(*(step_ptr as *const StreamingStepContext))
.step
.boundary
.is_some()
}
}) {
Ok(has_boundary) => has_boundary,
Err(_) => {
set_last_error("panic in webui_streaming_step_has_boundary");
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_instance_id(
step_ptr: *const webui_streaming_step_t,
out_instance_id: *mut u32,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_instance_id.is_null() {
set_last_error("step_ptr and out_instance_id must not be null");
return false;
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return false;
};
unsafe { *out_instance_id = boundary.instance_id.raw() };
true
}) {
Ok(ok) => ok,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_instance_id");
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_declaration_id(
step_ptr: *const webui_streaming_step_t,
out_declaration_id: *mut u32,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_declaration_id.is_null() {
set_last_error("step_ptr and out_declaration_id must not be null");
return false;
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return false;
};
unsafe { *out_declaration_id = boundary.declaration_id };
true
}) {
Ok(ok) => ok,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_declaration_id");
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_owner(
step_ptr: *const webui_streaming_step_t,
out_len: *mut usize,
) -> *const c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_len.is_null() {
set_last_error("step_ptr and out_len must not be null");
return std::ptr::null();
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return std::ptr::null();
};
unsafe { *out_len = boundary.owner.len() };
boundary.owner.as_ptr().cast()
}) {
Ok(owner) => owner,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_owner");
std::ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_name(
step_ptr: *const webui_streaming_step_t,
out_len: *mut usize,
) -> *const c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_len.is_null() {
set_last_error("step_ptr and out_len must not be null");
return std::ptr::null();
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return std::ptr::null();
};
unsafe { *out_len = boundary.name.len() };
boundary.name.as_ptr().cast()
}) {
Ok(name) => name,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_name");
std::ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_key_type(
step_ptr: *const webui_streaming_step_t,
out_key_type: *mut webui_boundary_key_type_t,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_key_type.is_null() {
set_last_error("step_ptr and out_key_type must not be null");
return false;
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return false;
};
let key_type = match boundary.key {
None => WEBUI_BOUNDARY_KEY_NONE,
Some(BoundaryKey::String(_)) => WEBUI_BOUNDARY_KEY_STRING,
Some(BoundaryKey::Number(_)) => WEBUI_BOUNDARY_KEY_NUMBER,
};
unsafe { *out_key_type = key_type };
true
}) {
Ok(ok) => ok,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_key_type");
false
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_key_string(
step_ptr: *const webui_streaming_step_t,
out_len: *mut usize,
) -> *const c_char {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_len.is_null() {
set_last_error("step_ptr and out_len must not be null");
return std::ptr::null();
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return std::ptr::null();
};
let Some(BoundaryKey::String(value)) = boundary.key.as_ref() else {
set_last_error("pending boundary key is not a string");
return std::ptr::null();
};
unsafe { *out_len = value.len() };
value.as_ptr().cast()
}) {
Ok(value) => value,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_key_string");
std::ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn webui_streaming_step_boundary_key_number(
step_ptr: *const webui_streaming_step_t,
out_value: *mut f64,
) -> bool {
clear_last_error();
match std::panic::catch_unwind(|| {
if step_ptr.is_null() || out_value.is_null() {
set_last_error("step_ptr and out_value must not be null");
return false;
}
let context = unsafe { &*(step_ptr as *const StreamingStepContext) };
let Some(boundary) = streaming_step_boundary(context) else {
return false;
};
let Some(BoundaryKey::Number(value)) = boundary.key.as_ref() else {
set_last_error("pending boundary key is not a number");
return false;
};
let Some(value) = value.as_f64() else {
set_last_error("pending boundary key cannot be represented as a finite number");
return false;
};
unsafe { *out_value = value };
true
}) {
Ok(ok) => ok,
Err(_) => {
set_last_error("panic in webui_streaming_step_boundary_key_number");
false
}
}
}
fn owned_streaming_step(step: StreamStep) -> *mut webui_streaming_step_t {
Box::into_raw(Box::new(StreamingStepContext { step })) as *mut webui_streaming_step_t
}
fn streaming_step_boundary(context: &StreamingStepContext) -> Option<&BoundaryDescriptor> {
match context.step.boundary.as_ref() {
Some(boundary) => Some(boundary),
None => {
set_last_error("streaming step has no pending boundary");
None
}
}
}
fn streaming_boundary_mode(mode: webui_boundary_mode_t) -> Option<BoundaryMode> {
match mode {
WEBUI_BOUNDARY_MODE_FINAL => Some(BoundaryMode::Final),
WEBUI_BOUNDARY_MODE_UPDATABLE => Some(BoundaryMode::Updatable),
_ => {
set_last_error(format!(
"invalid boundary mode {mode}; expected WEBUI_BOUNDARY_MODE_FINAL (0) \
or WEBUI_BOUNDARY_MODE_UPDATABLE (1)"
));
None
}
}
}
unsafe fn streaming_json_arg(value: *const c_char, name: &str) -> Option<Value> {
let text = unsafe { utf8_arg(value, name) }?;
match serde_json::from_str(text) {
Ok(value) => Some(value),
Err(error) => {
set_last_error(format!("failed to parse {name}: {error}"));
None
}
}
}
unsafe fn utf8_arg<'a>(value: *const c_char, name: &str) -> Option<&'a str> {
match unsafe { CStr::from_ptr(value) }.to_str() {
Ok(text) => Some(text),
Err(error) => {
set_last_error(format!("invalid UTF-8 in {name}: {error}"));
None
}
}
}