Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
P

progenitor

> 编程语言
Open source

An OpenAPI client generator

980 stars0 likes1 views
WebsiteGitHub

About

An OpenAPI client generator

Progenitor

Progenitor is a Rust crate for generating opinionated clients from API descriptions in the OpenAPI 3.0.x specification. It makes use of Rust futures for async API calls and Streams for paginated interfaces.

It generates a type called Client with methods that correspond to the operations specified in the OpenAPI document.

Progenitor can also generate a CLI to interact with an OpenAPI service instance, and httpmock helpers to create a strongly typed mock of the OpenAPI service.

The primary target is OpenAPI documents emitted by Dropshot-generated APIs, but it can be used for many OpenAPI documents. As OpenAPI covers a wide range of APIs, Progenitor may fail for some OpenAPI documents. If you encounter a problem, you can help the project by filing an issue that includes the OpenAPI document that produced the problem.

Using Progenitor

There are three different ways of using the progenitor crate. The one you choose will depend on your use case and preferences.

Macro

The simplest way to use Progenitor is via its generate_api! macro.

In a source file (often main.rs, lib.rs, or mod.rs) simply invoke the macro:

rust
generate_api!("path/to/openapi_document.json");

You'll need to add the following to Cargo.toml:

toml
[dependencies]
futures = "0.3"
progenitor = { git = "https://github.com/oxidecomputer/progenitor" }
reqwest = { version = "0.13", features = ["json", "query", "stream"] }
serde = { version = "1.0", features = ["derive"] }

In addition, if the OpenAPI document contains string types with the format field set to date or date-time, include

toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }

Similarly, if there is a format field set to uuid:

toml
[dependencies]
uuid = { version = "1.0.0", features = ["serde", "v4"] }

And if there are any websocket channel endpoints:

toml
[dependencies]
base64 = "0.21"
rand = "0.8"

If types include regular expression validation:

toml
[dependencies]
regress = "0.4.1"

The macro has some additional fancy options to control the generated code:

…

Note that the macro will be re-evaluated when the spec OpenAPI document changes (when its mtime is updated).

If you derive schemars::JsonSchema on generated types (see derives = [ ... ] in the macro example above), add a dependency on schemars and enable the necessary features for each formatted type that appears in your spec. For example:

toml
[dependencies]
schemars = { version = "0.8", features = ["chrono", "uuid1"] }

build.rs

Progenitor includes an interface appropriate for use in a build.rs file. While slightly more onerous than the macro, a builder has the advantage of making the generated code visible. The capability of generating a CLI and httpmock helpers is only available using build.rs and the Generator functions cli and httpmock respectively.

The build.rs file should look something like this:

…

In a source file (often main.rs, lib.rs, or mod.rs) include the generated code:

rust
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

You'll need to add the following to Cargo.toml:

toml
[dependencies]
futures = "0.3"
progenitor-client = { git = "https://github.com/oxidecomputer/progenitor" }
reqwest = { version = "0.13", features = ["json", "query", "stream"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[build-dependencies]
prettyplease = "0.2.22"
progenitor = { git = "https://github.com/oxidecomputer/progenitor" }
serde_json = "1.0"
syn = "2.0"

(chrono, uuid, base64, and rand as above)

Note that progenitor is used by build.rs, but the generated code required progenitor-client.

Static Crate

Progenitor can be run to emit a stand-alone crate for the generated client. This ensures no unexpected changes (e.g. from updates to progenitor). It is however, the most manual way to use Progenitor.

Usage:

cargo progenitor

Options:
    -i INPUT            OpenAPI definition document (JSON or YAML)
    -o OUTPUT           Generated Rust crate directory
    -n CRATE            Target Rust crate name
    -v VERSION          Target Rust crate version

For example:

cargo install cargo-progenitor
cargo progenitor -i sample_openapi/keeper.json -o keeper -n keeper -v 0.1.0

... or within the repo:

cargo run --bin cargo-progenitor -- progenitor -i sample_openapi/keeper.json -o keeper -n keeper -v 0.1.0

This will produce a package in the specified directory.

Options --license and --registry-name may also be used to improve metadata before publishing the static crate.

The output will use the published progenitor-client crate by default if progenitor is built in release mode. When built in debug mode, the progenitor-client will be inlined into the generated crate by default. The command line flag --include-client true|false can be used to override the default behavior. A value of true copies in the client code; a value of false includes a dependency on progenitor-client in the generated Cargo.toml file.

Here is an excerpt from the emitted Cargo.toml:

toml
[dependencies]
bytes = "1.9"
chrono = { version = "0.4", default-features=false, features = ["serde"] }
futures-core = "0.3"
progenitor-client = "0.9.1"
reqwest = { version = "0.13", default-features=false, features = ["json", "query", "stream"] }
serde = { version = "1.0", features = ["derive"] }
serde_urlencoded = "0.7"

Here's another example of dependencies with --include-client true:

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.13", default-features=false, features = ["json", "query", "stream"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"

Generation Styles

Progenitor can generate two distinct interface styles: positional and builder (described below). The choice is simply a matter of preference that many vary by API and taste.

Positional (current default)

The "positional" style generates Client methods that accept parameters in order, for example:

rust
impl Client {
    pub async fn instance_create(
        &'a self,
        organization_name: &'a types::Name,
        project_name: &'a types::Name,
        body: &'a types::InstanceCreate,
    ) -> Result, Error> {
        // ...
    }
}

A caller invokes this interface by specifying parameters by position:

rust
let result = client.instance_create(org, proj, body).await?;

Note that the type of each parameter must match precisely--no conversion is done implicitly.

Builder

The "builder" style generates Client methods that produce a builder struct. API parameters are applied to that builder, and then the builder is executed (via a send method). The code is more extensive and more complex to enable simpler and more legible consumers:

…

Note that, unlike positional generation, consumers can supply compatible (rather than invariant) parameters:

rust
let result = client
    .instance_create()
    .organization_name("org")
    .project_name("proj")
    .body(body)
    .send()
    .await?;

The string parameters will implicitly have TryFrom::try_from() invoked on them. Failed conversions or missing required parameters will result in an Error result from the send() call.

Generated struct types also have builders so that the body parameter can be constructed inline:

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?;

Consumers do not need to specify parameters and struct properties that are not required or for which the API specifies defaults. Neat!

Enabling the builder style in build.rs

To enable the builder style, the build.rs file should look something like this:

…

Changing default client settings

Currently, the generated code doesn't deal with request headers. To add default headers to all requests, you can use the default_headers method when constructing the Client.

…

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rust

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言