keelrun 0.4.1

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](https://github.com/MisterTK/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`:

```bash
cargo add keelrun --rename keel
```

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

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

## Quickstart

```rust,no_run
# 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`:

```rust,no_run
# 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]https://github.com/MisterTK/keel#readme — what Keel is,
  the two-tier resilience/durability model, demos.
- [`docs/dx-spec.md`]https://github.com/MisterTK/keel/blob/main/docs/dx-spec.md /
  [`docs/architecture-spec.md`]https://github.com/MisterTK/keel/blob/main/docs/architecture-spec.md
  — the full design.
- The `keel` CLI (`doctor`/`init`/`status`/`mcp`/…) is a separate package,
  [`keelrun-cli`]https://crates.io/crates/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.