An OpenAPI code generator for polymorphic specs.
Many OpenAPI specs use allOf to model inheritance; and oneOf, anyOf, and discriminators to model polymorphic or algebraic data types. These patterns are powerful, but can be tricky to support correctly, and most code generators struggle with them. Ploidy was built specifically with inheritance and polymorphism in mind, and aims to generate clean, type-safe, and idiomatic Rust that looks like how you'd write it by hand.
You can download a pre-built binary of Ploidy for your platform, or install from source with:
cargo install --locked ploidy💡 Tip: The -linux-musl binaries are statically linked with musl, and are a good choice for running Ploidy on CI platforms like GitHub Actions.
To generate a complete Rust client crate from your OpenAPI spec, run:
ploidy codegen <INPUT-SPEC> <OUTPUT-DIR> rustThis produces a ready-to-use crate that includes:
- A
Cargo.tomlfile, which you can extend with additional metadata, dependencies, or examples... - A
typesmodule, which contains Rust types for every schema defined in your spec, and... - A
clientmodule, with a RESTful HTTP client that provides async methods for every operation in your spec.
| Flag | Description |
|---|---|
-c, --check |
Run cargo check on the generated code |
--name <NAME> |
Set or override the generated package name. If not passed, and a Cargo.toml already exists in the output directory, preserves the existing package.name; otherwise, defaults to the name of the output directory |
--version <bump-major, bump-minor, bump-patch> |
If a Cargo.toml already exists in the output directory, increments the major, minor, or patch component of package.version. If not passed, preserves the existing package.version. Ignored if the package doesn't exist yet |
🎉 Ploidy is a good choice if:
- Your OpenAPI spec uses
allOf,oneOf, oranyOf. - You have a large or complex spec that's challenging for other generators.
- Your spec has inline schemas, and you'd like to generate the same strongly-typed models for them as for named schemas.
- You prefer convention over configuration.
- Your spec uses OpenAPI features that Ploidy doesn't support yet. Progenitor, Schema Tools or openapi-generator might be better choices, especially if your spec is simpler...but please open an issue for Ploidy to support the features you need!
- You'd like to use a custom template for the generated code, or a different HTTP client; or to generate synchronous code. A template-based generator like openapi-generator could better meet your needs.
- You need to target a language other than Rust. openapi-generator supports many more languages; as does swagger-codegen, if you don't need OpenAPI 3.1+ support.
- Your spec uses OpenAPI (Swagger) 2.0. Ploidy only supports OpenAPI 3.0+, but openapi-generator and swagger-codegen support older versions.
- You need to generate server stubs. Ploidy only generates clients, but openapi-generator can generate server stubs for Rust, and Dropshot could be a good alternative.
- You'd like a more mature, established tool.
Here are some of the things that make Ploidy different.
Ploidy is designed from the ground up to handle inheritance and polymorphism correctly:
- ✅
allOf: Structs with fields from all parent schemas. - ✅
oneOfwith discriminator: Enums with named newtype variants for each mapping, represented as an internally tagged Serde enum. - ✅
oneOfwithout discriminator: Enums with automatically named (V1,V2,Vn...) variants for each mapping, represented as an untagged Serde enum. - ✅
anyOf: Structs with optional flattened fields for each mapping.
Ploidy gives you speed and correctness, for quick iteration and zero manual fixes.
⏱️ Example: It takes Ploidy 5 seconds to generate a working crate for a large (2.6 MB JSON) OpenAPI spec, with ~3500 schemas and ~1300 operations.
Ploidy makes the choices that a Rust developer would make:
- Async by default, using the Tokio runtime.
- Reqwest, the most popular HTTP client, for making requests.
- Idiomatic type names, with collision detection across case transformations.
Ploidy intentionally keeps configuration to a minimum: just a handful of command-line options; no settings files; and no design choices to make.
This philosophy means that Ploidy might not be the right tool for every job. Ploidy's goal is to produce ready-to-use code for ~90% of use cases with zero configuration, but if you'd like more control over the generated code, check out one of the other great tools mentioned above!
Generated code looks like it was written by an experienced Rust developer:
- Built-in traits for generated Rust types:
From<T>for each polymorphic enum variant;FromStrandDisplayfor string enums; standard derives for all types (plusHashandEqfor all hashable types, andDefaultfor types with all optional fields; all derived automatically).
For example:
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Customer {
pub id: String,
pub email: String,
#[serde(skip_serializing_if = "Absent::is_absent")]
pub name: Option<String>,
}Ploidy takes a somewhat different approach to code generation. If you're curious about how it works, this section is for you!
Ploidy processes an OpenAPI spec in three stages:
📝 Parsing a JSON or YAML OpenAPI spec into Rust data structures. The parser is intentionally forgiving: short of syntax errors and type mismatches, Ploidy doesn't rigorously enforce OpenAPI (or JSON Schema Draft 2020-12) semantics.
🏗️ Constructing an IR (intermediate representation). Ploidy constructs a type graph from the spec, which lets it answer questions like "which types can derive Eq, Hash, and Default?" and "which fields need Box<T> to break cycles?"
✍️ Generating code from the IR. During the final stage, Ploidy builds proper Rust syntax trees from the processed schema, prettifies the code, and writes the generated code to disk.
Unlike code generators that use string templates, Ploidy uses Rust's syn and quote crates to generate real syntax trees. The generated code has the advantage of being syntactically valid by construction.
Schemas that represent graph and tree-like structures typically contain circular references: a User has friends: Vec<User>; a Comment has a parent: Option<Comment> and children: Vec<Comment>, and so on. Ploidy detects these cycles, and inserts Box<T> only where necessary.
For example, given a schema like:
Comment:
type: object
properties:
text:
type: string
required: true
parent:
schema:
$ref: "#/components/schemas/Comment"
children:
type: array
items:
$ref: "#/components/schemas/Comment"Ploidy generates:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
pub text: String,
pub parent: Option<Box<Comment>>,
pub children: Vec<Comment>,
}(Since Vec<T> is already indirect, only the parent field needs boxing).
OpenAPI specs can define schemas directly at their point of use—in operation parameters, in request and response bodies, or nested within other schemas—rather than in the /components/schemas section. These are called inline schemas.
Many code generators treat inline inline schemas as untyped values (Any or serde_json::Value), but Ploidy generates the same strongly-typed models for inline schemas as it does for named schemas. Inline schemas are named based on where they occur in the spec, and are namespaced in submodules within the parent schema module.
For example, given an operation with an inline response schema:
/users/{id}:
get:
operationId: getUser
responses:
'200':
content:
application/json:
schema:
type: object
properties:
id:
type: string
email:
type: string
name:
type: stringPloidy generates:
mod types {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GetUserResponse {
pub id: String,
pub email: String,
pub name: String,
}
}The inline schema gets a descriptive name, and the same derives as any named schema. This "just works": inline schemas are first-class types in the generated code.
We love contributions: issues, feature requests, discussions, code, documentation, and examples are all welcome!
If you find a case where Ploidy fails, or generates incorrect or unidiomatic code, please open an issue with your OpenAPI spec.
Other areas where we'd love help are:
- Additional examples, with real-world specs.
- Test coverage, especially for edge cases.
- Documentation improvements.
Ploidy only targets Rust now, but its architecture is designed to support other languages. Our philosophy is to only support languages where we can:
- Parse the target language properly.
- Generate valid syntax trees that are correct by construction, rather than interpolating string templates.
- Maintain the same correctness guarantees and generated code quality as our Rust pipeline.
This does mean that Ploidy won't target every language. We'd rather support three languages perfectly, than a dozen languages with gaps.
Ploidy is inspired by, learns from, and builds on the wonderful work of:
- The OpenAPI ecosystem: openapi-generator, Progenitor, and other code generators.
- The async Rust ecosystem: Tokio and Reqwest.
- The Rust parsing ecosystem:
quote,serde,syn, andwinnow.