> For the complete documentation index, see [llms.txt](/llms.txt)

# Rust

The Rust SDK is an idiomatic crate emitted from the same `GlottoIR` as every other target.
Operations under a declared resource hang off an accessor — `client.pets().list_pets()` — while an
operation you leave ungrouped stays hoisted on `impl Client`. Request/response shapes are `serde`
types, so you work with real structs rather than bytes.

## Quickstart

```bash
cargo add petstore
```

```rust
use futures::StreamExt;

let client = Client::new("https://api.petstore.example")
    .with_token(std::env::var("PETSTORE_TOKEN")?);

// a single call
let pet = client.pets().create_pet(NewPet::new("Rex".into())).await?;

// StreamScope owns the paginator until close or scope exit.
let mut stream = StreamScope::new(client.pets().list_pets());
while let Some(pet) = stream.next().await {
    println!("{}", pet?.name);
}
stream.close();
```

## Typed models

Each `GlottoIR` model becomes a `serde` `Deserialize`/`Serialize` type; operations decode and
return the typed response and accept a typed request body. Discriminated unions resolve to the
right enum variant.

## Typed errors

Operations return a `Result`; the `Err` is an `ApiError` struct carrying the parsed error body,
plus an `ApiErrorKind` enum so callers `match err.kind()` against `ApiErrorKind::NotFound` rather
than matching status codes. See [Errors](/docs/errors).

## Pagination

Paginated list methods expose a walker that fetches each page as you iterate, advancing the cursor
for you. Wrap the stream in `StreamScope` and call `close()` after an early loop break.
The wrapper also releases its inner stream on EOF, a yielded error, or Rust scope exit, including
when consumer code returns through `?`. Keeping a bare stream requires explicitly dropping it
when you stop reading. See [Pagination](/docs/pagination).

Manual page companions expose typed items, metadata and explicit `next_page()` navigation. The lazy `response()` accessor decodes the full wrapper without another request. See [manual pagination](/docs/pagination).

## Retries & backoff

Use `RequestOverrides` with an operation’s `_with_options` companion or builder to override headers, timeouts, retry counts and idempotency. Existing `RequestOptions` values remain accepted. See [request options and defaults](/docs/retries).

Transient failures (`5xx`, `429`, transport errors) retry with exponential backoff and jitter,
configurable per client. See [Retries & timeouts](/docs/retries).

## Streaming

Server-sent-event endpoints return a typed event stream decoded from the `text/event-stream`
framing. NDJSON endpoints yield each decoded line through the same stream interface.
Use `StreamScope` for deterministic cleanup, as with pagination. Stream opening shares the
client retry policy, authentication and hooks. The request timeout ends at the first response
byte; later frames can arrive after that timeout. WebSocket upgrades use the injected HTTP
client and end their opening deadline at the accepted upgrade.
See [Streaming](/docs/streaming) and [Authentication](/docs/authentication).

## Unknown response fields

Your API can add a response field without it being a breaking change — but a closed struct would drop it, and `serde` discards what it does not recognize. The generated
models keep it instead: a field the SDK wasn't generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.

```rust
let pet = client.pets().create_pet(body).await?;

// A field your API started returning after this SDK was generated.
if let Some(species) = pet.extra_fields().get("species") {
    println!("{species}");
}

// Re-encoding preserves it — a read-modify-write never silently drops it.
let json = serde_json::to_string(&pet)?;
```

`extra_fields()` returns an `&ExtraFields` over `serde_json::Value`s, so nested objects and
arrays survive intact, and retention is recursive.

### Constructing a model

Every generated object model has a `new(...)` constructor taking its required fields.
It initializes optional fields to `None` and retained fields to an empty bag, so you do not need
to name the internal retention field in a struct literal. A required nullable field still needs
an explicit argument: pass `None` to send its JSON null.

Models whose caller-supplied fields are all optional also implement `Default`. This includes
empty models and union variants whose only required field is their generated discriminator.
Use `Model::default()` for an empty request or `Model { field: Some(value), ..Default::default() }`
to set selected fields. Referenced models and enum types do not need their own `Default` because
the optional field itself defaults to `None`. An optional collection set to `Some(Vec::new())`
sends an empty array; leaving it `None` omits the key.

For a decoded model, `Model { field: Some(value), ..original.clone() }` preserves the other
fields and retained response values. Required-field models and enum types do not implement
`Default`; construct them with their required arguments or a named enum variant.

To *send* a field your spec doesn't model yet, use the per-call extra-body escape hatch rather than
the retained bag.

## Binary downloads

Binary endpoints return an owned `BinaryResponse` with response metadata. `read_all(max_bytes)` bounds delivered bytes and `copy_to(&mut sink)` writes through `AsyncWrite`. These helpers consume the owner; close or drop an incremental reader when leaving early. Mixed JSON/binary operations expose an explicit result branch. See [file transfers](/docs/file-transfers).

## File uploads

`multipart/form-data` operations build the multipart body from their fields for you; an
`application/octet-stream` operation takes a positional `body: Vec<u8>` argument sent raw with the
right `Content-Type`.
