Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

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

cargo add petstore
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.

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.

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.

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.

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

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 and 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.

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::Values, 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.

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.