The go generator — its skill
This file is the DESIGN of your ejected go generator (generators/go/):
to change the generator, edit this skill first, then make the code match it — a diff
to generators/go/ that has no covering sentence here is incomplete.
What it emits
One self-contained <stem>.go (package client): structs with json tags, a Client
with one (T, error) method per operation taking a context.Context, and the embedded
runtime. Go ≥ 1.21, standard library only — zero dependencies.
Design decisions that must hold
Models are structs: required fields by value, optionals as pointers with
,omitempty; thejsontag always carries the exact wire name.Package clause:
package clientby default,goPackageto override — a generated file usually lands in a package the consumer already owns. The value is checked against Go's own rule (lowercase letters, digits,_, no leading digit, not a keyword) and an invalid one fails generation: silently rewriting a publisher's package name would be worse than saying no.Doc comments are gofmt's shape, not the description's: a blank line prints as
//(never//, which gofmt strips), and CONSECUTIVE blank lines collapse to one — gofmt rewrites//\n//to a single//, so emitting both means our output is not gofmt-clean. Descriptions with a double blank line are common in real specs.Every parameter is its own argument, so their names share one namespace with the arguments the method declares itself (
ctx,body,params, and the receiver). Build them withuniqueIdentifiers(..., { taken: … }): OpenAPI lets one operation use a name in two locations (idin the path AND in the query), and Go rejects a duplicate parameter. The wire name is untouched, so the request is unchanged.Naming: exported PascalCase via
identifierFor+ anNprefix for digit-leading names (3ds→N3ds— an_-prefixed field is unexported and invisible toencoding/json);+1/-1becomePlus1/Minus1.Enums are typed consts (
type Status string+StatusInProgress Status = …); discriminated unions aretype X = anyplus a generatedUnmarshalX([]byte)dispatcher; allOf is flattened.Errors:
(T, error)returns ARE the error mode —errorModedoes not change the output (the generator declareserrorModes: ['throw'], soresultfails fast). Non-2xx →*APIError; timeouts →*TimeoutError.Dates:
dateType: Datemapsformat: date-timetotime.Time(encoding/json handles RFC 3339 natively) anddateto the runtime'sDatewrapper, which marshals as2006-01-02. Query values format explicitly, never viaString().Response headers: an operation that DECLARES success-response headers gains a
<Op>WithHeaders(ctx, …) (T, <Op>Headers, error)variant;<Op>Headersis a generated struct with pointer fields (nil when absent or unparsable), coerced to int64/bool/string. Operations without declared headers get no variant, and the base method stays(T, error).Servers: when the description declares servers, one
<Name>URL(...)function per server is emitted (named from the server description); server VARIABLES become string parameters (Go has no defaults — the doc comment states the spec default), so templated base URLs need no manual string building. The client's baked default staysservers[0]with variable defaults substituted.Parity surface: auth, retries with
Retry-After+ jittered backoff, per-attemptcontext.WithTimeout, idempotency keys, middleware, pagination (<Op>Pages/<Op>Itemsasfunc(yield func(T, error) bool)—range-over-func needs Go ≥ 1.23; 1.21 calls them with a callback), SSE, multipart.The EMITTED FILE is gofmt-clean, not just the runtime.
gofmt -lon generated output must print nothing, so the download is idiomatic as-is. The emitter earns that deterministically, without shelling out togofmt:alignGoColumnspads columns the way gofmt's tabwriter does — struct field types and tags,const/vartypes and=, and map-literal values — within each contiguous run. A line starting with a Go KEYWORD is a statement, never a declaration, and must never be padded (case "x":is not a field).casesits at itsswitch's own indent, so the switch body is not emitted as an indented block.- At most one blank line between declarations, none at end of file, and a blank line
inside a doc comment is
//— never//with a trailing space. A change here is verified by thegofmt -lbar in the unit suite, at cafe AND large-description scale.
The runtime is hand-written in
runtime/runtime.goin this folder (gofmt-clean,go vet-clean) and embedded at prepare time. Under--runtime moduleit is written as a same-packageruntime.gobeside the client, whose import block then lists only the packages its own body uses.Authored ONLY with the neutral toolkit — the dogfooding guard fails otherwise.
It documents itself. With
client.docs(or--docs), thedocshook writes<stem>.go.md: the security schemes, then one section per operation with its parameters, body, response type, and behavior notes. The call snippets come from this generator's ownsamplehook, so the page can only show the syntax of the SDK beside it, and the layout comes fromrenderReferencePagein the authoring toolkit — reachable from an ejected copy through@redocly/client-generator. Pagination on the page is decided bypaginationRuleFor, the same helper this generator resolves pagination with.
The modify loop
- Edit this skill: state the new behavior or decision.
- Make
generators/go/match it. - Run
redocly generate-clientand inspect thegit diffof the generated output — generated files are never hand-edited.
Newer built-in versions merge in with redocly eject-generator go --update.