Skip to main content

Rust SDK

The official Rust SDK for Rexec provides a memory-safe, high-performance interface for Terminal as a Service.

Installation

Add to your Cargo.toml:
[dependencies]
rexec = "1.0"
tokio = { version = "1", features = ["full"] }

Quick Start

use rexec::{RexecClient, CreateContainerRequest};

#[tokio::main]
async fn main() -> Result<(), rexec::Error> {
    let client = RexecClient::new(
        "https://your-instance.com",
        "your-api-token"
    );

    // Create a container
    let container = client.containers()
        .create(CreateContainerRequest::new("ubuntu:24.04")
            .name("my-sandbox"))
        .await?;

    println!("Created container: {}", container.id);

    // Connect to terminal
    let mut term = client.terminal().connect(&container.id).await?;
    term.write(b"echo 'Hello from Rexec!'\n").await?;

    // Read output
    if let Some(data) = term.read().await? {
        println!("Output: {}", String::from_utf8_lossy(&data));
    }

    // Clean up
    client.containers().delete(&container.id).await?;

    Ok(())
}

Client Initialization

Basic Client

use rexec::RexecClient;

let client = RexecClient::new(
    "https://your-instance.com",
    "your-api-token"
);

With Custom Configuration

use rexec::{RexecClient, ClientConfig};

let client = RexecClient::with_config(
    ClientConfig::new("https://your-instance.com", "your-token")
        .timeout(60)
);

Container Operations

The Container service provides methods for managing sandboxed environments.

List Containers

let containers = client.containers().list().await?;
for c in containers {
    println!("{}: {}", c.name, c.status);
}

Get Container

let container = client.containers().get("container-id").await?;
println!("Container {} is {}", container.name, container.status);

Create Container

The SDK uses the builder pattern for creating containers:
let container = client.containers()
    .create(CreateContainerRequest::new("ubuntu:24.04")
        .name("my-container")
        .env("MY_VAR", "value")
        .env("DEBUG", "true")
        .label("project", "demo"))
    .await?;

println!("Created: {}", container.id);

Start Container

client.containers().start("container-id").await?;

Stop Container

client.containers().stop("container-id").await?;

Delete Container

client.containers().delete("container-id").await?;

File Operations

Manage files and directories within containers.

List Files

let files = client.files().list("container-id", "/home").await?;
for f in files {
    let icon = if f.is_dir { "📁" } else { "📄" };
    println!("{} {} ({} bytes)", icon, f.name, f.size);
}

Download File

let content = client.files().download("container-id", "/etc/passwd").await?;
println!("{}", String::from_utf8_lossy(&content));

Create Directory

client.files().mkdir("container-id", "/home/mydir").await?;

Delete File

client.files().delete("container-id", "/home/file.txt").await?;

Terminal Operations

Connect to containers via WebSocket for real-time terminal access.

Connect to Terminal

let mut term = client.terminal().connect("container-id").await?;

// Or with custom size
let mut term = client.terminal()
    .connect_with_size("container-id", 120, 40)
    .await?;

Write to Terminal

// Write bytes
term.write(b"ls -la\n").await?;

// Write string (convenience method)
term.write_str("echo hello\n").await?;

Read from Terminal

// Single read
if let Some(data) = term.read().await? {
    print!("{}", String::from_utf8_lossy(&data));
}

// Loop until connection closes
while let Some(data) = term.read().await? {
    print!("{}", String::from_utf8_lossy(&data));
}

Resize Terminal

term.resize(150, 50).await?;

Close Terminal

term.close().await?;

Advanced Examples

Run a Script

use rexec::{RexecClient, Error};

async fn run_script(
    client: &RexecClient,
    container_id: &str,
    script: &str,
) -> Result<String, Error> {
    let mut term = client.terminal().connect(container_id).await?;
    let mut output = String::new();

    term.write_str(&format!("{}\nexit\n", script)).await?;

    while let Some(data) = term.read().await? {
        output.push_str(&String::from_utf8_lossy(&data));
    }

    Ok(output)
}

// Usage
let output = run_script(
    &client,
    &container.id,
    "apt update && apt install -y curl"
).await?;
println!("{}", output);

Concurrent Operations

use futures::future::join_all;
use rexec::{RexecClient, Container, CreateContainerRequest};

async fn create_batch(
    client: &RexecClient,
    count: usize
) -> Vec<Container> {
    let futures: Vec<_> = (0..count)
        .map(|i| {
            client.containers().create(
                CreateContainerRequest::new("ubuntu:24.04")
                    .name(format!("worker-{}", i))
            )
        })
        .collect();

    join_all(futures)
        .await
        .into_iter()
        .filter_map(Result::ok)
        .collect()
}

// Create 5 containers in parallel
let containers = create_batch(&client, 5).await;
println!("Created {} containers", containers.len());

Real-time Log Streaming

async fn stream_logs(
    client: &RexecClient,
    container_id: &str,
    command: &str,
) -> Result<(), rexec::Error> {
    let mut term = client.terminal().connect(container_id).await?;
    
    term.write_str(&format!("{}\n", command)).await?;
    
    while let Some(data) = term.read().await? {
        let output = String::from_utf8_lossy(&data);
        print!("{}", output);
        
        // Could also save to file, send to channel, etc.
        // tokio::fs::write("logs.txt", &data).await?;
    }
    
    Ok(())
}

// Usage
stream_logs(&client, &container.id, "tail -f /var/log/app.log").await?;

Error Recovery

use rexec::{RexecClient, Error};
use std::time::Duration;

async fn create_container_with_retry(
    client: &RexecClient,
    max_retries: u32,
) -> Result<Container, Error> {
    let mut attempts = 0;
    
    loop {
        match client.containers()
            .create(CreateContainerRequest::new("ubuntu:24.04"))
            .await
        {
            Ok(container) => return Ok(container),
            Err(e) if attempts < max_retries => {
                attempts += 1;
                eprintln!("Attempt {} failed: {}", attempts, e);
                tokio::time::sleep(Duration::from_secs(2)).await;
            }
            Err(e) => return Err(e),
        }
    }
}

Error Handling

use rexec::{RexecClient, Error};

match client.containers().get("invalid-id").await {
    Ok(container) => println!("Found: {}", container.name),
    Err(Error::Api { status_code, message }) => {
        eprintln!("API Error {}: {}", status_code, message);
    }
    Err(Error::Connection(msg)) => {
        eprintln!("Connection Error: {}", msg);
    }
    Err(e) => eprintln!("Error: {}", e),
}

Features

The SDK supports optional features:
[dependencies]
# Use rustls instead of native TLS
rexec = { version = "1.0", features = ["rustls"] }

# Default features (native TLS)
rexec = "1.0"
Available features:
  • default - Uses native TLS
  • rustls - Use rustls instead of native TLS

Type Safety

The SDK leverages Rust’s type system for compile-time safety:
// The compiler ensures you handle all error cases
let container = client.containers().get(id).await?;

// Builder pattern prevents invalid configurations
let request = CreateContainerRequest::new("ubuntu:24.04")
    .name("valid-name")  // Returns Self for chaining
    .env("KEY", "value"); // Type-safe builder

Source Code

View the full source code on GitHub:

License

MIT License - see LICENSE for details.

Build docs developers (and LLMs) love