Skip to content

feat(generator): build full SDK generation foundation - #11

Draft
nmorgan-cb wants to merge 5 commits into
mainfrom
feat/full-sdk-generator
Draft

nmorgan-cb wants to merge 5 commits into
mainfrom
feat/full-sdk-generator

Conversation

@nmorgan-cb

@nmorgan-cb nmorgan-cb commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a deterministic, OpenAPI-driven generator for the full Prime Java client surface (models, requests, responses, services, PrimeServiceFactory), and converges Java method/type names on the canonical names shared with the .NET, TypeScript, Python, and Go Prime SDKs — generating a @Deprecated compatibility alias for every existing public Java name that changes.

This PR adds the generator, its config, and tests. It does not include regenerated SDK source; see Generation preview below.

Review guide

Each commit is a self-contained step that builds and passes its tests:

  1. feat(generator): parse the OpenAPI spec into SDK operation bindings: spec parsing and fetching, config and overrides, binding derivation and validation, type resolution, and the operation inventory lock.
  2. feat(generator): route error enums and own generated model files: model post-processing, error-enum routing, and the manifest that tracks generated model files.
  3. feat(generator): generate requests, responses, services, and factory: the emission phases, the Main CLI (--check/--live-diff), the Makefile, and CoinbasePrimeClient /v2 support.
  4. feat(generator): generate deprecated Java aliases for canonical names: the alias config, alias emission, and the naming parity fixture.
  5. ci: gate generator tests and generated SDK compilation: runs the generator tests in CI, and adds a generated-sdk job that regenerates the SDK from the committed spec, compiles it, and runs the SDK tests.

Generator

  • Committed spec (apiSpec/prime-public-spec.yaml) drives normal generation and CI; live fetch/diff is explicit and non-mutating.
  • Raw OpenAPI model/enum generation → post-processing → custom request/response/service/factory emission.
  • Preserves builders, pagination, status handling, /v1//v2 routing, PrimeXM* naming, and existing model transforms.
  • Routes *ErrorCode/*Subcode enums into com.coinbase.prime.model.errors.
  • make fetch-spec, make generate, make check-generated, make generate-live-diff — check/live-diff render only in isolated temp dirs, never mutate the checkout.
  • Stays aligned with the vended SDK: optional DTO booleans stay boxed (Boolean), and primitive fields such as CreateOrderRequest.isRaiseExact/isBuyExact are kept via overrides.
  • operation-inventory.json locks the service and method of all 111 current operations. New spec operations generate automatically, while renames or removals fail OperationInventoryTest.
  • Fails before writing anything: bindings are validated before models are written, and a failed or empty fetch-spec never overwrites the committed spec. Missing operationIds, ambiguous inline enums, and duplicate service names are rejected.
  • CoinbasePrimeClient.withBaseUrl now keeps an injected HttpClient (used by generated /v2 calls).

Canonical naming + generated deprecated aliases

Every alias is declared in operations-overrides.json, never hand-written:

{
  "operationId": "PrimeRESTAPI_CreateWalletTransfer",
  "sdkMethod": "CreateTransfer",
  "deprecatedAliases": [{ "sdkMethod": "CreateWalletTransfer", "message": "Use createTransfer instead." }]
}

For each entry, the generator emits a @Deprecated request subclass (with a covariant Builder subclass, so new LegacyRequest.Builder()...build() still compiles and returns the legacy type), a @Deprecated response subclass, and @Deprecated forwarding methods on the service interface and implementation.

Canonical Deprecated alias
createTransfer createWalletTransfer
createWithdrawal createWalletWithdrawal
getOrder getOrderByOrderId
claimStakingRewards claimRewards
createPortfolioStake portfolioStakingInitiate
createPortfolioUnstake portfolioStakingUnstake
listEntityPaymentMethods listPaymentMethods
getEntityPaymentMethod getPaymentMethodDetails
listActivities listPortfolioActivities
getAddressBook listAddressBook
getAllocationsByClientNettingId listAllocationsByNettingId

listEntityUsers, getEntityFcmBalance, and getPortfolioCounterpartyId stay canonical as-is.

config/naming-parity.json plus NamingParityTest lock this mapping in-repo, without cloning other SDK repositories in CI.

Tests

  • Generator: 45 tests (was 29). Adds DeprecatedAliasGenerationTest, NamingParityTest, OperationInventoryTest, and SpecFetcherTest.
  • Root SDK: 287 tests. Adds CoinbasePrimeClientTest.

Generation preview

Ran make generate in an isolated git worktree, then compiled and tested the generated SDK (287 tests passing), including existing example programs, which compile with deprecation warnings on legacy calls. make check-generated reports new canonical types as additions and legacy names as converted alias files. The preview caught a real bug: alias Builder.build() initially skipped the canonical (private) validate(); it's now protected and called via super.validate().

The generated SDK baseline itself (models, DTOs, services, factory, error enums, manifest) will be committed in a follow-up, after a public API diff review, then make check-generated becomes a required CI gate.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Dependency update
  • Refactor / cleanup
  • Other (describe below)

Checklist

  • Tests included / updated
  • Changelog updated
  • Version bump if needed

Parse the committed spec into a stable, operation-ID sorted inventory and
derive one Java binding per operation (service folder, service name,
method, request/pagination shape). Config-driven overrides keep the vended
SDK names and types; the validator rejects missing path parameters,
duplicate methods, and one service name bound to two folders.

- SpecParser fails on operations without an operationId.
- SpecFetcher replaces the committed spec only after a 2xx response that
  parses with operations.
- JavaTypeResolver maps schemas to vended model/enum types, keeps DTO
  booleans boxed, and rejects ambiguous inline enums.
- operation-inventory.json locks the vended service and method of all 111
  current operations; new spec operations are allowed.
Post-process raw OpenAPI models so *ErrorCode/*Subcode enums land in
com.coinbase.prime.model.errors and every other enum keeps its vended
package. Model output is tracked in a manifest, so stale files are removed
only when the previous generation owned them, and enum directory read
errors fail generation instead of emitting partial imports.
Emit the client surface from operation bindings: request DTOs with
builders, validation, and PrimeListRequest pagination; response DTOs;
service interfaces and implementations with /v1 and /v2 routing; and
PrimeServiceFactory. Main renders and validates the whole surface before
writing any file, and adds isolated --check and --live-diff modes that
never mutate the checkout (make check-generated, make generate-live-diff).

CoinbasePrimeClient exposes its base URL and keeps an injected HttpClient
in withBaseUrl, which generated /v2 calls use.
Converge Java method and type names on the canonical names shared with
the .NET, TypeScript, Python, and Go Prime SDKs. Each rename is declared
in operations-overrides.json with deprecatedAliases; the generator emits a
@deprecated request subclass with a covariant Builder, a @deprecated
response subclass, and @deprecated forwarding service methods, so existing
callers keep compiling.

naming-parity.json and NamingParityTest lock the cross-SDK mapping, and
the operation inventory records the 11 renames with their aliases.
Run the model-generator test suite in CI, and add a generated-sdk job
that regenerates from the committed spec in the disposable checkout,
compiles, and runs the SDK tests. make check-generated replaces this
job once a generated baseline is committed.
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
1 if authored by an app that is not allowlisted and lacks verified human attribution 0
Sum 1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants