Skip to content

tofupilot/rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TofuPilot Rust Client

Rust client for the TofuPilot REST API. Async, typed, with retries and request lifecycle hooks.

Installation

Add to your Cargo.toml:

[dependencies]
tofupilot = "0.1"
tokio = { version = "1", features = ["full"] }

Quick Start

use tofupilot::TofuPilot;
use tofupilot::types::*;

#[tokio::main]
async fn main() -> tofupilot::Result<()> {
    let client = TofuPilot::new("your-api-key");

    // Create a test run
    let run = client.runs().create()
        .procedure_id("550e8400-e29b-41d4-a716-446655440000")
        .serial_number("SN-001234")
        .part_number("PCB-V1.2")
        .outcome(Outcome::Pass)
        .started_at(chrono::Utc::now() - chrono::TimeDelta::minutes(5))
        .ended_at(chrono::Utc::now())
        .send()
        .await?;

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

Available Resources

Resource Methods Docs
Runs list, create, get, update, delete docs/sdks/runs
Procedures list, create, get, update, delete docs/sdks/procedures
Units list, create, get, update, delete, add_child, remove_child docs/sdks/units
Parts list, create, get, update, delete docs/sdks/parts
Batches list, create, get, update, delete docs/sdks/batches
Stations list, create, get, get_current, update, remove docs/sdks/stations
Revisions create, get, update, delete docs/sdks/revisions
Versions create, get, delete docs/sdks/versions
Attachments initialize, finalize, delete, upload_file, upload_bytes, download_file docs/sdks/attachments
User list docs/sdks/user

All model types are documented in docs/models/.

Builder Pattern

Every API call uses the builder pattern. Required fields are enforced at send time:

// Optional fields are chained before .send()
let runs = client.runs().list()
    .outcomes(vec![Outcome::Pass])
    .part_numbers(vec!["PCB-V1.2".into()])
    .limit(50)
    .sort_by(RunListSortBy::StartedAt)
    .sort_order(ListSortOrder::Desc)
    .send()
    .await?;

for run in &runs.data {
    println!("{}: {:?}", run.id, run.outcome);
}

File Upload

The SDK provides high-level helpers that handle the three-step upload flow (initialize → PUT → finalize) in a single call:

// Upload from disk
let upload = client.attachments().upload_file("report.pdf").await?;

// Or upload raw bytes
let csv = b"header\nrow1\nrow2";
let upload = client.attachments().upload_bytes("data.csv", csv.to_vec()).await?;

// Link to a run
client.runs().update()
    .id(&run.id)
    .attachments(vec![upload.id])
    .send()
    .await?;

// Download a file
client.attachments().download_file(&upload.url, "local-report.pdf").await?;

Phases & Measurements

use tofupilot::types::*;

let now = chrono::Utc::now();

let run = client.runs().create()
    .procedure_id(proc_id)
    .serial_number("SN-001")
    .part_number("PCB-V1")
    .outcome(Outcome::Pass)
    .started_at(now - chrono::TimeDelta::minutes(5))
    .ended_at(now)
    .phases(vec![RunCreatePhases::builder()
        .name("voltage_check")
        .outcome(PhasesOutcome::Pass)
        .started_at(now - chrono::TimeDelta::minutes(5))
        .ended_at(now - chrono::TimeDelta::minutes(3))
        .measurements(vec![RunCreateMeasurements::builder()
            .name("output_voltage")
            .outcome(ValidatorsOutcome::Pass)
            .measured_value(serde_json::json!(3.3))
            .units("V")
            .build()
            .unwrap()
        ])
        .build()
        .unwrap()
    ])
    .send()
    .await?;

Error Handling

All API errors are typed:

use tofupilot::Error;

match client.runs().get().id("nonexistent").send().await {
    Ok(run) => println!("Found: {}", run.id),
    Err(Error::NotFound(e)) => println!("Not found: {}", e.message),
    Err(Error::Unauthorized(e)) => println!("Bad API key: {}", e.message),
    Err(Error::BadRequest(e)) => {
        println!("Validation error: {}", e.message);
        for issue in &e.issues {
            println!("  - {}", issue.message);
        }
    }
    Err(e) => println!("Other error: {e}"),
}

Retries

The client automatically retries on 429 (rate limit) and 5xx errors with exponential backoff. Configure via ClientConfig:

use tofupilot::config::ClientConfig;
use std::time::Duration;

let client = TofuPilot::with_config(
    ClientConfig::new("your-api-key")
        .base_url("https://your-instance.tofupilot.app/api")
        .timeout(Duration::from_secs(60))
        .max_retries(5),
);

Hooks

Inspect or modify requests and responses:

use tofupilot::Hooks;

let hooks = Hooks::new()
    .on_before_request(|ctx, req| async move {
        println!("[{}] {} {}", ctx.operation_id, req.method(), req.url());
        req
    })
    .on_after_error(|ctx, err| {
        let msg = format!("[{}] Error: {err}", ctx.operation_id);
        async move { eprintln!("{msg}"); }
    });

let client = TofuPilot::with_config(
    ClientConfig::new("your-api-key").hooks(hooks),
);

Nullable Fields

Some fields distinguish between "not sent" and "explicitly null". These use NullableField<T>:

use tofupilot::types::NullableField;

// Has a value (From<T> impl)
let field: NullableField<String> = "hello".to_string().into();

// Convenience constructors
let field = NullableField::value("hello".to_string());
let field: NullableField<String> = NullableField::null();

Builder methods handle this automatically — you never need to construct NullableField manually:

client.runs().create()
    .procedure_version("1.2.3")    // sets Value("1.2.3")
    .procedure_version_null()       // sets Null
    // omitted fields default to Absent

Self-Hosted

Point the client at your own TofuPilot instance:

let client = TofuPilot::with_config(
    ClientConfig::new("your-api-key")
        .base_url("https://your-instance.example.com/api"),
);

Per-Request Overrides

Override server URL or timeout for individual requests:

let result = client.runs().list()
    .server_url("https://staging.tofupilot.app/api")
    .timeout(std::time::Duration::from_secs(120))
    .send()
    .await?;

About

Upload hardware test data to TofuPilot from Rust.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages