Skip to content

spiraldb/metronome-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

metronome-sdk

crates.io docs.rs License: MIT

An unofficial Rust client for the Metronome billing API, generated from Metronome's published OpenAPI spec with progenitor.

Metronome doesn't ship a Rust SDK (only TypeScript and Go). This crate generates a typed, async client (reqwest + serde) from their OpenAPI spec. src/lib.rs is generated — do not hand-edit it. Re-run ./scripts/generate.sh instead.

Usage

Metronome uses bearer-token auth. progenitor doesn't inject the token itself, so attach it as a default header on the reqwest::Client and hand that to the generated client:

use std::str::FromStr;
use metronome_sdk::Client;
use metronome_sdk::types::IngestV1BodyItem;

fn metronome(token: &str) -> Client {
    let mut headers = reqwest::header::HeaderMap::new();
    let mut auth = reqwest::header::HeaderValue::from_str(&format!("Bearer {token}")).unwrap();
    auth.set_sensitive(true);
    headers.insert(reqwest::header::AUTHORIZATION, auth);

    let http = reqwest::Client::builder()
        .default_headers(headers)
        .build()
        .unwrap();

    Client::new_with_client("https://api.metronome.com", http)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = metronome(&std::env::var("METRONOME_API_KEY")?);

    // Ingest a usage event. The id/customer/type fields are validated newtypes,
    // so build them with FromStr (or TryFrom) rather than a bare String.
    let events = vec![IngestV1BodyItem {
        transaction_id: FromStr::from_str("txn-0001")?,
        customer_id: FromStr::from_str("cust_abc123")?,
        event_type: FromStr::from_str("api_request")?,
        timestamp: "2026-06-15T00:00:00Z".to_string(), // RFC 3339
        properties: serde_json::json!({ "tokens": 4096 })
            .as_object().unwrap().clone(),
    }];
    client.ingest_v1(&events).await?;

    Ok(())
}

Every Metronome endpoint is available as an async method on Client (119 in total) — e.g. ingest_v1, search_events_v1, create_billable_metric_v1. Request/response types live under metronome_sdk::types. Constrained string fields are generated as validated newtypes; construct them with FromStr/TryFrom (both return a ConversionError if validation fails).

Regenerating

cargo install cargo-progenitor --version 0.14.0   # one-time
./scripts/generate.sh             # regenerate from the vendored spec/
./scripts/generate.sh --refresh   # re-download the spec from Metronome, then regenerate

Why the spec needs patching

Metronome's published OpenAPI spec has constructs that progenitor/typify can't consume directly (the official Go/TS SDKs use a private Stainless overlay we don't have). scripts/patch_spec.py applies three mechanical, deterministic fixes before generation:

  1. Case-insensitive enums → string (618 of them). The spec enumerates every casing of each value (count/Count/COUNT), which can't map to unique Rust variants. Downgrading to string round-trips losslessly regardless of the casing the API returns.
  2. Mis-typed string enums → type: string (7). Some enums declare type: object despite having string values (e.g. billable_status).
  3. Stray scalar format on array/object nodes stripped (2). e.g. a type: array carrying format: uuid.

If a future spec revision introduces a new unsupported construct, generation will fail loudly; extend patch_spec.py with a new rule.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages