refactor(templates): unify router codegen into shared skeletons#2486
Open
mromaszewicz wants to merge 5 commits into
Open
refactor(templates): unify router codegen into shared skeletons#2486mromaszewicz wants to merge 5 commits into
mromaszewicz wants to merge 5 commits into
Conversation
…per-framework hooks
This change unifies different router code generation where possible. Over the years, I've had to fix many divergences between nearly identical templates, and maybe it would be better to templetize routers based on categories and similarity, rather than one template per backend.
Structural changes:
* Two-layer template architecture. Generic skeletons (server-interface.tmpl,
server-middleware.tmpl, server-handler.tmpl) hold the shared structure, with
{{block}} defaults in the net/http (stdhttp) shape. Each framework contributes
a small <framework>/hooks.tmpl of {{define}} overrides — path-param access,
handler signature, registration call, etc. — parsed into a per-framework
Clone() of the base template tree (buildServerTemplates in codegen.go).
What differs per router is now explicit and named in one small file instead
of hidden inside parallel near-copies.
* Duplicate templates merged. chi/gorilla/stdhttp receivers were byte-identical
and are now one receiver-stdlib.tmpl; same for the fiber v2/v3 handler. The
echo v4/v5 and fiber v2/v3 pairs (interface, wrapper, receiver, register,
strict interface, strict glue) are parameterized by ctx type/accessor hooks
instead of duplicated per version. The webhook and callback initiators were
100% token-substitution copies and are now one initiator.tmpl driven by
InitiatorTemplateData{Prefix, ...}.
* Strict servers. A shared strict.responseHeaders partial replaces ~20 inline
copies of the response-header-writing branch across the strict templates.
GenerateStrictServer gained a guard against double-emitting shared types
when multiple frameworks are enabled.
* Client view model. New client_view.go precomputes per-operation method
variants (suffix, signature, call args, doc comment — deprecation folded in),
so client.tmpl, client-with-responses.tmpl and initiator.tmpl now range
linearly over variants instead of re-deriving the WithBody/suffix/signature
conditionals at every call site. client.tmpl conditionals drop from 75 to 39.
The header-param serialization block is a shared partial.
* Deliberately NOT unified, after attempting: gin/iris/echo middleware and
registration (genuinely different error-handling and middleware-composition
models) and the fiber/iris strict interfaces (different response write
models). Forcing those under one skeleton would relocate complexity, not
remove it; they keep their own templates.
Templates shrink from 62 files / 6,623 lines to 48 files / 4,457 lines (-33%).
Generated code is byte-identical except three deliberate deltas:
* gorilla: inert `err :=` normalization in the required-header branch
* echo v5 strict: non-required text bodies gain the `len(data) > 0` guard
echo v4 already had
* fiber v3 strict: gains the optional-body/EOF guards fiber v2 already had
Note: template files were renamed/merged/removed, which affects
output-options.user-templates overrides keyed to the old template names.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Greptile SummaryThis PR refactors router and initiator code generation around shared templates. The main changes are:
Confidence Score: 5/5This looks safe to merge.
|
| Filename | Overview |
|---|---|
| pkg/codegen/operations.go | Adds shared template data, initiator path-parameter rejection, router template selection updates, and strict-server backend selection. |
| pkg/codegen/codegen.go | Builds per-framework template clones and routes server generation through the selected clone. |
| pkg/codegen/client_view.go | Adds precomputed client method variants for client and initiator template rendering. |
| pkg/codegen/templates/initiator.tmpl | Unifies webhook and callback initiator output under one shared template. |
| pkg/codegen/templates/strict/strict-fiber-interface.tmpl | Uses framework context hooks for Fiber strict response visitor signatures. |
Reviews (4): Last reviewed commit: "fix(codegen): enforce single server type..." | Re-trigger Greptile
jamietanna
reviewed
Jul 19, 2026
jamietanna
left a comment
Member
There was a problem hiding this comment.
Great to see the only generated code change (that I can see) is one new line of whitespace!
Review finding (Greptile): if a webhook/callback operation carried path parameters, the initiator methods (driven by ClientMethodVariants) would forward path arguments that the typed request builders do not accept, generating uncompilable code. This is unreachable today -- Webhook- and CallbackOperationDefinitions only bind header/query/cookie parameters, silently dropping in:path -- but the template's correctness depended on that non-local invariant. Make the invariant explicit: NewInitiatorTemplateData now returns an error when any operation has path parameters, so a future change that starts populating them fails loudly at generation time instead of emitting broken code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
I'm fixing the code review issues, and still investigating whether to move a lot of the |
Review finding (Greptile): the strict-interface dedup guard keyed on template filename, assuming a filename match means an identical rendering. That's false for fiber v2 + v3, which share strict-fiber-interface.tmpl but render it against different template trees (fiber.ctxType hook: *fiber.Ctx vs fiber.Ctx). With both enabled, v2 emitted its interface and v3's glue then referenced Visit methods with the wrong context type -- uncompilable output. Dedup now compares the rendered interface text: identical renderings (chi/gorilla/stdhttp/echo/gin/echo5 sharing strict-interface.tmpl) are emitted once as before, and divergent renderings of the same template return a clear error, since such a combination can never compile. Single-framework output is byte-identical, including the inter-template separator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding (Greptile): the NewInitiatorTemplateData guard was unreachable -- Webhook/CallbackOperationDefinitions never populate PathParams, they silently dropped in:path parameters before the guard could see them. A spec declaring a path parameter on a webhook or callback operation generated an initiator that sends to targetURL verbatim, so callers could end up requesting a URL that still contains an unsubstituted placeholder. Reject in:path parameters in both extraction functions with an error naming the webhook/callback, operation, and parameter. The NewInitiatorTemplateData check stays as defense in depth for direct callers of the exported API. Behavior deltas: * Specs declaring in:path parameters on webhook/callback operations now fail generation with a clear error instead of silently dropping the parameter. No committed fixture or example is affected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding (Greptile): the strict interface dedup compared renderings
only within the same template filename, so combinations like echo+fiber
would emit both strict/strict-interface.tmpl and
strict/strict-fiber-interface.tmpl, redeclaring StrictServerInterface,
<Op>RequestObject and <Op>ResponseObject with incompatible visitor
signatures.
Rather than growing the cross-template comparison, mirror the invariant
Configuration.Validate() already enforces ("only one server type is
supported at a time"): GenerateStrictServer now errors when more than
one strict target is enabled and emits the single chosen target plainly.
This replaces the rendered-output dedup machinery entirely -- it was
defending combinations that validated configurations cannot express --
and resolves the fiber-v2/v3 and cross-template collision scenarios with
one rule. chi/gorilla/stdhttp remain a single target since they share
both templates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
This change unifies different router code generation where possible. Over the years, I've had to fix many divergences between nearly identical templates, and maybe it would be better to templetize routers based on categories and similarity, rather than one template per backend.
Structural changes:
Two-layer template architecture. Generic skeletons (server-interface.tmpl, server-middleware.tmpl, server-handler.tmpl) hold the shared structure, with {{block}} defaults in the net/http (stdhttp) shape. Each framework contributes a small /hooks.tmpl of {{define}} overrides — path-param access, handler signature, registration call, etc. — parsed into a per-framework Clone() of the base template tree (buildServerTemplates in codegen.go). What differs per router is now explicit and named in one small file instead of hidden inside parallel near-copies.
Duplicate templates merged. chi/gorilla/stdhttp receivers were byte-identical and are now one receiver-stdlib.tmpl; same for the fiber v2/v3 handler. The echo v4/v5 and fiber v2/v3 pairs (interface, wrapper, receiver, register, strict interface, strict glue) are parameterized by ctx type/accessor hooks instead of duplicated per version. The webhook and callback initiators were 100% token-substitution copies and are now one initiator.tmpl driven by InitiatorTemplateData{Prefix, ...}.
Strict servers. A shared strict.responseHeaders partial replaces ~20 inline copies of the response-header-writing branch across the strict templates. GenerateStrictServer gained a guard against double-emitting shared types when multiple frameworks are enabled.
Client view model. New client_view.go precomputes per-operation method variants (suffix, signature, call args, doc comment — deprecation folded in), so client.tmpl, client-with-responses.tmpl and initiator.tmpl now range linearly over variants instead of re-deriving the WithBody/suffix/signature conditionals at every call site. client.tmpl conditionals drop from 75 to 39. The header-param serialization block is a shared partial.
Deliberately NOT unified, after attempting: gin/iris/echo middleware and registration (genuinely different error-handling and middleware-composition models) and the fiber/iris strict interfaces (different response write models). Forcing those under one skeleton would relocate complexity, not remove it; they keep their own templates.
Templates shrink from 62 files / 6,623 lines to 48 files / 4,457 lines (-33%).
Generated code is byte-identical except three deliberate deltas:
err :=normalization in the required-header branchlen(data) > 0guard echo v4 already hadNote: template files were renamed/merged/removed, which affects output-options.user-templates overrides keyed to the old template names.