Skip to content

rootlyhq/rootly-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rootly-rs

Strongly-typed Rust SDK for the Rootly API, generated from the OpenAPI spec using Progenitor.

  • Full coverage — all 415+ API operations with builder pattern
  • Strongly typed — every endpoint, parameter, and response is a Rust type
  • Async/await — powered by reqwest and tokio
  • JSON:API compliant — handles application/vnd.api+json content negotiation

Installation

Add to your Cargo.toml:

[dependencies]
rootly = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Quick Start

use rootly::RootlyClient;

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

    let response = client.client().list_incidents().page_size(10).send().await?;

    for item in &response.data {
        println!("{}: {}", item.id, item.attributes.title);
    }

    Ok(())
}

Examples

List Incidents

let response = client
    .client()
    .list_incidents()
    .filter_status("started".to_string())
    .page_number(1)
    .page_size(10)
    .include("causes,services".to_string())
    .send()
    .await?;

for item in &response.data {
    println!(
        "[{}] {} ({})",
        item.id,
        item.attributes.title,
        item.attributes.status.as_deref().unwrap_or("unknown"),
    );
}

Get an Incident

let response = client
    .client()
    .get_incident()
    .id("abc123")
    .include("sub_statuses,causes,subscribers".to_string())
    .send()
    .await?;

let incident = &response.data.attributes;
println!("Title: {}", incident.title);
println!("Summary: {}", incident.summary.as_deref().unwrap_or(""));

Create an Incident

use rootly::types::{NewIncident, NewIncidentData, NewIncidentDataAttributes, NewIncidentDataType};

let body = NewIncident::builder()
    .data(
        NewIncidentData::builder()
            .type_(NewIncidentDataType::Incidents)
            .attributes(
                NewIncidentDataAttributes::builder()
                    .title("Service degradation".to_string())
                    .summary("Users experiencing elevated latency".to_string())
                    .severity_id("sev-1".to_string()),
            ),
    )
    .build()
    .unwrap();

let response = client.client().create_incident().body(body).send().await?;

Update an Incident

use rootly::types::{UpdateIncident, UpdateIncidentData, UpdateIncidentDataAttributes, UpdateIncidentDataType};

let body = UpdateIncident::builder()
    .data(
        UpdateIncidentData::builder()
            .type_(UpdateIncidentDataType::Incidents)
            .attributes(
                UpdateIncidentDataAttributes::builder()
                    .summary("Root cause identified — deploying fix".to_string()),
            ),
    )
    .build()
    .unwrap();

let response = client
    .client()
    .update_incident()
    .id("abc123")
    .body(body)
    .send()
    .await?;

List Services

let response = client.client().list_services().page_size(50).send().await?;

for svc in &response.data {
    println!("{}: {}", svc.id, svc.attributes.name);
}

Create an Alert

use rootly::types::{NewAlert, NewAlertData, NewAlertDataAttributes, NewAlertDataType};

let body = NewAlert::builder()
    .data(
        NewAlertData::builder()
            .type_(NewAlertDataType::Alerts)
            .attributes(
                NewAlertDataAttributes::builder()
                    .summary("High error rate on payments service".to_string())
                    .source("datadog".to_string()),
            ),
    )
    .build()
    .unwrap();

let response = client
    .client()
    .create_alert()
    .alerts_source_id("src-123")
    .body(body)
    .send()
    .await?;

Pagination

let mut page = 1;
loop {
    let response = client
        .client()
        .list_incidents()
        .page_number(page)
        .page_size(25)
        .send()
        .await?;

    let data = response.into_inner();
    if data.data.is_empty() {
        break;
    }

    for item in &data.data {
        println!("{}: {}", item.id, item.attributes.title);
    }

    page += 1;
}

Error Handling

use rootly::ClientError;

match client.client().get_incident().id("nonexistent").send().await {
    Ok(response) => println!("Found: {}", response.data.attributes.title),
    Err(ClientError::ErrorResponse(resp)) => {
        eprintln!("API error {}: {}", resp.status(), resp.status());
    }
    Err(e) => eprintln!("Request failed: {e}"),
}

Retry with Backoff

For handling rate limits (HTTP 429):

use rootly::retry::{with_backoff, RateLimitConfig};

let config = RateLimitConfig {
    max_retries: 5,
    initial_backoff_ms: 500,
};

let response = with_backoff(config, || {
    client.client().list_incidents().page_size(100).send()
}).await?;

Configuration

use rootly::{RootlyClient, RootlyClientConfig};

// Custom base URL
let client = RootlyClient::new(RootlyClientConfig {
    token: "your-token".into(),
    base_url: "https://custom.rootly.com".into(),
});

// Quick setup with defaults
let client = RootlyClient::from_token("your-token");

Using Types

All generated types are available under rootly::types:

use rootly::types::{
    Incident,
    IncidentList,
    NewIncident,
    Service,
    Alert,
    Severity,
    Team,
    // ... 685 total types
};

API Coverage

Full coverage of the Rootly API v1 (415+ operations):

Resource Operations
Incidents list, get, create, update, delete
Services list, get, create, update, delete
Alerts list, get, create, update, resolve, snooze, escalate
Teams list, get, create, update, delete
Severities list, get, create, update, delete
Environments list, get, create, update, delete
Workflows list, get, create, update, delete
Playbooks list, get, create, update, delete
Schedules list, get, create, update, delete
Escalation Policies list, get, create, update, delete
Dashboards list, get, create, update, delete
Status Pages list, get, create, update, delete
Custom Fields list, get, create, update, delete
Catalogs list, get, create, update, delete
... and more

Development

make regenerate   # Full clean + fetch spec + generate + build
make fetch-spec   # Download latest OpenAPI spec from Rootly
make generate     # Run Progenitor code generation
make build        # Compile
make test         # Run tests
make clippy       # Lint
make fmt          # Format
make clean        # Remove build artifacts + generated code

How Generation Works

The xtask crate handles code generation with four spec sanitization passes:

  1. Sanitize enums — strips operator enums (=, !=, >=, <=) that can't be Rust identifiers
  2. Strip error responses — removes 4xx/5xx responses (Progenitor limitation)
  3. Strip nullable defaults — removes defaults from nullable fields (typify limitation)
  4. Simplify path params — flattens anyOf path params to plain strings

Then Progenitor generates a fully typed async client with builder-pattern methods from the preprocessed OpenAPI spec.

Requirements

  • Rust 1.88+
  • tokio runtime

License

MIT — see LICENSE

About

Official Rootly Rust SDK for incident management API

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages