Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ jobs:
# Keep cargo-install artifacts inside the target tree restored by
# rust-cache; the script still isolates and removes its package root.
CARGO_TARGET_DIR: ${{ github.workspace }}/target/install-smoke
- name: Build Action binary override
run: cargo build --locked --bin openapi-to-rust
- name: Smoke-test the local GitHub Action
uses: ./
with:
binary: ${{ github.workspace }}/target/debug/openapi-to-rust
command: check
manifest: tests/fixtures/client-sync/openapi-clients.yml

msrv:
runs-on: ubuntu-latest
Expand Down
52 changes: 48 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,47 @@ jobs:
RUSTDOCFLAGS: -D warnings
- run: scripts/install-smoke.sh

publish:
binaries:
needs: verify
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
executable: openapi-to-rust
- os: macos-15-intel
target: x86_64-apple-darwin
executable: openapi-to-rust
- os: macos-15
target: aarch64-apple-darwin
executable: openapi-to-rust
- os: windows-latest
target: x86_64-pc-windows-msvc
executable: openapi-to-rust.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build release binary
shell: bash
run: cargo build --locked --release --target "${{ matrix.target }}" --bin openapi-to-rust
- name: Package release binary
shell: bash
env:
TARGET: ${{ matrix.target }}
EXECUTABLE: ${{ matrix.executable }}
run: bash scripts/package-release.sh
- uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: dist/*
if-no-files-found: error

publish:
needs: [verify, binaries]
runs-on: ubuntu-latest
environment: crates-io
permissions:
Expand All @@ -41,6 +80,11 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/download-artifact@v4
with:
pattern: binary-*
path: release-assets
merge-multiple: true
- name: Authenticate with crates.io
id: crates-io-auth
uses: rust-lang/crates-io-auth-action@v1
Expand All @@ -52,6 +96,6 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
tag="${GITHUB_REF#refs/tags/}"
gh release create "$tag" \
--title "$tag" \
--generate-notes
gh release view "$tag" >/dev/null 2>&1 \
|| gh release create "$tag" --title "$tag" --generate-notes
gh release upload "$tag" release-assets/* --clobber
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes are recorded here. This project follows semantic versioning,
with one pre-1.0 qualification: a minor release may change generated Rust APIs
when correcting output that was wrong or incomplete on the wire.

## [Unreleased]

### Added

- `openapi-to-rust clients check|update|sync` manages a versioned YAML manifest
of checked-in and remote API clients, validates and vendors remote spec
changes, verifies generated output, and compiles the configured Cargo target.
- The repository is directly usable as a GitHub Action. Scheduled remote syncs
can create or update a stable pull request, using a draft when generation or
compilation fails so upstream drift remains visible.
- Tagged releases publish checksummed native CLI archives for Linux x86-64,
macOS x86-64/Apple Silicon, and Windows x86-64. The Action installs the
matching prebuilt instead of compiling the generator.

## [0.11.0] - 2026-07-27

Nearly everything here was found by generating a client from RunPod's published
Expand Down
98 changes: 96 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ cargo install --locked openapi-to-rust
```

This compiles the CLI locally and requires a Rust toolchain. Prebuilt binaries
are not currently published. If you use the generator as a Rust library instead,
run `cargo add openapi-to-rust`.
for Linux x86-64, macOS x86-64/Apple Silicon, and Windows x86-64 are attached
to each GitHub Release with SHA-256 checksums. If you use the generator as a
Rust library instead, run `cargo add openapi-to-rust`.

## Try it in one command

Expand Down Expand Up @@ -146,6 +147,94 @@ Then:
openapi-to-rust generate --config openapi-to-rust.toml
```

### Keep a collection of clients current

`openapi-clients.yml` declares checked-in and remotely sourced clients. Each
entry uses an ordinary generator TOML and names the Cargo package that must
continue to compile. The TOML's `spec_path` must match the local `path` or
remote `vendor` path in this manifest.

```yaml
version: 1
clients:
- name: internal
spec:
path: openapi/internal.yaml
config: clients/internal/openapi-to-rust.toml
cargo:
manifest_path: Cargo.toml
package: internal-api
test: true

- name: runpod-v2
spec:
url: https://api.runpod.io/v2/openapi.json
vendor: specs/runpod/v2.json
normalize:
strip:
- /info/x-build-time
config: crates/runpod-api/gen/v2.toml
cargo:
manifest_path: Cargo.toml
package: runpod-api
```

Use `check` in pull-request CI. It performs no source or generated-file writes
and tells contributors to run `update` when output is stale. `update` uses only
checked-in local or vendored documents. `sync` is the networked operation: it
validates remote documents, ignores configured volatile fields when comparing
them, vendors meaningful changes, regenerates, and runs Cargo checks.

```bash
openapi-to-rust clients check
openapi-to-rust clients update
openapi-to-rust clients sync
openapi-to-rust clients sync --client runpod-v2
```

For an internal checked-in API, keep the Action read-only:

```yaml
permissions:
contents: read

steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: gpu-cli/openapi-to-rust@v0.12.0
with:
command: check
manifest: openapi-clients.yml
```

For remote APIs, schedule `sync` and let the Action maintain one stable pull
request. Successful changes produce a normal PR; generation or compilation
failures produce a draft PR containing the upstream spec change for diagnosis.

```yaml
name: Sync OpenAPI clients
on:
schedule:
- cron: "17 6 * * *"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: gpu-cli/openapi-to-rust@v0.12.0
with:
command: sync
manifest: openapi-clients.yml
pull-request: true
```

### Select only the client operations you use

`[client]` is optional. Without it—or with an empty `operations` list—the HTTP client keeps every operation for backward compatibility. Selectors share the server grammar: exact `operationId`, exact `METHOD /path`, or `tag:<name>`.
Expand Down Expand Up @@ -569,6 +658,11 @@ openapi-to-rust generate --config openapi-to-rust.toml
openapi-to-rust generate --config openapi-to-rust.toml --types-conservative
openapi-to-rust validate --config openapi-to-rust.toml

# Multi-client lifecycle.
openapi-to-rust clients check
openapi-to-rust clients update
openapi-to-rust clients sync --client runpod-v2

# Server scope management — preserves TOML formatting (toml_edit)
openapi-to-rust server list # all operations
openapi-to-rust server list --tag Responses # filter by tag
Expand Down
100 changes: 100 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: OpenAPI to Rust client sync
description: Generate, verify, compile, and optionally update pull requests for Rust API clients.

inputs:
command:
description: check, update, or sync
required: false
default: check
manifest:
description: Path to the versioned client manifest
required: false
default: openapi-clients.yml
client:
description: Optional client name to run
required: false
default: ""
version:
description: Generator version; inferred from a version-tagged Action reference when omitted
required: false
default: ""
binary:
description: Existing openapi-to-rust binary to use instead of downloading a release
required: false
default: ""
pull-request:
description: Create or update a pull request when sync changes files
required: false
default: "false"
pull-request-branch:
description: Stable branch used for automated sync pull requests
required: false
default: openapi-sync/clients
pull-request-title:
description: Pull request title
required: false
default: "chore: sync generated OpenAPI clients"
pull-request-labels:
description: Newline-separated pull request labels
required: false
default: openapi-sync

outputs:
changed:
description: Number of clients whose remote specification or generated output changed
value: ${{ steps.run.outputs.changed }}
failed:
description: Number of clients that failed generation or compilation
value: ${{ steps.run.outputs.failed }}
report:
description: Path to the JSON report
value: ${{ steps.run.outputs.report }}
pull-request-url:
description: Created or updated pull request URL
value: ${{ steps.pr.outputs.pull-request-url }}

runs:
using: composite
steps:
- name: Install openapi-to-rust
id: install
shell: bash
env:
INPUT_BINARY: ${{ inputs.binary }}
INPUT_VERSION: ${{ inputs.version }}
ACTION_REF: ${{ github.action_ref }}
ACTION_REPOSITORY: ${{ github.action_repository }}
run: bash "$GITHUB_ACTION_PATH/scripts/action-install.sh"

- name: Generate and verify clients
id: run
shell: bash
env:
OPENAPI_TO_RUST_BIN: ${{ steps.install.outputs.binary }}
CLIENT_COMMAND: ${{ inputs.command }}
CLIENT_MANIFEST: ${{ inputs.manifest }}
CLIENT_NAME: ${{ inputs.client }}
run: bash "$GITHUB_ACTION_PATH/scripts/action-run.sh"

- name: Create or update sync pull request
id: pr
if: ${{ inputs.pull-request == 'true' && steps.run.outputs.changed != '0' }}
uses: peter-evans/create-pull-request@v8
with:
branch: ${{ inputs.pull-request-branch }}
title: ${{ inputs.pull-request-title }}
commit-message: ${{ inputs.pull-request-title }}
labels: ${{ inputs.pull-request-labels }}
draft: ${{ steps.run.outputs.exit_code != '0' }}
body: |
Automated OpenAPI client synchronization.

The workflow generated each changed client and ran its configured Cargo checks.
A draft pull request indicates that generation or compilation needs attention.

- name: Fail when a client did not verify
if: ${{ steps.run.outputs.exit_code != '0' }}
shell: bash
env:
EXIT_CODE: ${{ steps.run.outputs.exit_code }}
run: exit "$EXIT_CODE"
53 changes: 53 additions & 0 deletions scripts/action-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail

if [ -n "${INPUT_BINARY:-}" ]; then
if [ ! -x "$INPUT_BINARY" ]; then
echo "::error::binary is not executable: $INPUT_BINARY"
exit 1
fi
echo "binary=$INPUT_BINARY" >>"$GITHUB_OUTPUT"
exit 0
fi

version="${INPUT_VERSION:-}"
if [ -z "$version" ]; then
case "${ACTION_REF:-}" in
v[0-9]*) version="${ACTION_REF#v}" ;;
*)
echo "::error::version is required when the Action is not referenced by a version tag"
exit 1
;;
esac
fi
version="${version#v}"

runner_arch="$(uname -m)"
case "${RUNNER_OS:-}-${runner_arch}" in
Linux-x86_64) target="x86_64-unknown-linux-gnu" ;;
macOS-x86_64) target="x86_64-apple-darwin" ;;
macOS-arm64) target="aarch64-apple-darwin" ;;
Windows-x86_64) target="x86_64-pc-windows-msvc" ;;
*)
echo "::error::unsupported runner: ${RUNNER_OS:-unknown} ${runner_arch}"
exit 1
;;
esac

archive="openapi-to-rust-v${version}-${target}.tar.gz"
repository="${ACTION_REPOSITORY:-gpu-cli/openapi-to-rust}"
base_url="https://github.com/${repository}/releases/download/v${version}"
install_root="${RUNNER_TEMP}/openapi-to-rust-${version}"
mkdir -p "$install_root/bin"
curl --fail --location --silent --show-error "$base_url/$archive" --output "$install_root/$archive"
curl --fail --location --silent --show-error "$base_url/$archive.sha256" --output "$install_root/$archive.sha256"
node "$GITHUB_ACTION_PATH/scripts/verify-download.mjs" "$install_root/$archive" "$install_root/$archive.sha256"
tar -xzf "$install_root/$archive" -C "$install_root/bin"

binary="$install_root/bin/openapi-to-rust"
if [ "${RUNNER_OS:-}" = "Windows" ]; then
binary="$binary.exe"
fi
chmod +x "$binary"
echo "$install_root/bin" >>"$GITHUB_PATH"
echo "binary=$binary" >>"$GITHUB_OUTPUT"
Loading
Loading