Skip to main content

openapi-yagen logo

Yet another OpenAPI generator (openapi-yagen)

GitHub release Build status Docker image

Main features:

  • a small generator core written in C++
  • specific generators are written in JavaScript with support for ES2023 features (QuickJS) and Inja templates (like Jinja)
  • extending templates with additional functions defined in JS
  • possibility to extend existing generators by overriding some files from a specified directory
  • post-processing of output files using custom tools (code formatters, linters, checkers...)
  • using generators available via HTTP/S (directly from GitHub, or other sources). The curl tool is required.
  • understands OpenAPI 3.0, 3.1, 3.2, and Swagger 2.0, converting between them automatically so a generator only needs to be written against one 3.x version (see convert below, and generator-format.md)

Installation​

Statically-linked binaries are published on the releases page: openapi-yagen for Linux x86_64, openapi-yagen-linux-arm64 for Linux arm64, and openapi-yagen-macos (a universal binary - runs on both Intel and Apple Silicon Macs). Install one to ~/.local/bin - the standard per-user executable location (see file-hierarchy(7)), already on PATH on most modern distributions - no root/sudo needed:

mkdir -p ~/.local/bin && curl -L https://github.com/openapi-yagen/openapi-yagen/releases/latest/download/openapi-yagen -o ~/.local/bin/openapi-yagen && chmod +x ~/.local/bin/openapi-yagen

If ~/.local/bin isn't already on your PATH, add this to your shell's startup file (e.g. ~/.bashrc, ~/.zshrc) and restart your shell:

export PATH="$HOME/.local/bin:$PATH"

macOS​

Grab the universal binary instead, and clear its quarantine flag - it isn't code-signed or notarized (no Apple Developer account behind this project), so Gatekeeper otherwise refuses to run it:

mkdir -p ~/.local/bin && curl -L https://github.com/openapi-yagen/openapi-yagen/releases/latest/download/openapi-yagen-macos -o ~/.local/bin/openapi-yagen && chmod +x ~/.local/bin/openapi-yagen && xattr -d com.apple.quarantine ~/.local/bin/openapi-yagen

Unlike on Linux, ~/.local/bin is never on PATH by default on macOS - don't skip the export PATH step above (/usr/local/bin, the one macOS location that is on PATH out of the box, is normally root-owned and needs sudo to write to, unless Homebrew already claimed it).

Or skip both the quarantine step and the PATH setup with the Docker image below.

Docker​

A runtime image is published to the GitHub Container Registry as ghcr.io/openapi-yagen/openapi-yagen, for linux/amd64 and linux/arm64 - the latter runs natively (no emulation) on Apple Silicon under Docker Desktop, making this the easiest route on macOS: no Gatekeeper quarantine step, and no local build toolchain either. Mount the directory holding your spec (and any --override-dir/generator files) as /workspace, its working directory:

docker run --rm -v "$PWD":/workspace ghcr.io/openapi-yagen/openapi-yagen \
generate openapi.yaml -g builtin:kotlin_ktor_client -o out

Every subcommand below (generate, convert, list-generators, extract, info) works the same way - append it and its options after the image name exactly as you would after the openapi-yagen binary itself. -g/--override-dir/-o/spec-file paths are resolved inside the container, so keep them relative to the directory you mounted. Loading a generator from an HTTP(S) URL (-g https://...) works too - the image bundles curl for that.

An alias makes it behave like a locally installed binary for the rest of a shell session:

alias openapi-yagen='docker run --rm -v "$PWD":/workspace ghcr.io/openapi-yagen/openapi-yagen'

CLI reference​

Supported CLI root options and subcommands:

Usage: ./openapi-yagen [OPTIONS] [SUBCOMMAND]

Options:
-h,--help Print this help message and exit
-v,--version Print version and exit

Subcommands:
generate, g Generate sources from openapi specification
convert, c Convert an OpenAPI spec from one version to another
list-generators, l List built-in generators bundled with this binary (use with -g builtin:<name>)
extract, e Extract a built-in generator's files to a directory, e.g. to customize it further without starting from scratch
info, i Show a generator's metadata: name, description, OpenAPI version, and declared variables

Generate subcommand​

Generate sources from openapi specification
Usage: ./openapi-yagen generate [OPTIONS] [spec-file]

Positionals:
spec-file TEXT [openapi.yaml]
Specification file

Options:
-h,--help Print this help message and exit
--override-dir TEXT Directory with overridden generator files
-o,--out-dir TEXT [.] Output directory for generated code
-g,--generator TEXT REQUIRED
Path to generator. It can be a directory, zip archive, HTTP URL, or a built-in generator (builtin:<name> - see the list-generators command)
-p,--post-process TEXT ... Post process file with specified tool for extension
-c,--clear Clear output directory before generating
-v,--var TEXT ... Set variable. Syntax is: -v (var_name)=(var_value)
-t,--tags TEXT ... Only generate operations/models tagged with one of these tags (default: generate everything)
--header TEXT Override the auto-generated "do not edit" header text prepended to every generated file (default: names the generator; wrapped in whichever comment syntax the file's own extension/generator.yml commentStyle resolves to)
--no-header Disable the auto-generated "do not edit" header entirely

Auto-generated file headers​

Every file written via renderTemplate/copyFile (see generator-format.md) gets a "do not edit" header prepended automatically, wrapped in whatever comment syntax that file's own extension uses (# for .rb/.py/.yml, // for .kt/.go/.ts/.h/..., and so on - see lib/generator/file_header.cpp for the full table). An extension the table doesn't recognize (e.g. a hypothetical .json output, which has no comment syntax at all) is left untouched rather than corrupted with a guess. renderTemplateToString's output never gets one, since it's an in-memory fragment (e.g. for a {% include %}-style partial), not a real output file.

The default header names the specific generator that produced the file:

// Code generated by openapi-yagen (kotlin_ktor_server). DO NOT EDIT.
// https://openapi-yagen.github.io

--header "<text>" replaces the message itself (still auto-wrapped in the resolved comment syntax); --no-header disables the header entirely for that run. A generator can also override which comment style the engine picks for its own output, regardless of extension, via generator.yml's commentStyle field (see generator-format.md) - useful if the built-in extension table guesses wrong for an unusual output format.

Clearing the output directory​

-c/--clear deletes everything inside --out-dir before generating; without it existing files are kept and only overwritten. As a safeguard, clearing is refused when the output directory is the current directory (the default -o .) or one of its parents - point --out-dir at a dedicated directory for generated code.

Post-processing generated files​

-p/--post-process <spec> runs an external command on each generated file, keyed by extension:

-p "ts:prettier --write %file%"

%file% is replaced with the generated file's path. The ext1,ext2: prefix is optional - a -p value with no prefix (just a bare command) runs on every generated file, regardless of extension. Pass -p more than once to chain multiple tools (e.g. one for .kt files, a different one for everything else).

Filtering by tags​

-t/--tags <tag>... restricts generation to a subset of the spec's OpenAPI tags. With no -t flag (the default), everything in the spec is generated, unchanged from before this option existed.

openapi-yagen generate openapi.yaml -g builtin:kotlin_ktor_client -o out -t pets -t orders

An operation is kept if it declares any of the given tags (an operation with several tags needs only one to match); an untagged operation is dropped as soon as -t is used at all. This is an engine-level filter, applied before the spec ever reaches the generator's JavaScript: a generator doesn't need to know tag filtering exists to support it. Filtering happens in three steps:

  1. Operations - only matching operations survive, in paths and webhooks alike; a path left with none of its operations remaining is dropped entirely.
  2. Models - components.schemas (and parameters/requestBodies/responses/headers) are pruned down to whatever's still reachable from a surviving operation's parameters, request body, or responses (following $refs, properties, items, allOf/oneOf/anyOf, and so on) - a model only ever used by a filtered-out operation is dropped along with it; one used by both a surviving and a filtered-out operation is kept.
  3. The document's own tags: list is trimmed to the tags actually passed to -t.

Convert subcommand​

Convert an OpenAPI spec from one version to another
Usage: ./openapi-yagen convert [OPTIONS] spec-file

Positionals:
spec-file TEXT REQUIRED Specification file to convert

Options:
-h,--help Print this help message and exit
--from TEXT Source OpenAPI version (e.g. 2.0, 3.0, 3.1, 3.2) - auto-detected from the spec's own "openapi"/"swagger" field if omitted
--to TEXT REQUIRED Target OpenAPI version (e.g. 2.0, 3.0, 3.1, 3.2)
-o,--out TEXT REQUIRED Output file path
--format TEXT Output format: "yaml" or "json" - inferred from --out's extension if omitted
openapi-yagen convert openapi-3.1.yaml --to 3.0 -o openapi-3.0.yaml

List-generators subcommand​

List built-in generators bundled with this binary (use with -g builtin:<name>)
Usage: ./openapi-yagen list-generators [OPTIONS]

Options:
-h,--help Print this help message and exit
-d,--describe Also print each generator's description

Extract subcommand​

Extract a built-in generator's files to a directory, e.g. to customize it further without starting from scratch
Usage: ./openapi-yagen extract [OPTIONS] name

Positionals:
name TEXT REQUIRED Built-in generator name, with or without a "builtin:" prefix (see the list-generators command)

Options:
-h,--help Print this help message and exit
-o,--out-dir TEXT REQUIRED Output directory

Info subcommand​

Show a generator's metadata: name, description, OpenAPI version, and declared variables
Usage: ./openapi-yagen info [OPTIONS] generator

Positionals:
generator TEXT REQUIRED Path to generator. It can be a directory, zip archive, HTTP URL, or a built-in generator (builtin:<name> - see the list-generators command)

Options:
-h,--help Print this help message and exit
openapi-yagen info builtin:kotlin_ktor_client

This is the same version conversion generate runs automatically when a spec's version doesn't match a generator's declared openApiVersion (see generator-format.md), exposed as a standalone tool - useful to inspect what a spec looks like in another version, or to pin a spec to one version before checking it in. It's scoped to the constructs the engine itself models (see the same doc section) rather than a fully lossless converter for every possible spec field.

The output's top-level keys (openapi/info/paths/... or swagger/info/host/... for a 2.0 target) are written in the specification's own documented order, not alphabetical - nested objects still come out alphabetical. String values that would otherwise be ambiguous when read back (a response status code like 200, or a string that happens to look like a bool/float/null, e.g. "true"/"1.5") are always quoted, so they round-trip as strings rather than being silently misread as a different YAML type by another tool.

Examples​

Running a generator directly from GitHub​

-g also accepts an HTTP/S URL pointing at a generator's source folder, so you can run a generator straight from GitHub without cloning it. openapi-yagen fetches the generator's files (generator.yml, scripts, templates) over HTTP/S as it needs them, so the URL must point at the raw folder contents (curl is required). For example, using this repo's own sample_cpp_models_generator:

openapi-yagen generate openapi.yaml \
-g https://raw.githubusercontent.com/openapi-yagen/openapi-yagen/master/generators/sample_cpp_models_generator/src \
-o out

Using a built-in generator​

A handful of the generators in generators/ are embedded directly into the openapi-yagen binary at build time, so they work offline with no local checkout, zip, or network access:

openapi-yagen list-generators
openapi-yagen generate openapi.yaml -g builtin:kotlin_ktor_client -o out -v packageName=com.example.petstore

To fork a built-in generator and customize it, extract its files to a directory first:

openapi-yagen extract kotlin_ktor_client -o my-kotlin-client-generator

Writing a generator​

See docs/ for the full generator-writing documentation: a step-by-step tutorial building a small generator from scratch, the generator format (folder layout, generator.yml), the JavaScript API (globals, built-in functions), and templating (Inja syntax, calling functions from templates).

Generators​

Generators are located in the generators folder. See generators/README.md for the full list - which ones are built into the openapi-yagen binary itself (usable via -g builtin:<name>, see "Using a built-in generator" above) and which need -g pointed at a directory/zip/URL - plus a link to each generator's own documentation.

Development​

The project is a CMake + Conan 2 C++20 codebase (core in lib/, CLI in cli/, tests in test/). See AGENTS.md and docs/develop/ for build/test commands, project architecture, and coding conventions - it's the entry point for working on the openapi-yagen engine itself (as opposed to writing a generator, which docs/ covers).

TODO​

  • Add schema validation (structural validation via the typed OpenAPI model itself - no longer depends on an external JSON Schema validator)
  • Support multiple OpenAPI versions (2.0, 3.0, 3.1, 3.2) with automatic conversion between them
  • Add configuration variables
  • Improve documentation and add more examples
  • Use https://github.com/batterycenter/embed to embed some popular templates into binary
  • Add remote templates reading (from GitHub for example)
  • Command to create generator stub
  • Command to show available variables for generator
  • Restrict access to files outside working folder
  • Post-processing files in batches rather than one at a time
  • Improve documentation (postprocessing, more examples in README)