vtx_sdk/modules/io/
stream.rs

1//! Host-side stream I/O helpers (Buffer).
2
3use crate::bindings::vtx::api::vtx_vfs;
4use crate::bindings::vtx::api::vtx_vfs::Buffer;
5use crate::error::{VtxError, VtxResult};
6use serde::de::DeserializeOwned;
7
8pub type StreamBuffer = Buffer;
9
10/// Opens a file via the host UUID and returns a `Buffer` resource handle.
11pub fn open_uri(uri: &str) -> VtxResult<Buffer> {
12    vtx_vfs::open_uri(uri).map_err(VtxError::from_host_message)
13}
14
15pub fn open_file(uuid: &str) -> VtxResult<Buffer> {
16    open_uri(uuid)
17}
18
19/// Creates an in-memory Buffer (typically used for constructing `HttpResponse.body`).
20pub fn memory_buffer(data: impl AsRef<[u8]>) -> Buffer {
21    vtx_vfs::create_memory_buffer(data.as_ref())
22}
23
24/// Convenience extension methods for `Buffer` resources.
25pub trait BufferExt {
26    /// Reads the entire Buffer.
27    ///
28    /// - For file/memory: Uses `size()` for chunked reading.
29    /// - For pipe: Reads continuously until EOF (returns empty array) or `max_total_bytes` is reached.
30    ///
31    /// **IO Boundary:** This method reads a maximum of 64MB (`MAX_TOTAL`).
32    fn read_all(&self) -> Vec<u8>;
33
34    /// Reads the entire Buffer as UTF-8.
35    fn read_to_string(&self) -> VtxResult<String>;
36
37    /// Deserializes JSON in the Buffer into the target type.
38    fn read_json<T: DeserializeOwned>(&self) -> VtxResult<T>;
39
40    /// Appends data to the Buffer (File: append; Pipe: write to stdin; Memory: append).
41    fn write_all(&self, data: impl AsRef<[u8]>) -> u64;
42}
43
44impl BufferExt for Buffer {
45    fn read_all(&self) -> Vec<u8> {
46        const CHUNK: u64 = 64 * 1024;
47        const MAX_TOTAL: usize = 64 * 1024 * 1024;
48
49        let mut out = Vec::new();
50
51        let total = self.size();
52        if total > 0 {
53            let mut offset = 0u64;
54            while offset < total && out.len() < MAX_TOTAL {
55                let to_read = std::cmp::min(CHUNK, total - offset);
56                let chunk = self.read(offset, to_read);
57                if chunk.is_empty() {
58                    break;
59                }
60                out.extend_from_slice(&chunk);
61                offset += chunk.len() as u64;
62            }
63            return out;
64        }
65
66        // Pipe mode: size unknown, read until empty (EOF).
67        while out.len() < MAX_TOTAL {
68            let chunk = self.read(0, CHUNK);
69            if chunk.is_empty() {
70                break;
71            }
72            out.extend_from_slice(&chunk);
73        }
74
75        out
76    }
77
78    fn read_to_string(&self) -> VtxResult<String> {
79        let bytes = self.read_all();
80        String::from_utf8(bytes).map_err(|e| VtxError::SerializationError(e.to_string()))
81    }
82
83    fn read_json<T: DeserializeOwned>(&self) -> VtxResult<T> {
84        let s = self.read_to_string()?;
85        serde_json::from_str(&s).map_err(|e| VtxError::SerializationError(e.to_string()))
86    }
87
88    fn write_all(&self, data: impl AsRef<[u8]>) -> u64 {
89        self.write(data.as_ref())
90    }
91}