Skip to content

feat: auto-generate harmonised, ergonomic SDKs across 12 languages#35

Draft
mridang wants to merge 787 commits into
masterfrom
feat/better-generated-clients
Draft

feat: auto-generate harmonised, ergonomic SDKs across 12 languages#35
mridang wants to merge 787 commits into
masterfrom
feat/better-generated-clients

Conversation

@mridang

@mridang mridang commented Feb 2, 2026

Copy link
Copy Markdown
Owner

Description

Adds 7 new language generators (Kotlin, Go, Rust, Dart, Swift, Elixir, C#) alongside the existing 5 (Java, Python, Ruby, PHP, Node/TypeScript) to produce fully harmonised, ergonomic client SDKs from any OpenAPI 3.0 spec. Every generated SDK shares the same architecture: BaseApi, DefaultApiClient, ObjectSerializer, ValueSerializer, HeaderSelector, Configuration, structured exceptions, per-operation Options classes, authentication (API key, Basic, Bearer, OAuth2), OpenTelemetry propagation, HTTP compression, TLS/proxy transport options, binary upload/download, and per-operation server overrides.

Motivation and Context

The default OpenAPI Generator templates produce inconsistent, non-idiomatic code across languages. This PR replaces them with a unified set of Mustache templates that generate production-grade SDKs on par with hand-written clients from Stripe, Stainless, or Speakeasy — fully linted, statically analysed, formatted, and integration-tested in Docker.

How Has This Been Tested?

All 12 SDKs are generated on-the-fly during the Maven build. Each SDK runs a full test suite inside Docker covering unit tests, integration tests against a live Petstore mock, formatting checks, linting, and static analysis. Regression tests for HeaderSelector and ValueSerializer ensure cross-language parity.

Checklist

  • I have checked my code for any possible security vulnerabilities

@gitguardian

gitguardian Bot commented Mar 5, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@mridang mridang force-pushed the feat/better-generated-clients branch from 5166131 to 3cb26d3 Compare March 10, 2026 00:23
@mridang mridang force-pushed the feat/better-generated-clients branch from 1113be3 to bc8fd3d Compare April 13, 2026 06:21
@mridang mridang self-assigned this Apr 16, 2026
@mridang mridang marked this pull request as draft April 21, 2026 06:11
@mridang mridang force-pushed the feat/better-generated-clients branch 9 times, most recently from e134b80 to f61e4ed Compare April 27, 2026 08:19
@mridang mridang changed the title feat: added tests to enure that we iterate rapidly feat: auto-generate harmonised, ergonomic SDKs across 12 languages Apr 28, 2026
@mridang mridang force-pushed the feat/better-generated-clients branch from ace5c59 to 9039e83 Compare April 28, 2026 09:21
mridang added a commit that referenced this pull request May 4, 2026
…, #35)

- #2: Make Go ApiResult.Data nullable (*T pointer type)
- #5: Dart caCertPath defaults to null instead of empty string
- #9: Add toCookieValue to ObjectSerializer in all 12 languages
- #14: Dart DefaultApiClient uses caCertPath with SecurityContext
- #15: Fix Dart redirect handling (autoRedirect + maxRedirects)
- #18: Route Dart/Rust path params through ValueSerializer.serializeStyled()
- #26: Add default value support in Go, Rust, Swift, Dart, Elixir models
- #35: Update Go version to 1.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mridang mridang force-pushed the feat/better-generated-clients branch 2 times, most recently from 58765c7 to df347a4 Compare May 4, 2026 19:34
mridang added a commit that referenced this pull request May 4, 2026
…, #35)

- #2: Make Go ApiResult.Data nullable (*T pointer type)
- #5: Dart caCertPath defaults to null instead of empty string
- #9: Add toCookieValue to ObjectSerializer in all 12 languages
- #14: Dart DefaultApiClient uses caCertPath with SecurityContext
- #15: Fix Dart redirect handling (autoRedirect + maxRedirects)
- #18: Route Dart/Rust path params through ValueSerializer.serializeStyled()
- #26: Add default value support in Go, Rust, Swift, Dart, Elixir models
- #35: Update Go version to 1.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mridang mridang force-pushed the feat/better-generated-clients branch from df347a4 to 642ef91 Compare May 4, 2026 20:27
mridang added a commit that referenced this pull request May 4, 2026
…, #35)

- #2: Make Go ApiResult.Data nullable (*T pointer type)
- #5: Dart caCertPath defaults to null instead of empty string
- #9: Add toCookieValue to ObjectSerializer in all 12 languages
- #14: Dart DefaultApiClient uses caCertPath with SecurityContext
- #15: Fix Dart redirect handling (autoRedirect + maxRedirects)
- #18: Route Dart/Rust path params through ValueSerializer.serializeStyled()
- #26: Add default value support in Go, Rust, Swift, Dart, Elixir models
- #35: Update Go version to 1.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mridang mridang force-pushed the feat/better-generated-clients branch from 642ef91 to 6713b61 Compare May 5, 2026 06:35
mridang added a commit that referenced this pull request May 5, 2026
…, #35)

- #2: Make Go ApiResult.Data nullable (*T pointer type)
- #5: Dart caCertPath defaults to null instead of empty string
- #9: Add toCookieValue to ObjectSerializer in all 12 languages
- #14: Dart DefaultApiClient uses caCertPath with SecurityContext
- #15: Fix Dart redirect handling (autoRedirect + maxRedirects)
- #18: Route Dart/Rust path params through ValueSerializer.serializeStyled()
- #26: Add default value support in Go, Rust, Swift, Dart, Elixir models
- #35: Update Go version to 1.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mridang mridang force-pushed the feat/better-generated-clients branch from 6713b61 to f6d73f3 Compare May 5, 2026 07:36
mridang added a commit that referenced this pull request May 5, 2026
…, #35)

- #2: Make Go ApiResult.Data nullable (*T pointer type)
- #5: Dart caCertPath defaults to null instead of empty string
- #9: Add toCookieValue to ObjectSerializer in all 12 languages
- #14: Dart DefaultApiClient uses caCertPath with SecurityContext
- #15: Fix Dart redirect handling (autoRedirect + maxRedirects)
- #18: Route Dart/Rust path params through ValueSerializer.serializeStyled()
- #26: Add default value support in Go, Rust, Swift, Dart, Elixir models
- #35: Update Go version to 1.26

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mridang mridang force-pushed the feat/better-generated-clients branch 4 times, most recently from b4d5152 to 1c15042 Compare May 10, 2026 11:55
mridang added 30 commits June 15, 2026 21:38
The protobuf-duration migration left ISO-8601 duration assertions in the Swift,
Dart and Elixir test templates (the serializer emits "5400s" / "0s" / "-300s",
not "PT1H30M"). Rewrite every duration test in those languages to protobuf-JSON
values and assertions; the reject cases now feed ISO-8601 strings and expect a
parse error. Verified locally: Swift, Dart and Elixir ClientSpec all pass.
…ements

Structural invariants (one source file = one test file; identical docs/shape;
idempotency), a 10th test-parity dimension, per-finding why_not_surfaced +
why_not_caught fields, multi-round loop-until-dry, and a hard WONTFIX deny-list.
…/AK/AU-resid/N1)

Code fixes: csharp/node/swift/elixir AL decompression-error wrapping; node+dart N1
redirect body-replay guard scoped to 307/308; php AU-resid discriminator throw.
Identical canonical tests for all 5 findings added across all 12 SDKs per the
fix-time parity rule. Goldens not yet regenerated.
swift: AL decompression guard now gates on gzip magic bytes (Linux URLSession
auto-decompresses but leaves the Content-Encoding header, so the residual-header
heuristic wrongly threw on every response); platform-guarded zlib decode.
rust/elixir: N1 302-proceeds test asserted the guard predicate deterministically
instead of a chasm round-trip (chasm can't model https->http; returned 405).
…ats (D1/U1/U2/D2)

D1: standalone Bearer + ApiKey authenticator tests in the 11 non-java SDKs.
U1: ServerConfiguration + ServerVariable tests across all 12.
U2: ApiResult tests across all 12.
D2: README Caveats section added to java/kotlin/csharp/python/rust.
Each new test registered in its Better<Lang>Codegen; all 12 ClientSpecs green.
The Gap AL guard used full-line // comments in DefaultApiClient; the swift and
node FormattingSpec (generatedCodeShouldNotContainInlineComments) require block
/* */ comments in generated production code. Convert them; behaviour unchanged.
…R5-1)

Plus fleet-wide parity tests, identical across all 12:
- R5-1: config-default-header-precedence test (red in ruby pre-fix, green elsewhere).
- P1: rust gets a dedicated server_variable_test (split from server_configuration_test).
- P2: BOM-tolerance test in all 12 (kotlin previously stripped a BOM with no test).
Full per-language spec suite green for all 12.
…2-1); harmonize empty-Accept to "" (R13-1)

R12-1: python/api/api.mustache gates the options default on nullable (like ruby) so an
op whose Options has required fields requires the argument — matching the other 11.
R13-1: select_accept_header returns "" (not null) in python/kotlin/csharp/node/elixir,
with the consumer gated on non-empty in lockstep so an empty result still omits Accept;
canonical empty-Accept test aligned across all 12. Full per-language spec suite green.
The SDK's true floor is Elixir 1.17 (it uses the stdlib Duration struct, added
in 1.17); the runtime deps (req/jason/mime) all support it. Drops the floor from
1.19 to 1.17 — a 2-version reach gain. ElixirClientSpec green.
…ility

The 3.4 floor was arbitrary; the source uses no 3.2+ feature and the runtime deps
(faraday 2.0, dry-struct/dry-types 1.6/1.7, iso8601, tod) all support Ruby 3.1.
Drops the floor 3.4 -> 3.1 (reach gain 3.1/3.2/3.3/3.4). RubyClientSpec green.
Package.swift declared only .macOS(.v14), so the SDK couldn't be added to iOS/
tvOS/watchOS app targets via SwiftPM. Declare all four Apple platforms at the
async-URLSession API floor (macOS 12 / iOS 15 / tvOS 15 / watchOS 8). Linux/
Windows are unaffected (SwiftPM only gates Apple platforms). SwiftClientSpec green.
Extract the transport-agnostic orchestration (security-aware redirect loop,
response decompression, charset decoding, header injection) into a new
AbstractApiClient behind a single execute() seam. DefaultApiClient keeps the
Symfony transport unchanged; a new Psr18ApiClient lets callers drive the SDK
with any PSR-18 client (Guzzle, Symfony adapter, Laravel, a mock) while reusing
that single-sourced orchestration. The client facade now accepts an optional
custom ApiClient. Adds a Psr18ApiClientUnitTest that mirrors the DefaultApiClient
behaviours through the PSR-18 path.
Extract the transport-agnostic orchestration (security-aware redirect loop,
multipart encoding, response decompression, charset decoding, header injection)
into a new AbstractApiClient built on Web-standard fetch/crypto/DecompressionStream.
Two thin subclasses supply only the dispatcher: the undici-backed Node transport
(static node:*/undici imports, full proxy/CA/TLS support) and a portable Fetch
transport that imports nothing Node-specific. The package's #transport subpath
import resolves the right one per runtime, so the SDK now loads on Deno, Bun, and
edge runtimes (Cloudflare Workers, Vercel Edge), not just Node. undici becomes an
optionalDependency. Adds a web-api-client test covering the portable variant.
Extract the transport-agnostic orchestration (security-aware redirect loop,
multipart encoding, charset decoding, header injection) into a new
AbstractApiClient written against package:http only, with no dart:io. Two thin
subclasses supply the platform client and decompression: the native dart:io
IOClient transport (full proxy/CA/TLS support) and a portable BrowserClient
transport that imports no dart:io. A conditional export in default_api_client.dart
resolves the right one per target, so the SDK now compiles and runs on Flutter
Web (and dart2js/wasm), not just native. Importers are unchanged; the io/web
files re-export the shared multipart helpers. No new pub dependencies.
The generated client targeted net10.0, excluding net8.0 (LTS) where most
production C# and Unity run. Two net9+ APIs forced the floor: the
[JsonStringEnumMemberName] enum attribute and System.Threading.Lock.

Replace [JsonStringEnumMemberName]/JsonStringEnumConverter with a uniform
generated per-enum JsonConverter<TEnum> (top-level and inline enums) that maps
each member to its wire string explicitly -- net8-compatible, AOT-safe, and
behaviour-preserving (unknown values throw JsonException). Swap
System.Threading.Lock for a plain object lock. Drop TargetFramework to net8.0
and LangVersion to 12. The test project carries RollForward=Major so the net8
test host still runs on a newer-major-only runtime; the shipped library stays
pure net8.
The generated client targeted Java 25 via <release>25</release>, excluding the
widely-deployed LTS releases 17 and 21. The only API above 17 the code uses is
HttpClient.close() (added in Java 21), so the true floor is 21, not 25. Lower
<release> to 21 and update the README. The build still runs on JDK 25 and emits
Java 21 bytecode, which runs on any JDK 21+.
The crate targeted edition 2024, which forces rust-version 1.85 (the newest
toolchain). The source uses no edition-2024-specific features, so lower it to
edition 2021 and declare MSRV 1.75 (the floor the tokio 1 / reqwest 0.12
dependency tree needs), widening support to older Rust toolchains.
Generated Jackson-based clients fail at runtime in a GraalVM native image
because the model POJOs (and their inline enums) have no reflection metadata, so
Jackson cannot (de)serialize them. The generator already knows every model it
emits, so it now writes a reflect-config.json under META-INF/native-image
registering each model class and nested inline enum for reflection.

Verified end-to-end: a native-image build of the generated SDK round-trips a Pet
(including its nested status enum's custom wire value) where it previously
crashed with a missing-reflection error. The metadata is a build-time resource
only and does not affect the JVM build.
Add AbstractWasmSpec, a shared spec a language opts into by supplying its
wasm/WASI build command; it asserts the generated SDK compiles for a
WebAssembly target. Wire the two toolchains proven wasm-clean: GoWasmSpec
(GOOS=wasip1 go build) and DartWasmSpec (dart compile wasm of a probe that
forces the BrowserClient web transport). Languages whose toolchain or transport
is not wasm-capable simply do not subclass it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant