Rust SDK and CLI
This is the README that tungsten generates for the Rust target, shown for a small example API (Swagger's Petstore). Names change with your API; the structure does not.
Rust SDK for the openapi API (1.0.0), generated by tungsten.
Every call returns tungsten_runtime::Result<T>: the value with its response metadata, or the diagnostic envelope. API and transport errors are values, never panics, and arguments are validated before any request is sent. The SDK is async (tokio), with a blocking client for programs without an async runtime (), and builds with Rust 1.88 or newer; models are serde types.
Install
cargo add openapi-sdk
cargo add tokio --features rt-multi-thread,macros
Create a client
#![allow(unused)] fn main() { use std::collections::BTreeMap; use tungsten_runtime::Credential; use openapi_sdk::{CallOptions, ClientOptions, OpenapiClient}; let client = OpenapiClient::new(ClientOptions { base_url: Some("http://petstore.swagger.io/v1".to_string()), ..Default::default() })?; }
Read
use openapi_sdk::resources::pets::PetsListRequest; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // let client = ...; match client.pets().list(PetsListRequest::default(), &CallOptions::default()).await { Ok(response) => println!("{:?}", response.value), Err(error) => println!("{:?} {}", error.category, error.remediation), } Ok(()) }
response.meta has the status, headers, request id and the number of attempts. A success without a body is () (or None when only some success statuses have one).
Change something, safely
Mutations are never retried blindly. When the operation takes an idempotency key, generate it once, persist it with your intent, and reuse it on every retry.
#![allow(unused)] fn main() { use openapi_sdk::resources::pets::PetsCreateRequest; let opts = CallOptions { idempotency_key: Some(key), // a UUIDv4 you persisted ..Default::default() }; let result = client.pets().create(PetsCreateRequest::default(), &opts).await; }
Models and optional fields
Request and response bodies are serde structs under models. A field that may be left out is Option<T> and is not sent when None; a required field that may be null is Option<T> and is sent as null; a field that may be left out or null is Patch<T> (Patch::Undefined, Patch::Null, Patch::Value). Closed records reject unknown members; open records keep them in extra. String and integer enums are closed: an unknown value does not decode. Integer fields are i64 (i32 for int32, u64 for non-negative ones above i64::MAX) and do not accept 1.0. Constraints (bounds, lengths, patterns, formats, unique items) are checked by the validators the SDK registers with the runtime, before a request is sent and, per ClientOptions::validate_responses, on responses.
Errors
A failed call is Err(error); error.diagnostic is the envelope:
{
"status": "error",
"category": "VALIDATION_FAILED",
"operation": "<operation id>",
"http_status": null,
"code": null,
"failed_parameter": "<args path>",
"received_value": null,
"expected": "<what was expected>",
"remediation": "<what to do next>",
"retryable": "never",
"retry_after_ms": null,
"next_action": null,
"request_id": null,
"trace": { "attempts": 0 }
}
Branch on category and retryable: same_key_only means retry only with the same idempotency key, after_remediation means act first (fix the input, or check whether an unknown outcome took effect: without a key a repeat can apply it twice), and OUTCOME_UNKNOWN means the request may have been applied. error.partial holds what a failed call or macro already produced (a one-time secret, completed step results): store it before acting on the error.
Blocking client
OpenapiBlockingClient has the same resources, methods, arguments and results as OpenapiClient, but each call returns when the request is done: it owns a thread that runs a current-thread tokio runtime, so it needs no async runtime of its own and never panics inside one (it blocks the calling thread while it waits, so from async code use OpenapiClient). Page iterators are blocking Iterators of pages.
#![allow(unused)] fn main() { use openapi_sdk::{ClientOptions, OpenapiBlockingClient}; let client = OpenapiBlockingClient::new(ClientOptions::default())?; // client.<resource>().<method>(request, &CallOptions::default()) }
Dynamic calls
The client implements tungsten_runtime::Dispatch: invoke, preview, pages, run_macro and preview_macro take an operation id (or macro name) and a JSON arguments object keyed by argument name, run the same typed methods and return JSON. Arguments that do not decode go to the runtime as given, which answers with the pre-flight envelope.
Command line
The openapi binary (crate openapi-cli) is built on this SDK: one command per operation, --json output, --dry-run previews and the same safety rules. Build it with cargo build -p openapi-cli --release.
Extending
src/custom/mod.rs is yours: it is created once, never overwritten, and exported as custom.