### Rust Example Using a Standalone Generated Crate Source: https://context7.com/oxidecomputer/progenitor/llms.txt Demonstrates how to use a client crate that was generated as a standalone package by Progenitor. It imports the generated client and types, creates a client instance with a base URL, and then calls an API endpoint (`enrol`) with specific request body data. This example requires the `tokio` runtime and the generated `keeper-client` crate. ```rust // Using the standalone generated crate // use keeper_client::{Client, types}; // #[tokio::main] // async fn main() -> Result<(), Box> { // let client = Client::new("https://keeper.example.com"); // let result = client.enrol( // "auth-header", // &types::EnrolBody { // host: "server1".to_string(), // key: "secret-key".to_string(), // }, // ).await?; // println!("Enrollment result: {:?}", result); // Ok(()) // } ``` -------------------------------- ### Rust Consumer Example with Builder Pattern Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Illustrates how a consumer would utilize the generated builder pattern in Rust. It shows chaining method calls on the builder to set parameters like `organization_name`, `project_name`, and `body`, followed by invoking the `send` method to execute the request. This example highlights the improved readability and ease of use provided by the builder style. ```rust let result = client .instance_create() .organization_name("org") .project_name("proj") .body(body) .send() .await?; ``` -------------------------------- ### Rust Consumer Example with Inline Struct Builder Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Demonstrates constructing complex parameters, specifically the `body` parameter, inline using nested builders. This example shows how generated struct types also have their own builders, allowing for the creation of nested data structures directly within the API call chain. This further enhances the expressiveness and conciseness of the consumer code. ```rust let result = client .instance_create() .organization_name("org") .project_name("proj") .body(types::InstanceCreate::builder() .name("...") .description("...") .hostname("...") .ncpus(types::InstanceCpuCount(4)) .memory(types::ByteCount(1024 * 1024 * 1024)), ) .send() .await?; ``` -------------------------------- ### Example Cargo.toml Dependencies (Default) Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md An excerpt from a `Cargo.toml` file generated by Progenitor when not explicitly including the `progenitor-client` code. ```toml [dependencies] bytes = "1.9" chrono = { version = "0.4", default-features=false, features = ["serde"] } utures-core = "0.3" progenitor-client = "0.9.1" reqwest = { version = "0.12", default-features=false, features = ["json", "stream"] } serde = { version = "1.0", features = ["derive"] } serde_urlencoded = "0.7" ``` -------------------------------- ### Progenitor Macro with Advanced Options Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md This example shows advanced configuration options for the `generate_api!` macro. It allows customization of the OpenAPI document source, interface style, tag merging, client inner types, pre/post hooks for HTTP requests/responses, and additional derive macros for generated types. ```rust generate_api!( spec = "path/to/openapi_document.json", // The OpenAPI document interface = Builder, // Choose positional (default) or builder style tags = Separate, // Tags may be Merged or Separate (default) inner_type = my_client::InnerType, // Client inner type available to pre and post hooks pre_hook = closure::or::path::to::function, // Hook invoked before issuing the HTTP request post_hook = closure::or::path::to::function, // Hook invoked prior to receiving the HTTP response derives = [ schemars::JsonSchema ], // Additional derive macros applied to generated types ); ``` -------------------------------- ### Example Cargo.toml Dependencies (--include-client true) Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md An excerpt from a `Cargo.toml` file generated by Progenitor when the `--include-client true` flag is used, directly embedding the client code. ```toml [dependencies] bytes = "1.9" chrono = { version = "0.4", default-features=false, features = ["serde"] } futures-core = "0.3" percent-encoding = "2.3" reqwest = { version = "0.12", default-features=false, features = ["json", "stream"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" ``` -------------------------------- ### Positional Client Interface Example (Rust) Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Illustrates a Rust `Client` method generated using the 'positional' style, where parameters are accepted in a specific order. ```rust impl Client { pub async fn instance_create<'a>( &'a self, organization_name: &'a types::Name, project_name: &'a types::Name, body: &'a types::InstanceCreate, ) -> Result, Error> { // ... } } ``` -------------------------------- ### Positional Parameter Invocation Example (Rust) Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Shows how to call a Rust client method generated with the 'positional' style, passing arguments by their defined order. ```rust let result = client.instance_create(org, proj, body).await?; ``` -------------------------------- ### Rust build.rs Configuration for Builder Style Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Provides a `build.rs` script configuration in Rust to enable the builder style generation in Progenitor. It details how to read an OpenAPI specification file, set generation settings to `InterfaceStyle::Builder`, and then generate and write the Rust code to the output directory. This setup is crucial for integrating Progenitor's code generation into a Rust project's build process. ```rust fn main() { let src = "../sample_openapi/keeper.json"; println!("cargo:rerun-if-changed={}", src); let file = std::fs::File::open(src).unwrap(); let spec = serde_json::from_reader(file).unwrap(); let mut binding = GenerationSettings::default(); let settings = binding.with_interface(InterfaceStyle::Builder); let mut generator = progenitor::Generator::new(&settings); let tokens = generator.generate_tokens(&spec).unwrap(); let ast = syn::parse2(tokens).unwrap(); let content = prettyplease::unparse(&ast); let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf(); out_file.push("codegen.rs"); std::fs::write(out_file, content).unwrap(); } ``` -------------------------------- ### Bash CLI for Standalone Crate Generation with Progenitor Source: https://context7.com/oxidecomputer/progenitor/llms.txt Installs the `cargo-progenitor` CLI tool and then uses it to generate a standalone Rust crate for an API client. This process takes an OpenAPI specification file and outputs a complete crate with its own `Cargo.toml` and source files, ready for distribution or use as a dependency. The command specifies input file, output directory, crate name, version, license, and registry name. ```bash # Install the CLI tool # cargo install cargo-progenitor # Generate a standalone crate # cargo progenitor \ # -i sample_openapi/keeper.json \ # -o generated/keeper-client \ # -n keeper-client \ # -v 0.1.0 \ # --license "Apache-2.0" \ # --registry-name "keeper-api" # The generated crate structure: # generated/keeper-client/ # ├── Cargo.toml # ├── src/ # │ ├── lib.rs # │ └── types.rs # Use the generated crate # cd generated/keeper-client # cargo build # Or add as a dependency # # [dependencies] # # keeper-client = { path = "../generated/keeper-client" } ``` -------------------------------- ### Generated Client Operation Signature with Progenitor Types Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/progenitor-client.md Demonstrates how a typical generated client method signature utilizes `ResponseValue` for success types and `Error` for error types, providing a clear example of their application in asynchronous operations. ```rust impl Client { pub async fn operation_name<'a>( &'a self, // parameters ... ) -> Result< ResponseValue, Error> { // ... } } ``` -------------------------------- ### Handle API Responses and Errors in Rust Source: https://context7.com/oxidecomputer/progenitor/llms.txt Demonstrates how to make a GET request using the Progenitor Rust client, process successful typed responses, extract metadata like status codes and headers, and handle various error types including invalid requests, communication errors, API-specific errors, and parsing failures. It utilizes `ResponseValue` for parsed data and metadata, and the `Error` enum for detailed error information. ```rust use progenitor_client::{Error, ResponseValue}; #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::new("https://api.example.com"); // ResponseValue contains the parsed response plus metadata match client.get_user("user123").await { Ok(response) => { // Access the typed response println!("User ID: {}", response.id); println!("User name: {}", response.name); // Access response metadata println!("Status: {}", response.status()); println!("Content-Length: {:?}", response.content_length()); if let Some(rate_limit) = response.headers().get("x-rate-limit-remaining") { println!("Rate limit remaining: {:?}", rate_limit); } // Extract inner value let user = response.into_inner(); process_user(user); } Err(e) => { match e { Error::InvalidRequest(msg) => { eprintln!("Invalid request: {}", msg); } Error::CommunicationError(reqwest_err) => { eprintln!("Network error: {}", reqwest_err); if let Some(status) = reqwest_err.status() { eprintln!("Status code: {}", status); } } Error::ErrorResponse(error_response) => { eprintln!("API error ({}): {:?}", error_response.status(), error_response.into_inner() ); } Error::InvalidResponsePayload(bytes, json_err) => { eprintln!("Failed to parse response: {}", json_err); eprintln!("Raw bytes: {:?}", bytes); } Error::UnexpectedResponse(response) => { eprintln!("Unexpected response status: {}", response.status()); } Error::ResponseBodyError(err) => { eprintln!("Error reading response body: {}", err); } _ => { eprintln!("Other error: {:?}", e); } } } } Ok(()) } fn process_user(user: types::User) { println!("Processing user: {:?}", user); } ``` -------------------------------- ### Create Basic Client with Progenitor Rust Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/implementing-client.md Instantiates a basic client without custom headers or reqwest configurations. It requires a base URL string as input. ```rust let client = Client::new("https://foo/bar"); ``` -------------------------------- ### Create Advanced Client with Progenitor and Reqwest Rust Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/implementing-client.md Creates a client with custom headers and reqwest configurations. This method takes a base URL string and a pre-configured reqwest::ClientBuilder. Dependencies include the `reqwest` crate and `std::time::Duration`. ```rust let mut val = reqwest::header::HeaderValue::from_static("super-secret"); val.set_sensitive(true); let mut headers = reqwest::header::HeaderMap::new(); headers.insert(reqwest::header::AUTHORIZATION, val); // Insert more headers if necessary let client_builder = reqwest::ClientBuilder::new() // Set custom timeout .connect_timeout(Duration::new(60, 0)) // Set custom headers .default_headers(headers) .build() .unwrap(); let client Client::new_with_client("https://foo/bar", client_builder); ``` -------------------------------- ### Configure Client with Custom HTTP Settings and Authentication in Rust Source: https://context7.com/oxidecomputer/progenitor/llms.txt This snippet demonstrates how to create a Rust client with custom HTTP headers, timeouts, and authentication using the reqwest library. It shows both a simple client creation and a more complex one with configurable settings. Dependencies include `std::time::Duration` and `reqwest`. ```rust use std::time::Duration; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, USER_AGENT}; // Simple client with default reqwest settings let client = Client::new("https://api.example.com"); // Custom client with headers, timeouts, and other settings let baseurl = std::env::var("API_URL").unwrap_or_else(|_| "https://api.example.com".to_string()); let access_token = std::env::var("API_ACCESS_TOKEN").expect("API_ACCESS_TOKEN not set"); let mut headers = HeaderMap::new(); headers.insert( AUTHORIZATION, HeaderValue::from_str(&format!("Bearer {}", access_token)).unwrap(), ); headers.insert( USER_AGENT, HeaderValue::from_static("my-app/1.0"), ); let reqwest_client = reqwest::ClientBuilder::new() .connect_timeout(Duration::from_secs(15)) .timeout(Duration::from_secs(30)) .default_headers(headers) .pool_idle_timeout(Duration::from_secs(60)) .build() .unwrap(); let client = Client::new_with_client(&baseurl, reqwest_client); // Use the client match client.get_user("user123").await { Ok(user) => println!("User: {:?}", user), Err(e) => eprintln!("Error: {}", e), } ``` -------------------------------- ### Rust Client Initialization with Custom Default Headers Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Demonstrates how to initialize a custom `reqwest::Client` with default headers in Rust, which can then be used to construct a Progenitor `Client`. This snippet shows setting connection and request timeouts, defining authorization headers, and passing the configured `reqwest::Client` to `Client::new_with_client`. This allows for centralized management of request headers across API calls. ```rust let baseurl = std::env::var("API_URL").expect("$API_URL not set"); let access_token = std::env::var("API_ACCESS_TOKEN").expect("$API_ACCESS_TOKEN not set"); let authorization_header = format!("Bearer {}", access_token); let mut headers = reqwest::header::HeaderMap::new(); headers.insert( reqwest::header::AUTHORIZATION, authorization_header.parse().unwrap(), ); let client_with_custom_defaults = reqwest::ClientBuilder::new() .connect_timeout(Duration::from_secs(15)) .timeout(Duration::from_secs(15)) .default_headers(headers) .build() .unwrap(); let client = Client::new_with_client(baseurl, client_with_custom_defaults); ``` -------------------------------- ### Generate API Client with Progenitor Macro Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md This snippet demonstrates the simplest way to use Progenitor via its `generate_api!` macro. It takes the path to an OpenAPI document as input. Ensure all necessary dependencies like `futures`, `progenitor`, and `reqwest` are added to your `Cargo.toml`. ```rust generate_api!("path/to/openapi_document.json"); ``` -------------------------------- ### Rust CLI Generation with Progenitor and Clap Source: https://context7.com/oxidecomputer/progenitor/llms.txt Generates a command-line interface (CLI) for interacting with an API using Progenitor and the Clap library. This build script reads an OpenAPI spec, generates client code and CLI arguments, and writes them to files included in the main application. It requires the `progenitor`, `serde_json`, `syn`, and `prettyplease` crates. ```rust // build.rs fn main() { let spec_file = std::fs::File::open("../sample_openapi/keeper.json").unwrap(); let spec = serde_json::from_reader(spec_file).unwrap(); let mut generator = progenitor::Generator::default(); // Generate the main client let client_tokens = generator.generate_tokens(&spec).unwrap(); let client_ast = syn::parse2(client_tokens).unwrap(); let client_content = prettyplease::unparse(&client_ast); let client_path = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()) .join("client.rs"); std::fs::write(client_path, client_content).unwrap(); // Generate CLI using clap let cli_tokens = generator.cli(&spec, "my_api").unwrap(); let cli_ast = syn::parse2(cli_tokens).unwrap(); let cli_content = prettyplease::unparse(&cli_ast); let cli_path = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()) .join("cli.rs"); std::fs::write(cli_path, cli_content).unwrap(); } // src/main.rs include!(concat!(env!("OUT_DIR"), "/client.rs")); include!(concat!(env!("OUT_DIR"), "/cli.rs")); use clap::Parser; #[tokio::main] async fn main() -> Result<(), Box> { let args = Cli::parse(); let baseurl = std::env::var("API_URL").unwrap_or_else(|_| "https://api.example.com".to_string()); let client = Client::new(&baseurl); // CLI automatically handles subcommands and arguments args.execute(&client).await?; Ok(()) } // Usage: // $ my_api user get --id user123 // $ my_api instance list --project my-project --limit 10 // $ my_api instance create --name web1 --ncpus 4 --memory 8589934592 ``` -------------------------------- ### Consuming a Paginated Stream (Rust) Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/positional-generation.md Demonstrates how to consume a stream generated for a paginated operation. It uses a loop with `try_next().await` to process items from the stream, handling both successful item retrieval, the end of the stream, and potential errors during the process. ```rust let mut stream = client.operation_name_stream(None); loop { match stream.try_next().await { Ok(Some(item)) => println!("item {:?}", item), Ok(None) => { println!("done."); break; } Err(_) => { println!("error!"); break; } } } ``` -------------------------------- ### Generate API Client using build.rs Script Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md This snippet illustrates using Progenitor within a `build.rs` script for more control and visibility over generated code. It reads an OpenAPI spec, generates Rust tokens, parses them into an AST, and then formats and writes the code to `OUT_DIR`. This method is required for generating CLI and httpmock helpers. ```rust fn main() { let src = "../sample_openapi/keeper.json"; println!("cargo:rerun-if-changed={}", src); let file = std::fs::File::open(src).unwrap(); let spec = serde_json::from_reader(file).unwrap(); let mut generator = progenitor::Generator::default(); let tokens = generator.generate_tokens(&spec).unwrap(); let ast = syn::parse2(tokens).unwrap(); let content = prettyplease::unparse(&ast); let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf(); out_file.push("codegen.rs"); std::fs::write(out_file, content).unwrap(); } ``` -------------------------------- ### Rust Builder Pattern for API Client Methods Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Demonstrates the Rust implementation of the 'builder' pattern for API client methods. It includes the `Client` struct with a method to create a builder, and the `builder` module defining the builder struct and its methods for setting parameters and sending requests. This pattern enables cleaner consumer code by abstracting away parameter construction and validation. ```rust impl Client pub fn instance_create(&self) -> builder::InstanceCreate { builder::InstanceCreate::new(self) } } mod builder { pub struct InstanceCreate<'a> { client: &'a super::Client, organization_name: Result, project_name: Result, body: Result, } impl<'a> InstanceCreate<'a> { pub fn new(client: &'a super::Client) -> Self { // ... } pub fn organization_name(mut self, value: V) -> Self where V: TryInto, { // ... } pub fn project_name(mut self, value: V) -> Self where V: TryInto, { // ... } pub fn body(mut self, value: V) -> Self where V: TryInto, { // ... } pub async fn send(self) -> Result, Error> { // ... } } } ``` -------------------------------- ### Progenitor CLI Usage for Static Crate Generation Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md Demonstrates how to use the `cargo progenitor` command to generate a static Rust crate from an OpenAPI definition. It specifies input, output, crate name, and version. ```bash cargo install cargo-progenitor cargo progenitor -i sample_openapi/keeper.json -o keeper -n keeper -v 0.1.0 ``` ```bash cargo run --bin cargo-progenitor -- progenitor -i sample_openapi/keeper.json -o keeper -n keeper -v 0.1.0 ``` -------------------------------- ### Rust: Implement Request and Response Hooks for API Clients Source: https://context7.com/oxidecomputer/progenitor/llms.txt This snippet demonstrates how to implement custom request and response hooks in Rust using the progenitor_client library. It allows for intercepting and modifying requests (e.g., adding authentication headers) and responses (e.g., logging details) before they are sent or after they are received. This is useful for tasks like logging, authentication, and custom error handling. The implementation involves creating a custom client struct that implements the `ClientInfo` and `ClientHooks` traits. ```rust use progenitor_client::{ClientInfo, ClientHooks, OperationInfo, Error}; // Define custom client wrapper with state pub struct MyClient { baseurl: String, client: reqwest::Client, auth_token: String, request_count: std::sync::atomic::AtomicUsize, } impl MyClient { pub fn new(baseurl: &str, auth_token: String) -> Self { Self { baseurl: baseurl.to_string(), client: reqwest::Client::new(), auth_token, request_count: std::sync::atomic::AtomicUsize::new(0), } } } // Implement ClientInfo to provide metadata impl ClientInfo for MyClient { fn api_version() -> &'static str { "1.0.0" } fn baseurl(&self) -> &str { &self.baseurl } fn client(&self) -> &reqwest::Client { &self.client } fn inner(&self) -> &String { &self.auth_token } } // Implement hooks for custom behavior impl ClientHooks for MyClient { async fn pre( &self, request: &mut reqwest::Request, info: &OperationInfo, ) -> Result<(), Error> { // Add authentication request.headers_mut().insert( reqwest::header::AUTHORIZATION, reqwest::header::HeaderValue::from_str(&format!("Bearer {}", self.auth_token)) .map_err(|e| Error::InvalidRequest(e.to_string()))?, ); // Log request let count = self.request_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); println!("[Request #{}] {} {}", count, request.method(), request.url()); println!(" Operation: {}", info.operation_id); Ok(()) } async fn post( &self, result: &reqwest::Result, info: &OperationInfo, ) -> Result<(), Error> { // Log response match result { Ok(response) => { println!("[Response] {} - Status: {}", info.operation_id, response.status()); if let Some(content_len) = response.headers().get(reqwest::header::CONTENT_LENGTH) { println!(" Content-Length: {:?}", content_len); } } Err(err) => { eprintln!("[Error] {} - {}", info.operation_id, err); } } Ok(()) } } #[tokio::main] async fn main() -> Result<(), Box> { let client = MyClient::new("https://api.example.com", "secret-token".to_string()); // Hooks automatically applied to all requests let result = client.get_user("user123").await?; println!("User: {:?}", result); Ok(()) } ``` -------------------------------- ### generate_api! Macro: Compile-time API Client Generation (Rust) Source: https://context7.com/oxidecomputer/progenitor/llms.txt The `generate_api!` macro generates an API client directly within your Rust source code at compile time. It supports basic and advanced configurations, including specifying the OpenAPI spec path, interface style (Positional or Builder), tag handling, custom inner types, pre/post hooks for request/response manipulation, derives for generated structs, and request timeouts. The generated client can then be used to make API calls. ```rust use progenitor::generate_api; // Simple usage - generates client from OpenAPI spec generate_api!("path/to/openapi_document.json"); // Advanced usage with configuration generate_api!( spec = "../sample_openapi/keeper.json", interface = Builder, // Positional (default) or Builder tags = Separate, // Merged (default) or Separate inner_type = my_client::CustomData, pre_hook = (|request| { println!("Sending request: {:?}", request); }), pre_hook_async = crate::add_auth_headers, post_hook = crate::log_response, derives = [schemars::JsonSchema, Clone], timeout = 30, ); async fn add_auth_headers( req: &mut reqwest::Request, ) -> Result<(), reqwest::header::InvalidHeaderValue> { req.headers_mut().insert( reqwest::header::AUTHORIZATION, reqwest::header::HeaderValue::from_str("Bearer token123")?, ); Ok(()) } fn log_response(result: &reqwest::Result) { if let Ok(response) = result { println!("Response status: {}", response.status()); } } #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::new("https://api.example.com"); let result = client.enrol( "auth-token", &types::EnrolBody { host: "hostname".to_string(), key: "api-key".to_string(), }, ).await?; println!("Result: {:?}", result); Ok(()) } ``` -------------------------------- ### Rust: Customize Generated Types with Patches and Replacements in build.rs Source: https://context7.com/oxidecomputer/progenitor/llms.txt This Rust build script (`build.rs`) demonstrates how to customize generated types using the `progenitor` library. It allows for renaming types, adding derive attributes (like `Hash`, `Eq`, `Clone`), replacing generated types with existing ones (e.g., using `chrono::DateTime`), and configuring how unknown crates are handled. The script reads an OpenAPI specification, applies these customizations via `GenerationSettings`, generates Rust code, and writes it to a file in the `OUT_DIR`. ```rust // build.rs with type customization use progenitor::{Generator, GenerationSettings, TypePatch, TypeImpl}; use std::collections::HashMap; fn main() { let spec_file = std::fs::File::open("../sample_openapi/api.json").unwrap(); let spec: openapiv3::OpenAPI = serde_json::from_reader(spec_file).unwrap(); let mut settings = GenerationSettings::default(); // Rename a type let mut patch = TypePatch::default(); patch.set_rename("UserId".to_string()); settings.with_patch("Uuid", &patch); // Add derives to a specific type let mut user_patch = TypePatch::default(); user_patch.add_derive("Hash"); user_patch.add_derive("Eq"); settings.with_patch("User", &user_patch); // Add derives to all generated types settings .with_derive("Clone") .with_derive("PartialEq"); // Replace generated type with existing type settings.with_replacement( "Timestamp", "chrono::DateTime", std::iter::empty(), ); // Allow external crate types settings .with_unknown_crates(progenitor::UnknownPolicy::Allow) .with_crate("uuid", progenitor::CrateVers::Version("1.0.0".to_string()), None); // Use BTreeMap instead of HashMap for object types settings.with_map_type("BTreeMap"); let mut generator = Generator::new(&settings); let tokens = generator.generate_tokens(&spec).unwrap(); let ast = syn::parse2(tokens).unwrap(); let content = prettyplease::unparse(&ast); let out_path = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("codegen.rs"); std::fs::write(out_path, content).unwrap(); } ``` -------------------------------- ### Rust Client Builder for API Operations Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/builder-generation.md Generates a client method that returns a builder struct for a specific API operation. The builder allows setting path parameters, query parameters, and request bodies before sending the request asynchronously. Dependencies include `std::convert::TryInto` and potentially custom `types` for request/response bodies. ```rust impl Client { pub fn operation_name(&self) -> builder::InstanceCreate { builder::OperationName::new(self) } } mod builder { #[derive(Debug, Clone)] pub struct OperationName<'a> { client: &'a super::Client, path_parameter_1: Result, path_parameter_2: Result, query_parameter_1: Result, query_parameter_2: Result, String>, body: Result, } impl<'a> OperationName<'a> { pub fn new(client: &'a super::Client) -> Self { Self { client, path_parameter_1: Err("path_parameter_1 was not initialized".to_string()), path_parameter_2: Err("path_parameter_2 was not initialized".to_string()), query_parameter_1: Err("query_parameter_1 was not initialized".to_string()), query_parameter_2: Ok(None), body: Err("body was not initialized".to_string()), } } pub fn path_parameter_1(mut self, value: V) -> Self where V: std::convert::TryInto, { self.organization_name = value .try_into() .map_err(|_| "conversion to `String` for path_parameter_1 failed".to_string()); self } pub fn path_parameter_2(mut self, value: V) -> Self where V: std::convert::TryInto, { self.organization_name = value .try_into() .map_err(|_| "conversion to `u32` for path_parameter_2 failed".to_string()); self } pub fn query_parameter_1(mut self, value: V) -> Self where V: std::convert::TryInto, { self.organization_name = value .try_into() .map_err(|_| "conversion to `String` for query_parameter_1 failed".to_string()); self } pub fn query_parameter_2(mut self, value: V) -> Self where V: std::convert::TryInto, { self.organization_name = value .try_into() .map_err(|_| "conversion to `u32` for query_parameter_2 failed".to_string()); self } pub fn body(mut self, value: V) -> Self where V: TryInto, { self.body = value .try_into() .map_err(|_| "conversion to `ThisOperationBody` for body failed".to_string()); self } pub async fn send(self) -> Result< ResponseValue, Error, > { // ... } } ``` -------------------------------- ### Generate API Client with Builder Interface Style in Rust Source: https://context7.com/oxidecomputer/progenitor/llms.txt This Rust snippet demonstrates generating an API client with Progenitor's 'Builder' interface style. This pattern uses a fluent API for setting parameters, offering automatic type conversion and optional parameter handling. Dependencies include `progenitor` and `tokio`. ```rust // Generated by Progenitor with interface = Builder use progenitor::generate_api; generate_api!( spec = "../sample_openapi/nexus.json", interface = Builder, ); #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::new("https://api.oxide.computer"); // Builder style - parameters can be strings, automatic conversion let instance = client .instance_create() .organization_name("my-org") // String converts to types::Name .project_name("my-project") .body( types::InstanceCreate::builder() .name("web-server") .description("Production web server") .hostname("web1.example.com") .ncpus(types::InstanceCpuCount(4)) .memory(types::ByteCount(8 * 1024 * 1024 * 1024)) .network_interfaces(types::InstanceNetworkInterfaceAttachment::Default) .start(true) // Optional fields can be omitted ) .send() .await?; println!("Created instance: {}", instance.id); // Builder pattern allows flexible parameter ordering let users = client .user_list() .limit(50) .page_token("next-page") .sort_by(types::UserSortMode::NameAscending) .send() .await?; for user in users.items { println!("User: {} <{}>", user.name, user.email); } Ok(()) } ``` -------------------------------- ### Include Generated Code in main.rs or lib.rs Source: https://github.com/oxidecomputer/progenitor/blob/main/README.md After generating code using a `build.rs` script, this line includes the generated Rust code into your main application or library. The `concat!` macro and `env!("OUT_DIR")` are used to correctly locate the `codegen.rs` file placed in the output directory by the build script. ```rust include!(concat!(env!("OUT_DIR"), "/codegen.rs")); ``` -------------------------------- ### Generate API Client with Positional Interface Style in Rust Source: https://context7.com/oxidecomputer/progenitor/llms.txt This Rust snippet shows how to generate an API client using Progenitor with the 'Positional' interface style. Parameters are passed in the order they are defined in the OpenAPI specification. This style requires exact type matching for parameters. Dependencies include `progenitor` and `tokio`. ```rust // Generated by Progenitor with interface = Positional (default) use progenitor::generate_api; generate_api!( spec = "../sample_openapi/nexus.json", interface = Positional, ); #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::new("https://api.oxide.computer"); // Parameters passed in order, types must match exactly let instance = client.instance_create( &types::Name("my-org".to_string()), &types::Name("my-project".to_string()), &types::InstanceCreate { name: "web-server".to_string(), description: "Production web server".to_string(), hostname: "web1.example.com".to_string(), ncpus: types::InstanceCpuCount(4), memory: types::ByteCount(8 * 1024 * 1024 * 1024), disks: vec![], network_interfaces: types::InstanceNetworkInterfaceAttachment::Default, external_ips: vec![], user_data: "".to_string(), ssh_public_keys: None, start: true, }, ).await?; println!("Created instance: {}", instance.id); Ok(()) } ``` -------------------------------- ### Generator Class: Build-time Code Generation (Rust) Source: https://context7.com/oxidecomputer/progenitor/llms.txt The `Generator` class in `build.rs` scripts provides programmatic control over API client code generation. It allows customization of generation settings such as interface style, tag handling, derived traits, and timeouts. The generated code is written to the `OUT_DIR` and can be included in the main Rust project. This method is suitable for build-time generation with visible output. ```rust // build.rs use std::{env, fs::{self, File}, path::Path}; fn main() { let src = "../sample_openapi/keeper.json"; println!("cargo:rerun-if-changed={}", src); let file = File::open(src).unwrap(); let spec = serde_json::from_reader(file).unwrap(); // Create generator with custom settings let mut settings = progenitor::GenerationSettings::default(); settings .with_interface(progenitor::InterfaceStyle::Builder) .with_tag(progenitor::TagStyle::Merged) .with_derive("Clone") .with_derive("PartialEq") .with_timeout(60); let mut generator = progenitor::Generator::new(&settings); // Generate the client code let tokens = generator.generate_tokens(&spec).unwrap(); let ast = syn::parse2(tokens).unwrap(); let content = prettyplease::unparse(&ast); // Write to OUT_DIR let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); out_file.push("codegen.rs"); fs::write(out_file, content).unwrap(); // Optional: Generate CLI let cli_tokens = generator.cli(&spec, "my_api_client").unwrap(); let cli_ast = syn::parse2(cli_tokens).unwrap(); let cli_content = prettyplease::unparse(&cli_ast); let mut cli_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); cli_file.push("cli.rs"); fs::write(cli_file, cli_content).unwrap(); } // src/lib.rs or src/main.rs include!(concat!(env!("OUT_DIR"), "/codegen.rs")); #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::new("https://api.example.com"); let response = client.ping().await?; println!("API is alive: {:?}", response); Ok(()) } ``` -------------------------------- ### Async API Operation Method Generation (Rust) Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/positional-generation.md Generates an asynchronous method for an OpenAPI operation. It accepts path parameters, optional query parameters, and an optional request body. The method returns a Result containing a ResponseValue on success or an Error on failure. It relies on types defined in `progenitor_client`. ```rust impl Client { pub async fn operation_name<'a>( &'a self, // Path parameters (if any) come first and are always mandatory path_parameter_1: String, path_parameter_2: u32, // Query parameters (if any) come next and may be optional query_parameter_1: String, query_parameter_2: Option, // A body parameter (if specified) comes last body: &types::ThisOperationBody, ) -> Result< ResponseValue, Error, > { // ... } } ``` -------------------------------- ### Rust Stream Builder for Paginated Dropshot Operations Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/builder-generation.md Generates a `stream` method on the builder struct for operations using Dropshot's pagination. This method returns an asynchronous stream that yields items without manual page fetching. Requires `futures` crate for stream handling and `Unpin` trait. ```rust impl<'a> OperationName<'a> { pub fn stream( self, ) -> impl futures::Stream< Item = Result> > + Unpin + 'a { // ... } } } // Example Consumer: // let mut stream = client.operation_name().stream(); // loop { // match stream.try_next().await { // Ok(Some(item)) => println!("item {:?}", item), // Ok(None) => { // println!("done."); // break; // } // Err(_) => { // println!("error!"); // break; // } // } // } ``` -------------------------------- ### Error Enum Variants for Request Error Handling Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/progenitor-client.md Illustrates the `Error` enum in Rust, which categorizes various potential errors encountered during client operations. It covers request validation failures, communication issues, unexpected responses, and custom errors, providing a comprehensive error handling mechanism. ```rust pub enum Error { InvalidRequest(String), CommunicationError(reqwest::Error), InvalidUpgrade(reqwest::Error), ErrorResponse(ResponseValue), ResponseBodyError(reqwest::Error), InvalidResponsePayload(bytes::Bytes, reqwest::Error), UnexpectedResponse(reqwest::Response), Custom(String), } ``` -------------------------------- ### Dropshot Paginated Operation Stream Generation (Rust) Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/positional-generation.md Generates a method for operations utilizing Dropshot's pagination. This method returns a stream that yields individual items from the paginated collection. It allows consumers to iterate over all items without manual page fetching. The stream produces Results containing the success response type or an error type. ```rust pub fn operation_name_stream<'a>( &'a self, // Specific parameters... limit: Option, ) -> impl futures::Stream< Item = Result> > + Unpin + '_ { // ... } ``` -------------------------------- ### ResponseValue Structure and Implementations Source: https://github.com/oxidecomputer/progenitor/blob/main/docs/progenitor-client.md Details the `ResponseValue` struct and its key implementations in Rust. This type wraps the success response data, providing access to the HTTP status code, headers, and the inner value. It also implements traits like `Deref` for easy access to the inner type. ```rust /// Success value returned by generated client methods. pub struct ResponseValue { .. } impl ResponseValue { pub fn status(&self) -> &reqwest::StatusCode { .. } pub fn headers(&self) -> &reqwest::header::HeaderMap { .. } pub fn into_inner(self) -> T { .. } } impl std::ops::Deref for ResponseValue { type Target = T; .. } impl std::ops::DerefMut for ResponseValue { .. } impl std::fmt::Debug for ResponseValue { .. } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.