Skip to main content

Crate tofupilot

Crate tofupilot 

Source
Expand description

§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

ResourceMethodsDocs
Runslist, create, get, update, deletedocs/sdks/runs
Procedureslist, create, get, update, deletedocs/sdks/procedures
Unitslist, create, get, update, delete, add_child, remove_childdocs/sdks/units
Partslist, create, get, update, deletedocs/sdks/parts
Batcheslist, create, get, update, deletedocs/sdks/batches
Stationslist, create, get, get_current, update, removedocs/sdks/stations
Revisionscreate, get, update, deletedocs/sdks/revisions
Versionscreate, get, deletedocs/sdks/versions
Attachmentsinitialize, finalize, delete, upload_file, upload_bytes, download_filedocs/sdks/attachments
Userlistdocs/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?;

Re-exports§

pub use client::TofuPilot;
pub use config::ClientConfig;
pub use error::Error;
pub use error::Result;
pub use hooks::Hook;
pub use hooks::Hooks;

Modules§

attachments
batches
client
config
error
hooks
parts
procedures
revisions
runs
stations
types
units
upload
High-level file upload and download helpers.
user
versions