refactor: introduce self-describing OperationSpec via codegen#1520
Merged
bernerd-stripe merged 12 commits intomasterfrom Mar 31, 2026
Merged
refactor: introduce self-describing OperationSpec via codegen#1520bernerd-stripe merged 12 commits intomasterfrom
bernerd-stripe merged 12 commits intomasterfrom
Conversation
Replaces the anonymous map-literal approach in resources_cmds.go with named, typed OperationSpec structs — one per API operation — that are accessible at runtime for introspection, rich help, or future features like --dry-run validation and agent-oriented JSON output. Key changes: - pkg/cmd/resource/spec_data.go (new): OperationSpec, ParamSpec, EnumSpec types. Single source of truth for operation metadata. - pkg/cmd/resource/operation.go: NewOperationCmd now takes *OperationSpec instead of seven positional args (name, path, verb, propFlags, enumFlags, isPreview, serverURL). Flag registration now iterates spec.Params with full type, description, and enum data. - pkg/cmd/resources/ (new package, 100% generated): AddAllResourcesCmds wires the command tree; ~25 specs_*_gen.go files hold the OperationSpec vars organised by API namespace × sub-namespace. - pkg/cmd/root.go: calls resources.AddAllResourcesCmds(rootCmd, &Config) replacing the old unexported addAllResourcesCmds(rootCmd). - pkg/gen/gen_resources_cmds.go + resources_cmds.go.tpl: generator now emits the resources/ package instead of a single resources_cmds.go. OperationData carries Summary, IsPreview, VarName, and Params (map[string]*resource.ParamSpec) with Description, Required, Format, and merged enum/x-stripeEnum values. - pkg/spec/spec.go: adds Summary to Operation and Description to Schema (the latter was already accepted by the parser but silently dropped). - Deletes pkg/cmd/resources_cmds.go (8 012 lines; replaced by the new generated package). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
…degen Two improvements to the generated resources/ package: 1. Change the file header from "This file is generated; DO NOT EDIT." to "Code generated by gen_resources_cmds.go; DO NOT EDIT." — the exact pattern GitHub's linguist looks for to auto-collapse generated files in PR diffs (the Go spec also documents this as the canonical form). 2. Add cleanGeneratedFiles() to the generator: before writing any new output it glob-deletes all *_gen.go files in resources/. This ensures that renamed or removed namespaces never leave orphaned spec files behind across generator runs. Hand-written files (resources_test.go) are unaffected since they don't match the *_gen.go pattern. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
The generateSpecFileContent function was 60 lines of fmt.Sprintf / strings.Builder calls hand-rolling what text/template already does. This replaces it with pkg/gen/resource_specs.go.tpl, consistent with how the wiring file (resources_gen.go) is produced. Side effects of the switch: - Params are now emitted in alphabetical key order: text/template ranges over maps in sorted key order, whereas the previous builder iterated the map in random Go map order. Generated files are now deterministic across runs. - The three template invocations (wiring, spec, stripe-version) now share a single templateFuncs FuncMap (ToCamel, quote, upper) instead of each constructing its own. - varEntry fields are exported (VarName, Op) so the template can access them directly. - generateSpecFileContent is deleted. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
The previous implementation built flag descriptions only for enum params (listing possible values), leaving all other flags with empty usage text. Replace that dead block with a single assignment so every flag gets the description already stored on ParamSpec. Add TestNewOperationCmd_FlagUsageFromDescription to verify the behaviour. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
Map iteration in Go is non-deterministic. generateSpecFiles builds the
[]varEntry slice for each (apiNs, nsName) group by ranging over nested
maps, so the order of `var X = resource.OperationSpec{...}` declarations
varied between runs, causing the CI diff check to fail.
Sort each entries slice by VarName before executing the template so the
generated files are identical regardless of map iteration order. Also
regenerate all specs_*_gen.go files with the now-stable ordering.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Committed-By-Agent: claude
The --dry-run tests added in master used the old positional-arg signature for NewOperationCmd. Update them to use *OperationSpec. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
Replace the 22 per-namespace specs_*_gen.go files with a single specs_gen.go containing all OperationSpec variables sorted by name. The per-namespace split was an artifact of how the coordinator was structured and doesn't add navigational value for pure data declarations. Also improve resources_test.go: replace the tautological struct construction test with integration tests that verify generated specs wire cobra flags correctly, covering POST, GET, and v2 operations, plus a smoke test for the AddAllResourcesCmds coordinator. Regenerate from the current OpenAPI spec (2026-03-25.dahlia). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
Remove Description, EnumSpec.Description, and Summary from the generated spec structs and codegen. None of these fields were read after being set. Removing them shrinks specs_gen.go by ~14k lines and keeps the structs honest — only fields that are actually consumed at runtime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
Remove Description, EnumSpec.Description, and Summary from the generated spec structs and codegen. None of these fields were read after being set. Removing them shrinks specs_gen.go by ~14k lines and keeps the structs honest — only fields that are actually consumed at runtime. Also removes Operation.Summary from pkg/spec/spec.go — it was parsed from the OpenAPI spec but never consumed anywhere. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
- Remove dead `descByValue` map in mergeEnumValues (built but never read) - Remove `DenormalizeObject` and `denormalizedObject` helpers, which are no longer called after addDenormalizedParams replaced them in codegen - Add `balance_settings` case to GetResourceCmdName to prevent the default +s rule from producing the user-visible command "balance_settingss" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
vcheung-stripe
approved these changes
Mar 31, 2026
Collaborator
vcheung-stripe
left a comment
There was a problem hiding this comment.
Nice! Looks much cleaner and easier to extend.
++ for this:
The named OperationSpec vars are accessible at runtime, allowing subsequent PRs to power richer help text, validations, and other features using spec data directly.
I tested a few commands as a sanity check:
go run cmd/stripe/main.go customers
go run cmd/stripe/main.go customers list
go run cmd/stripe/main.go customers create
go run cmd/stripe/main.go customers create --help
go run cmd/stripe/main.go customers delete cus_UFd6MUb06oRtsX
go run cmd/stripe/main.go events resend evt_123
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviewers
r?
cc @stripe/developer-products
Summary
NewOperationCmdpreviously took seven positional arguments (name, path, verb, propFlags, enumFlags, isPreview, serverURL). Each new piece of per-operation metadata required adding another raw argument and updating every call site. Operation data was also inaccessible after the command was built — the CLI had no way to inspect what an operation does, what parameters it accepts, or what types those parameters have.This introduces
OperationSpecas a single typed struct that carries all operation metadata, passed as one argument toNewOperationCmd. Adding new fields no longer touches the function signature. All generated code is now isolated inpkg/cmd/resources/, cleanly separated from the hand-written command code inpkg/cmd/. The namedOperationSpecvars are accessible at runtime, allowing subsequent PRs to power richer help text, validations, and other features using spec data directly.Codegen now emits a
pkg/cmd/resources/package with two generated files:resources_gen.go—AddAllResourcesCmds, the wiring function (unchanged behavior, exported)specs_gen.go— one namedOperationSpecvariable per API operationThe types are in a new
pkg/cmd/resource/spec_data.go:OperationSpec,ParamSpec,EnumSpec.Other changes:
"Code generated by ...; DO NOT EDIT."header so GitHub Linguist auto-collapses them in diffs*_gen.gofiles before writing, so renamed or removed operations don't leave orphanspkg/cmd/resources_cmds.go(8 012 lines) is deletedpkg/spec/spec.goparsesdescriptionfromSchema(was silently dropped before)balance_settingsadded as an explicit case inGetResourceCmdNameto prevent the default+srule from producing the user-visible commandbalance_settingssdescByValuemap frommergeEnumValues(was built but never read)DenormalizeObjectand itsdenormalizedObjecthelper, which were no longer called afteraddDenormalizedParamsreplaced them in codegenNote on flag usage strings: All generated flags are currently registered with empty usage strings.
WrappedRequestParamsFlagUsagesdoes not render them, so this is inert — not a regression from the prior generated code. A follow-up PR addsShortDescriptiontoParamSpecand wires it into help output.