guacamole-client 0.5.0

Rust client library for the Guacamole REST API
Documentation

guacamole-client

Rust client library for the Apache Guacamole REST API.

Features

  • Async — built on reqwest and Tokio
  • Type-safe — strongly typed request/response models with serde
  • Input validation — all identifiers are validated before they reach the network
  • Security-first — auth tokens and passwords are redacted from Debug output, #![forbid(unsafe_code)]
  • Ergonomic errors — typed Error enum with per-status-code variants
  • Multi-data-source — every method accepts an optional data source override

Installation

Add to your Cargo.toml:

[dependencies]
guacamole-client = "0.5"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Quick start

#[tokio::main]
async fn main() -> guacamole_client::Result<()> {
    let mut client = guacamole_client::GuacamoleClient::new("http://localhost:8080/guacamole")?;
    client.login("guacadmin", "guacadmin").await?;

    let users = client.list_users(None).await?;
    for (username, user) in &users {
        println!("{username}: {user:?}");
    }

    client.logout().await?;
    Ok(())
}

API overview

Every method that operates on a data source takes an Option<&str> parameter. Pass None to use the default data source from your login session, or Some("postgresql") to target a specific one.

Authentication

# async fn example(client: &mut guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
// Login — stores the session token and default data source
let auth = client.login("guacadmin", "guacadmin").await?;
println!("Available data sources: {:?}", auth.available_data_sources);

// Logout — clears the stored session
client.logout().await?;
# Ok(())
# }

Users

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
// List / get
let users = client.list_users(None).await?;
let user = client.get_user(None, "guacadmin").await?;
let me = client.get_self(None).await?;

// Create
use guacamole_client::User;
let new_user = User {
    username: Some("alice".into()),
    password: Some("secret".into()),
    ..Default::default()
};
client.create_user(None, &new_user).await?;

// Update / delete
client.update_user(None, "alice", &new_user).await?;
client.delete_user(None, "alice").await?;

// Password
use guacamole_client::PasswordChange;
let pw = PasswordChange {
    old_password: Some("secret".into()),
    new_password: Some("new-secret".into()),
};
client.update_user_password(None, "alice", &pw).await?;

// Permissions
let perms = client.get_user_permissions(None, "alice").await?;
let effective = client.get_user_effective_permissions(None, "alice").await?;

// Groups & history
let groups = client.get_user_groups(None, "alice").await?;
let history = client.get_user_history(None, "alice").await?;

// Patch operations (permissions, group membership)
use guacamole_client::PatchOperation;
client.update_user_permissions(None, "alice", &[
    PatchOperation::add("/connectionPermissions/1", "READ"),
]).await?;
client.update_user_groups(None, "alice", &[
    PatchOperation::add("/", "developers"),
]).await?;
# Ok(())
# }

User groups

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
use guacamole_client::{UserGroup, PatchOperation};

let groups = client.list_user_groups(None).await?;
let group = client.get_user_group(None, "developers").await?;

// Create / update / delete
let new_group = UserGroup {
    identifier: Some("qa-team".into()),
    ..Default::default()
};
client.create_user_group(None, &new_group).await?;
client.update_user_group(None, "qa-team", &new_group).await?;
client.delete_user_group(None, "qa-team").await?;

// Member & parent management
client.update_user_group_member_users(None, "developers", &[
    PatchOperation::add("/", "alice"),
]).await?;
client.update_user_group_member_groups(None, "developers", &[
    PatchOperation::add("/", "interns"),
]).await?;
client.update_user_group_parent_groups(None, "developers", &[
    PatchOperation::add("/", "engineering"),
]).await?;
client.update_user_group_permissions(None, "developers", &[
    PatchOperation::add("/connectionPermissions/1", "READ"),
]).await?;
# Ok(())
# }

Connections

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
use std::collections::HashMap;
use guacamole_client::{Connection, PatchOperation};

let connections = client.list_connections(None).await?;
let conn = client.get_connection(None, "1").await?;

// Create
let new_conn = Connection {
    name: Some("my-server".into()),
    protocol: Some("ssh".into()),
    parent_identifier: Some("ROOT".into()),
    parameters: Some(HashMap::from([
        ("hostname".into(), "10.0.0.5".into()),
        ("port".into(), "22".into()),
    ])),
    ..Default::default()
};
let created = client.create_connection(None, &new_conn).await?;

// Update / delete
client.update_connection(None, "1", &new_conn).await?;
client.delete_connection(None, "1").await?;

// Parameters & history
let params = client.get_connection_parameters(None, "1").await?;
let history = client.get_connection_history(None, "1").await?;

// Sharing profiles for a connection
let profiles = client.get_connection_sharing_profiles(None, "1").await?;

// Active connections
let active = client.list_active_connections(None).await?;
client.kill_connections(None, &[
    PatchOperation::remove("/abc-123-def"),
]).await?;
# Ok(())
# }

Connection groups

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
use guacamole_client::ConnectionGroup;

let groups = client.list_connection_groups(None).await?;
let group = client.get_connection_group(None, "ROOT").await?;

// Connection tree (recursive)
let tree = client.get_connection_group_tree(None, "ROOT", Some("READ")).await?;

// Create / update / delete
let new_group = ConnectionGroup {
    name: Some("Servers".into()),
    type_: Some("ORGANIZATIONAL".into()),
    parent_identifier: Some("ROOT".into()),
    ..Default::default()
};
let created = client.create_connection_group(None, &new_group).await?;
client.update_connection_group(None, "1", &new_group).await?;
client.delete_connection_group(None, "1").await?;
# Ok(())
# }

Sharing profiles

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
use guacamole_client::SharingProfile;

let profiles = client.list_sharing_profiles(None).await?;
let profile = client.get_sharing_profile(None, "1").await?;
let params = client.get_sharing_profile_parameters(None, "1").await?;

// Create / delete
let new_profile = SharingProfile {
    name: Some("read-only-view".into()),
    primary_connection_identifier: Some("1".into()),
    ..Default::default()
};
let created = client.create_sharing_profile(None, &new_profile).await?;
client.delete_sharing_profile(None, "1").await?;
# Ok(())
# }

History

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
// Connection history (with optional filter and sort order)
let history = client.list_connection_history(None, Some("my-server"), Some("desc")).await?;

// User history
let user_history = client.list_user_history(None, Some("asc")).await?;
# Ok(())
# }

Schema & protocols

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
let user_schema = client.list_user_attributes_schema(None).await?;
let group_schema = client.list_user_group_attributes_schema(None).await?;
let conn_schema = client.list_connection_attributes_schema(None).await?;
let sp_schema = client.list_sharing_profile_attributes_schema(None).await?;
let cg_schema = client.list_connection_group_attributes_schema(None).await?;

let protocols = client.list_protocols(None).await?;
# Ok(())
# }

Tunnels & server patches

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
let tunnels = client.list_tunnels().await?;
let active = client.get_tunnel_active_connection("abc-123").await?;

let patches = client.list_patches().await?;
# Ok(())
# }

Languages

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
let languages = client.list_languages().await?;
// e.g. {"en": "English", "de": "Deutsch", ...}
# Ok(())
# }

Error handling

All methods return guacamole_client::Result<T>. The Error enum maps HTTP status codes to typed variants:

# async fn example(client: &guacamole_client::GuacamoleClient) -> guacamole_client::Result<()> {
use guacamole_client::Error;

match client.get_user(None, "alice").await {
    Ok(user) => println!("{user:?}"),
    Err(Error::NotFound { resource, .. }) => eprintln!("{resource} does not exist"),
    Err(Error::Unauthorized { .. }) => eprintln!("bad credentials"),
    Err(Error::Forbidden { .. }) => eprintln!("insufficient permissions"),
    Err(Error::RateLimited { retry_after, .. }) => eprintln!("slow down: {retry_after:?}"),
    Err(e) => eprintln!("unexpected error: {e}"),
}
# Ok(())
# }

Security

  • #![forbid(unsafe_code)] — no unsafe Rust anywhere in the crate
  • All identifiers (usernames, connection IDs, data sources, etc.) are validated against path-traversal and injection attacks before use
  • Auth tokens and passwords are redacted from Debug output — safe to log
  • HTTP timeouts are enforced (30 s request, 10 s connect)
  • Error response bodies are truncated to 512 bytes to prevent memory exhaustion

License

MIT