keelrun 0.2.0

Keel as a Rust front end: #[keel::wrap] plus a reqwest-middleware adapter, routing calls through the keel-core Engine chain (cache/rate/breaker/timeout/retry) from one `keel.toml`. No import hook (Rust has none to hang off of) — call `keel::init()` once from your own `main`. Published as `keelrun` (plain `keel` is taken on crates.io); add it as `cargo add keelrun --rename keel` so `#[keel::wrap]` resolves — see README.md.
Documentation

keelrun

The Rust front end for Keel — resilience (retry/backoff/timeout/breaker/rate/cache) as a library, policy in one keel.toml, no service to run.

Rust has no import hooks to hang a zero-code-change promise off of, so this crate keeps the smallest possible ceiling instead: one attribute macro, plus a reqwest-middleware adapter for outbound HTTP.

Install

Published on crates.io as keelrun (plain keel is taken). Add it with an explicit rename so #[keel::wrap] resolves — this is the intended way to add it, mirroring how this project's own crates already depend on keelrun-core as keel-core:

cargo add keelrun --rename keel

which is equivalent to adding this to Cargo.toml by hand:

[dependencies]
keel = { package = "keelrun", version = "0.1" }

Quickstart

# use thiserror::Error;
# #[derive(Debug, Error)]
# #[error("upstream unavailable: {0}")]
# struct UpstreamError(String);
#[keel::wrap(target = "orders-api")]
async fn fetch_order(id: u64) -> Result<Order, UpstreamError> {
    // your existing code, unmodified — the attribute routes it through
    // Keel's cache -> rate -> breaker -> timeout -> retry chain
    todo!()
}
# #[derive(serde::Serialize, serde::Deserialize)]
# struct Order;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    keel::init()?; // reads ./keel.toml, or Level 0 defaults if absent
    let order = fetch_order(42).await?;
    # let _ = order;
    Ok(())
}

Outbound HTTP via reqwest:

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
keel::init()?;
let raw = reqwest::Client::new();
let client = reqwest_middleware::ClientBuilder::new(raw.clone())
    .with(keel::KeelMiddleware::new(raw))
    .build();
let resp = client.get("https://api.example.com/orders").send().await?;
# let _ = resp;
# Ok(())
# }

#[keel::wrap]'s v1 scope: a free (no self) async fn returning Result<T, E> where T: Serialize + DeserializeOwned and E: std::error::Error + Send + Sync + 'static; every parameter must implement Clone (a retried call re-invokes the body, cloning fresh arguments per attempt); the target string is explicit only, no signature inference. KeelMiddleware's v1 scope: exact-host targets only (no host:/URL-pattern globs), no response caching, add it last in the ClientBuilder chain. Both documented in full in each type's own rustdoc.

Learn more

  • Root README — what Keel is, the two-tier resilience/durability model, demos.
  • docs/dx-spec.md / docs/architecture-spec.md — the full design.
  • The keel CLI (doctor/init/status/mcp/…) is a separate package, keelrun-cli — it does not yet scan Rust projects (crates/keel/src/lib.rs's crate docs track this as known debt).

Licensed under Apache-2.0.