Files
Kyle Isom 0c441f5c4f Implement Phase 9: client libraries (Go, Rust, Lisp, Python)
- clients/README.md: canonical API surface and error type reference
- clients/testdata/: shared JSON response fixtures
- clients/go/: mciasgoclient package; net/http + TLS 1.2+; sync.RWMutex
  token state; DisallowUnknownFields on all decoders; 25 tests pass
- clients/rust/: async mcias-client crate; reqwest+rustls (no OpenSSL);
  thiserror MciasError enum; Arc<RwLock> token state; 22+1 tests pass;
  cargo clippy -D warnings clean
- clients/lisp/: ASDF mcias-client; dexador HTTP, yason JSON; mcias-error
  condition hierarchy; Hunchentoot mock-dispatcher; 37 fiveam checks pass
  on SBCL 2.6.1; yason boolean normalisation in validate-token
- clients/python/: mcias_client package (Python 3.11+); httpx sync;
  py.typed; dataclasses; 32 pytest tests; mypy --strict + ruff clean
- test/mock/mockserver.go: in-memory mock server for Go client tests
- ARCHITECTURE.md §19: updated per-language notes to match implementation
- PROGRESS.md: Phase 9 marked complete
- .gitignore: exclude clients/rust/target/, python .venv, .pytest_cache,
  .fasl files
Security: token never logged or exposed in error messages in any library;
TLS enforced in all four languages; token stored under lock/mutex/RwLock
2026-03-11 16:38:32 -07:00
..

mcias-client (Rust)

Async Rust client library for the MCIAS identity and access management API.

Requirements

  • Rust 2021 edition (stable toolchain)
  • Tokio async runtime

Installation

Add to Cargo.toml:

[dependencies]
mcias-client = { path = "path/to/clients/rust" }

Quick Start

use mcias_client::{Client, ClientOptions};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(
        "https://auth.example.com".to_string(),
        ClientOptions::default(),
    )?;

    // Authenticate.
    let (token, expires_at) = client.login("alice", "s3cret", None).await?;
    println!("token expires at {expires_at}");

    // The token is stored in the client automatically.
    let accounts = client.list_accounts().await?;

    // Revoke the token when done.
    client.logout().await?;

    Ok(())
}

Custom CA Certificate

let ca_pem = std::fs::read("/etc/mcias/ca.pem")?;
let client = Client::new(
    "https://auth.example.com".to_string(),
    ClientOptions {
        ca_cert_pem: Some(ca_pem),
        token: None,
    },
)?;

Error Handling

All methods return Result<_, MciasError>:

use mcias_client::MciasError;

match client.login("alice", "wrongpass", None).await {
    Err(MciasError::Auth { message }) => eprintln!("auth failed: {message}"),
    Err(MciasError::Forbidden { message }) => eprintln!("forbidden: {message}"),
    Err(MciasError::NotFound { message }) => eprintln!("not found: {message}"),
    Err(MciasError::InvalidInput { message }) => eprintln!("bad input: {message}"),
    Err(MciasError::Conflict { message }) => eprintln!("conflict: {message}"),
    Err(MciasError::Server { status, message }) => eprintln!("server error {status}: {message}"),
    Err(MciasError::Transport(e)) => eprintln!("network error: {e}"),
    Err(MciasError::Decode(e)) => eprintln!("parse error: {e}"),
    Ok((token, _)) => println!("ok: {token}"),
}

Thread Safety

Client is Send + Sync. The internal token is wrapped in Arc<RwLock<Option<String>>> for safe concurrent access.

Running Tests

cargo test
cargo clippy -- -D warnings