vtx-sdk 0.1.14

Official SDK for developing VTX plugins using Rust and WebAssembly.
Documentation
//! Host-side stream I/O helpers (Buffer).

use crate::bindings::vtx::api::vtx_vfs;
use crate::bindings::vtx::api::vtx_vfs::Buffer;
use crate::error::{VtxError, VtxResult};
use serde::de::DeserializeOwned;

pub type StreamBuffer = Buffer;

/// Opens a file via the host UUID and returns a `Buffer` resource handle.
pub fn open_uri(uri: &str) -> VtxResult<Buffer> {
    vtx_vfs::open_uri(uri).map_err(VtxError::from_host_message)
}

pub fn open_file(uuid: &str) -> VtxResult<Buffer> {
    open_uri(uuid)
}

/// Creates an in-memory Buffer (typically used for constructing `HttpResponse.body`).
pub fn memory_buffer(data: impl AsRef<[u8]>) -> Buffer {
    vtx_vfs::create_memory_buffer(data.as_ref())
}

/// Convenience extension methods for `Buffer` resources.
pub trait BufferExt {
    /// Reads the entire Buffer.
    ///
    /// - For file/memory: Uses `size()` for chunked reading.
    /// - For pipe: Reads continuously until EOF (returns empty array) or `max_total_bytes` is reached.
    ///
    /// **IO Boundary:** This method reads a maximum of 64MB (`MAX_TOTAL`).
    fn read_all(&self) -> Vec<u8>;

    /// Reads the entire Buffer as UTF-8.
    fn read_to_string(&self) -> VtxResult<String>;

    /// Deserializes JSON in the Buffer into the target type.
    fn read_json<T: DeserializeOwned>(&self) -> VtxResult<T>;

    /// Appends data to the Buffer (File: append; Pipe: write to stdin; Memory: append).
    fn write_all(&self, data: impl AsRef<[u8]>) -> u64;
}

impl BufferExt for Buffer {
    fn read_all(&self) -> Vec<u8> {
        const CHUNK: u64 = 64 * 1024;
        const MAX_TOTAL: usize = 64 * 1024 * 1024;

        let mut out = Vec::new();

        let total = self.size();
        if total > 0 {
            let mut offset = 0u64;
            while offset < total && out.len() < MAX_TOTAL {
                let to_read = std::cmp::min(CHUNK, total - offset);
                let chunk = self.read(offset, to_read);
                if chunk.is_empty() {
                    break;
                }
                out.extend_from_slice(&chunk);
                offset += chunk.len() as u64;
            }
            return out;
        }

        // Pipe mode: size unknown, read until empty (EOF).
        while out.len() < MAX_TOTAL {
            let chunk = self.read(0, CHUNK);
            if chunk.is_empty() {
                break;
            }
            out.extend_from_slice(&chunk);
        }

        out
    }

    fn read_to_string(&self) -> VtxResult<String> {
        let bytes = self.read_all();
        String::from_utf8(bytes).map_err(|e| VtxError::SerializationError(e.to_string()))
    }

    fn read_json<T: DeserializeOwned>(&self) -> VtxResult<T> {
        let s = self.read_to_string()?;
        serde_json::from_str(&s).map_err(|e| VtxError::SerializationError(e.to_string()))
    }

    fn write_all(&self, data: impl AsRef<[u8]>) -> u64 {
        self.write(data.as_ref())
    }
}