Skip to content

Idiomatic Go SDK output for imperfect vendor specs - #653

Merged
daveshanley merged 1 commit into
mainfrom
claude/nifty-snyder-3d2f35
Oct 7, 2026
Merged

daveshanley merged 1 commit into
mainfrom
claude/nifty-snyder-3d2f35

Conversation

@daveshanley

@daveshanley daveshanley commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

A real-world vendor OpenAPI 3.1 spec failed to generate. It uses mostly inline schemas, nullable inside 3.1, and declares a text/plain 401. After working around the 401, the output had underscore type names, ID__2, 151 "example value is defined in the OpenAPI schema" comments, raw HTML in parameter docs, and any / map[string]any for undescribed JSON. This change makes such specs generate clean, idiomatic Go while staying contract-faithful.

gosdk

  • A declared non-JSON error response (such as text/plain) keeps its raw body in APIError.Body; it no longer fails generation. Non-JSON success bodies are still refused.
  • Types for a named operation derive from Resource+Method: PeopleMatchParams, PeopleMatchResponse, PeopleMatchBadRequest, PeopleMatchTooManyRequests. Unnamed operations still use the operationId.
  • Inline types in operation payloads take their property name when it is free (Person, Organization, EmploymentHistoryItem, ContactEmail). Inline types inside components stay qualified by their parent (AccountStatus).
  • Parameter docs are wrapped godoc above each field, with HTML and Markdown turned into plain text. Method comments name the request and list the typed error values.
  • Error cases are grouped by type, sorted, and ordered exact > range > default. Before, a 4XX declared ahead of 404 caught the 404.
  • Models only decoded from responses use *T instead of **T. The client declares only the unexported helpers its operations call.
  • New defaults, all overridable through Options.Models: idiomatic names, json.RawMessage for undescribed JSON, and enum constants.
  • sdk.Operation.Named records whether the caller chose the method name.

generator/golang

  • New: NameStyleIdiomatic, WithNameStyle, WithInlineRoots, WithReservedTypeNames, WithUntypedAsRawMessage, WithDecodeOnlyRoots, WithFallbackDescriptions, FieldNames, NewIdiomaticNameRegistry, NameRegistry.ResolveFirst/ClaimFirst, and DiagnosticNullableKeyword. A nullable: true in a 3.1 document is honoured and reported.
  • Comments: descriptions render whole and wrapped, the noise notes are removed, and deprecated schemas get a standard Deprecated: paragraph.
  • Naming: plural initialisms (LabelIDs), digit word breaks (Base64Encoded), MD5/SHA256. Fields _id and id become UnderscoreID and ID.
  • Bug fixes: properties written beside allOf were silently dropped. Unused nested map and tuple declarations were emitted. An inline schema reached twice (for example through a YAML anchor) got two type names but only one declaration.
  • The shared internal package generator/internal/gocomment turns description text into Go comments.

generator/typescript

  • Removed its workaround for the dropped allOf siblings, because the shared IR now carries them. Output is identical for valid schemas. A sibling property that cannot be built now renders as unknown with a diagnostic, like any other unbuildable property.

Reviewer notes

  • The standalone model generator keeps its Parent_Child and __N naming by default. Only gosdk opts into the idiomatic style.
  • Four existing golden files change in comments only. New goldens: name_collisions_idiomatic.golden.go, plus three SDK goldens generated from a people-match fixture. The fixture is extracted from the public vendor spec, with its source cited in its header; only the response examples blocks are removed.
  • A runtime test compiles the generated client and checks the 200, 400, a text 401, and 429 responses. The full 1.2 MB vendor spec gives byte-identical output to the fixture.
  • All five generator packages are at 100% coverage, and go test ./... passes.

Playing Field impact (regenerated in a scratch copy)

  • Its generator accepts the output, with no new diagnostic codes. The api.gen.ts output is byte-identical.
  • The Go SDK drops from 5,596 to 4,187 lines. 74 underscore type names, 360 noise comment lines and 17 map[string]any fields are gone, and 55 enums gain constants.
  • All methods, wire parameters, named JSON fields and error statuses are unchanged.
  • Hand-written code needs five renames: ListAccountsParams → AccountsListRawParams, ListAccountsStatus200responseUnion → AccountsListRawResponseUnion, CreateAccountParams → AccountsCreateParams, CreateAccountRequest → AccountsCreateRequest, GetAccountParams → AccountsGetParams. With those renames, PF's SDK tests pass under -race.

🤖 Generated with Claude Code

@daveshanley
daveshanley marked this pull request as ready for review October 7, 2026 16:03
@daveshanley
daveshanley force-pushed the claude/nifty-snyder-3d2f35 branch from 8b827e9 to 088b3c1 Compare October 7, 2026 16:05
A real-world vendor spec (OpenAPI 3.1, inline schemas, nullable in 3.1,
a text/plain 401) failed to generate, and the output after working
around the 401 was noisy: underscore type names, ID__2, "example value
is defined" comments, raw HTML in parameter docs, and any/map[string]any
for undescribed JSON.

gosdk
- Declared non-JSON error responses keep their raw body in APIError.Body
  instead of failing generation.
- Types for a named operation derive from Resource+Method
  (PeopleMatchParams, PeopleMatchResponse, PeopleMatchBadRequest).
- Inline payload types take their property name when free (Person,
  Organization, EmploymentHistoryItem); component children stay qualified.
- Parameter docs render as wrapped godoc above each field.
- Defaults: idiomatic names, json.RawMessage for undescribed JSON, enum
  constants (all overridable through Options.Models).
- Error cases are grouped, sorted, and ordered exact > range > default;
  response-only models use single pointers; the client declares only the
  helpers its operations call.

generator/golang
- NameStyleIdiomatic, WithInlineRoots, WithReservedTypeNames,
  WithUntypedAsRawMessage, WithDecodeOnlyRoots, WithFallbackDescriptions,
  FieldNames, NewIdiomaticNameRegistry, DiagnosticNullableKeyword.
- Comments: descriptions rendered whole and wrapped; noise notes removed.
- Fixes: properties beside allOf were dropped; unused nested map and tuple
  declarations removed; plural initialisms and digit word breaks.

Standalone model generator naming defaults are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daveshanley
daveshanley force-pushed the claude/nifty-snyder-3d2f35 branch from 088b3c1 to 593584c Compare October 7, 2026 16:07
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (55aa758) to head (593584c).

Additional details and impacted files
@@            Coverage Diff             @@
##              main      #653    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          308       309     +1     
  Lines        38781     39208   +427     
==========================================
+ Hits         38781     39208   +427     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@daveshanley
daveshanley merged commit 60b177e into main Oct 7, 2026
7 checks passed
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