wasmx 0.0.0

High-performance, embeddable WebAssembly execution engine
Documentation
use core::fmt;

use tokio::io::{
    empty, stderr, stdin, stdout, AsyncRead, AsyncWrite, Empty, Stderr, Stdin, Stdout,
};
use wasmtime::component::Linker;

use crate::Ctx;

mod host;

pub struct WasiCliCtx {
    pub environment: Vec<(String, String)>,
    pub arguments: Vec<String>,
    pub initial_cwd: Option<String>,
    pub stdin: Box<dyn InputStream + Send>,
    pub stdout: Box<dyn OutputStream + Send>,
    pub stderr: Box<dyn OutputStream + Send>,
}

impl Default for WasiCliCtx {
    fn default() -> Self {
        Self {
            environment: Vec::default(),
            arguments: Vec::default(),
            initial_cwd: None,
            stdin: Box::new(empty()),
            stdout: Box::new(empty()),
            stderr: Box::new(empty()),
        }
    }
}

/// Add all WASI interfaces from this module into the `linker` provided.
pub fn add_to_linker<T: Send>(
    linker: &mut Linker<T>,
    get: impl Fn(&mut T) -> &mut Ctx + Copy + Sync + Send + 'static,
) -> wasmtime::Result<()> {
    let exit_options = crate::engine::bindings::wasi::cli::exit::LinkOptions::default();
    add_to_linker_with_options(linker, get, &exit_options)
}

/// Similar to [`add_to_linker`], but with the ability to enable unstable features.
pub fn add_to_linker_with_options<T: Send>(
    linker: &mut Linker<T>,
    get: impl Fn(&mut T) -> &mut Ctx + Copy + Sync + Send + 'static,
    exit_options: &crate::engine::bindings::wasi::cli::exit::LinkOptions,
) -> anyhow::Result<()> {
    crate::engine::bindings::wasi::cli::environment::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::exit::add_to_linker(linker, exit_options, get)?;
    crate::engine::bindings::wasi::cli::stdin::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::stdout::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::stderr::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::terminal_input::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::terminal_output::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::terminal_stdin::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::terminal_stdout::add_to_linker(linker, get)?;
    crate::engine::bindings::wasi::cli::terminal_stderr::add_to_linker(linker, get)?;
    Ok(())
}

/// An error returned from the `proc_exit` host syscall.
///
/// Embedders can test if an error returned from wasm is this error, in which
/// case it may signal a non-fatal trap.
#[derive(Debug)]
pub struct I32Exit(pub i32);

impl fmt::Display for I32Exit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Exited with i32 exit status {}", self.0)
    }
}

impl std::error::Error for I32Exit {}

pub struct TerminalInput;
pub struct TerminalOutput;

pub trait IsTerminal {
    /// Returns whether this stream is backed by a TTY.
    fn is_terminal(&self) -> bool;
}

impl IsTerminal for Empty {
    fn is_terminal(&self) -> bool {
        false
    }
}

pub trait InputStream: IsTerminal {
    fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin>;
}

pub trait OutputStream: IsTerminal {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin>;
}

impl InputStream for Empty {
    fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin> {
        Box::new(empty())
    }
}

impl OutputStream for Empty {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(empty())
    }
}

impl IsTerminal for std::io::Empty {
    fn is_terminal(&self) -> bool {
        false
    }
}

impl InputStream for std::io::Empty {
    fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin> {
        Box::new(empty())
    }
}

impl OutputStream for std::io::Empty {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(empty())
    }
}

impl IsTerminal for Stdin {
    fn is_terminal(&self) -> bool {
        std::io::stdin().is_terminal()
    }
}

impl InputStream for Stdin {
    fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin> {
        Box::new(stdin())
    }
}

impl IsTerminal for std::io::Stdin {
    fn is_terminal(&self) -> bool {
        std::io::IsTerminal::is_terminal(self)
    }
}

impl InputStream for std::io::Stdin {
    fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin> {
        Box::new(stdin())
    }
}

impl IsTerminal for Stdout {
    fn is_terminal(&self) -> bool {
        std::io::stdout().is_terminal()
    }
}

impl OutputStream for Stdout {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(stdout())
    }
}

impl IsTerminal for std::io::Stdout {
    fn is_terminal(&self) -> bool {
        std::io::IsTerminal::is_terminal(self)
    }
}

impl OutputStream for std::io::Stdout {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(stdout())
    }
}

impl IsTerminal for Stderr {
    fn is_terminal(&self) -> bool {
        std::io::stderr().is_terminal()
    }
}

impl OutputStream for Stderr {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(stderr())
    }
}

impl IsTerminal for std::io::Stderr {
    fn is_terminal(&self) -> bool {
        std::io::IsTerminal::is_terminal(self)
    }
}

impl OutputStream for std::io::Stderr {
    fn writer(&self) -> Box<dyn AsyncWrite + Send + Sync + Unpin> {
        Box::new(stderr())
    }
}