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.