Skip to main content

mimobox_sdk/
types.rs

1#[cfg(all(feature = "vm", target_os = "linux"))]
2use crate::config::Config;
3use crate::error::SdkError;
4#[cfg(all(feature = "vm", target_os = "linux"))]
5use crate::vm_helpers::map_microvm_error;
6use crate::vm_helpers::{map_pty_session_error, map_snapshot_bytes_error};
7use mimobox_core::{PtyEvent, PtySize, SandboxResult};
8use std::path::{Path, PathBuf};
9#[cfg(all(feature = "vm", target_os = "linux"))]
10use std::sync::Arc;
11use std::sync::mpsc;
12
13/// Result of a sandbox command execution.
14///
15/// Contains raw stdout/stderr bytes, exit code, timeout flag, and wall-clock elapsed time.
16/// Use `String::from_utf8_lossy()` to convert stdout/stderr to text.
17///
18/// # Examples
19///
20/// ```rust,no_run
21/// use mimobox_sdk::Sandbox;
22///
23/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
24/// let mut sandbox = Sandbox::new()?;
25/// let result = sandbox.execute("/bin/echo hello")?;
26/// println!("exit: {:?}", result.exit_code);
27/// println!("stdout: {}", String::from_utf8_lossy(&result.stdout));
28/// println!("elapsed: {:?}", result.elapsed);
29/// # Ok(())
30/// # }
31/// ```
32#[non_exhaustive]
33pub struct ExecuteResult {
34    /// Standard output byte stream.
35    pub stdout: Vec<u8>,
36    /// Standard error byte stream.
37    pub stderr: Vec<u8>,
38    /// Exit code; may be `None` if the process did not exit normally.
39    pub exit_code: Option<i32>,
40    /// Whether execution was terminated due to timeout.
41    pub timed_out: bool,
42    /// Total elapsed time for this execution.
43    pub elapsed: std::time::Duration,
44}
45
46impl ExecuteResult {
47    /// Constructs a new execution result.
48    ///
49    /// Because `ExecuteResult` is marked `#[non_exhaustive]`, external crates
50    /// cannot construct it with a struct literal.
51    /// This method provides a stable construction path.
52    pub fn new(
53        stdout: Vec<u8>,
54        stderr: Vec<u8>,
55        exit_code: Option<i32>,
56        timed_out: bool,
57        elapsed: std::time::Duration,
58    ) -> Self {
59        Self {
60            stdout,
61            stderr,
62            exit_code,
63            timed_out,
64            elapsed,
65        }
66    }
67}
68
69/// HTTP response from the controlled host-side proxy.
70///
71/// Returned by [`Sandbox::http_request()`]. Only available with the `vm` feature on Linux.
72///
73/// # Examples
74///
75/// ```rust,no_run,ignore
76/// // Requires `vm` feature + Linux
77/// use mimobox_sdk::{Config, IsolationLevel, Sandbox};
78/// use std::collections::HashMap;
79///
80/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
81/// let config = Config::builder()
82///     .isolation(IsolationLevel::MicroVm)
83///     .allowed_http_domains(["example.com"])
84///     .build();
85/// let mut sandbox = Sandbox::with_config(config)?;
86/// let resp = sandbox.http_request("GET", "https://example.com", HashMap::new(), None)?;
87/// println!("status: {}", resp.status);
88/// # Ok(())
89/// # }
90/// ```
91#[non_exhaustive]
92pub struct HttpResponse {
93    /// HTTP status code.
94    pub status: u16,
95    /// Normalized response headers.
96    pub headers: std::collections::HashMap<String, String>,
97    /// Response body byte stream.
98    pub body: Vec<u8>,
99}
100
101/// Opaque handle to a sandbox memory snapshot.
102///
103/// Supports both in-memory bytes and file-backed storage modes.
104/// Create via [`Sandbox::snapshot()`] or restore from bytes/file.
105///
106/// # Examples
107///
108/// ```rust,no_run
109/// use mimobox_sdk::{Config, IsolationLevel, Sandbox};
110///
111/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
112/// let config = Config::builder()
113///     .isolation(IsolationLevel::MicroVm)
114///     .build();
115/// let mut sandbox = Sandbox::with_config(config)?;
116/// let snapshot = sandbox.snapshot()?;
117/// println!("snapshot size: {} bytes", snapshot.size());
118/// # Ok(())
119/// # }
120/// ```
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct SandboxSnapshot {
123    pub(crate) inner: mimobox_core::SandboxSnapshot,
124}
125
126impl SandboxSnapshot {
127    /// Restores a snapshot from raw bytes.
128    pub fn from_bytes(data: &[u8]) -> Result<Self, SdkError> {
129        mimobox_core::SandboxSnapshot::from_bytes(data)
130            .map(Self::from_core)
131            .map_err(map_snapshot_bytes_error)
132    }
133
134    /// Creates a snapshot from a file-backed snapshot's `memory.bin`.
135    pub fn from_file(path: PathBuf) -> Result<Self, SdkError> {
136        mimobox_core::SandboxSnapshot::from_file(path)
137            .map(Self::from_core)
138            .map_err(map_snapshot_bytes_error)
139    }
140
141    /// Returns the memory file path for a file-backed snapshot.
142    pub fn memory_file_path(&self) -> Option<&Path> {
143        self.inner.memory_file_path()
144    }
145
146    /// Returns a snapshot byte slice without an extra copy.
147    ///
148    /// Only in-memory snapshots support this operation; file-backed snapshots return an error.
149    pub fn as_bytes(&self) -> Result<&[u8], SdkError> {
150        self.inner.as_bytes().map_err(map_snapshot_bytes_error)
151    }
152
153    /// Returns a copied snapshot byte buffer.
154    ///
155    /// For file-backed snapshots, rebuilds the complete self-describing snapshot bytes.
156    pub fn to_bytes(&self) -> Result<Vec<u8>, SdkError> {
157        #[cfg(all(feature = "vm", target_os = "linux"))]
158        if let Some(memory_path) = self.inner.memory_file_path() {
159            return mimobox_vm::MicrovmSnapshot::from_memory_file(memory_path)
160                .and_then(|snapshot| snapshot.snapshot())
161                .map_err(map_microvm_error);
162        }
163
164        self.inner.to_bytes().map_err(map_snapshot_bytes_error)
165    }
166
167    /// Consumes the snapshot and returns the underlying bytes without an extra copy.
168    ///
169    /// For file-backed snapshots, rebuilds the complete self-describing snapshot bytes.
170    pub fn into_bytes(self) -> Result<Vec<u8>, SdkError> {
171        #[cfg(all(feature = "vm", target_os = "linux"))]
172        if let Some(memory_path) = self.inner.memory_file_path().map(Path::to_path_buf) {
173            return mimobox_vm::MicrovmSnapshot::from_memory_file(&memory_path)
174                .and_then(|snapshot| snapshot.snapshot())
175                .map_err(map_microvm_error);
176        }
177
178        self.inner.into_bytes().map_err(map_snapshot_bytes_error)
179    }
180
181    /// Returns the snapshot size in bytes.
182    pub fn size(&self) -> usize {
183        self.inner.size()
184    }
185
186    pub(crate) fn from_core(inner: mimobox_core::SandboxSnapshot) -> Self {
187        Self { inner }
188    }
189}
190
191#[cfg(all(feature = "vm", target_os = "linux"))]
192#[derive(Debug, Clone)]
193/// Configuration for creating a snapshot-based restore pool.
194///
195/// Requires `vm` feature + Linux.
196pub struct RestorePoolConfig {
197    /// Target restore pool size.
198    pub pool_size: usize,
199    /// Base configuration used by the restore pool.
200    pub base_config: Config,
201}
202
203#[cfg(all(feature = "vm", target_os = "linux"))]
204#[derive(Clone)]
205/// Snapshot-based restore pool for sub-millisecond VM restore-to-ready latency.
206///
207/// Pre-creates a pool of empty microVM shells. When a sandbox is needed,
208/// a snapshot is restored into one of the pre-warmed shells, avoiding
209/// the full VM boot sequence.
210///
211/// Requires `vm` feature + Linux.
212pub struct RestorePool {
213    pub(crate) inner: Arc<mimobox_vm::RestorePool>,
214}
215
216impl From<SandboxResult> for ExecuteResult {
217    fn from(r: SandboxResult) -> Self {
218        Self {
219            stdout: r.stdout,
220            stderr: r.stderr,
221            exit_code: r.exit_code,
222            timed_out: r.timed_out,
223            elapsed: r.elapsed,
224        }
225    }
226}
227
228#[cfg(feature = "vm")]
229impl From<mimobox_vm::HttpResponse> for HttpResponse {
230    fn from(value: mimobox_vm::HttpResponse) -> Self {
231        Self {
232            status: value.status,
233            headers: value.headers,
234            body: value.body,
235        }
236    }
237}
238
239/// Streaming output event from [`Sandbox::stream_execute()`].
240///
241/// Events are delivered via a `std::sync::mpsc::Receiver`. `Exit` or `TimedOut`
242/// is always the last event in the stream.
243///
244/// Requires `vm` feature + Linux.
245///
246/// # Examples
247///
248/// ```rust,no_run,ignore
249/// // Requires `vm` feature + Linux
250/// use mimobox_sdk::{Config, IsolationLevel, Sandbox, StreamEvent};
251///
252/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
253/// let config = Config::builder()
254///     .isolation(IsolationLevel::MicroVm)
255///     .build();
256/// let mut sandbox = Sandbox::with_config(config)?;
257/// let rx = sandbox.stream_execute("/bin/echo hello")?;
258/// for event in rx.iter() {
259///     match event {
260///         StreamEvent::Stdout(data) => print!("{}", String::from_utf8_lossy(&data)),
261///         StreamEvent::Stderr(data) => eprint!("{}", String::from_utf8_lossy(&data)),
262///         StreamEvent::Exit(code) => println!("exit = {code}"),
263///         StreamEvent::TimedOut => println!("timed out"),
264///         _ => {}
265///     }
266/// }
267/// # Ok(())
268/// # }
269/// ```
270#[non_exhaustive]
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub enum StreamEvent {
273    /// A chunk of standard output data.
274    Stdout(Vec<u8>),
275    /// A chunk of standard error data.
276    Stderr(Vec<u8>),
277    /// The process exited with an exit code.
278    Exit(i32),
279    /// Execution was terminated due to timeout.
280    TimedOut,
281}
282
283/// Interactive PTY terminal session.
284///
285/// Wraps a backend-provided terminal handle with a unified interface for
286/// input, resize, output events, and lifecycle control.
287///
288/// Currently supported on OS-level backends only (not microVM).
289///
290/// # Examples
291///
292/// ```rust,no_run
293/// use mimobox_sdk::Sandbox;
294///
295/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
296/// let mut sandbox = Sandbox::new()?;
297/// let mut pty = sandbox.create_pty("/bin/sh")?;
298/// pty.send_input(b"echo hello\n")?;
299/// let exit_code = pty.wait()?;
300/// println!("exited with {exit_code}");
301/// # Ok(())
302/// # }
303/// ```
304#[non_exhaustive]
305pub struct PtySession {
306    inner: Box<dyn mimobox_core::PtySession>,
307}
308
309impl std::fmt::Debug for PtySession {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        f.debug_struct("PtySession").finish_non_exhaustive()
312    }
313}
314
315impl PtySession {
316    /// Constructs an SDK PTY wrapper from a backend PTY session.
317    pub(crate) fn from_inner(inner: Box<dyn mimobox_core::PtySession>) -> Self {
318        Self { inner }
319    }
320
321    /// Writes input data to the PTY session.
322    pub fn send_input(&mut self, data: &[u8]) -> Result<(), SdkError> {
323        self.inner.send_input(data).map_err(map_pty_session_error)
324    }
325
326    /// Resizes the PTY terminal.
327    pub fn resize(&mut self, cols: u16, rows: u16) -> Result<(), SdkError> {
328        self.inner
329            .resize(PtySize { cols, rows })
330            .map_err(map_pty_session_error)
331    }
332
333    /// Returns the PTY output event receiver.
334    pub fn output(&self) -> &mpsc::Receiver<PtyEvent> {
335        self.inner.output_rx()
336    }
337
338    /// Forcefully terminates the PTY session.
339    pub fn kill(&mut self) -> Result<(), SdkError> {
340        self.inner.kill().map_err(map_pty_session_error)
341    }
342
343    /// Waits for the PTY process to exit and returns its exit code.
344    pub fn wait(&mut self) -> Result<i32, SdkError> {
345        self.inner.wait().map_err(map_pty_session_error)
346    }
347}