Skip to content

refactor(templates): unify router codegen into shared skeletons#2486

Open
mromaszewicz wants to merge 5 commits into
oapi-codegen:mainfrom
mromaszewicz:chore/simplify-templates
Open

refactor(templates): unify router codegen into shared skeletons#2486
mromaszewicz wants to merge 5 commits into
oapi-codegen:mainfrom
mromaszewicz:chore/simplify-templates

Conversation

@mromaszewicz

@mromaszewicz mromaszewicz commented Jul 19, 2026

Copy link
Copy Markdown
Member

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:

  • 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.

…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>
@mromaszewicz
mromaszewicz requested a review from a team as a code owner July 19, 2026 20:16
@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors router and initiator code generation around shared templates. The main changes are:

  • Shared server skeleton templates with framework-specific hook overrides.
  • Unified webhook and callback initiator rendering.
  • Precomputed client method variants for client templates.
  • Shared strict-server response header handling and stricter backend selection.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

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

Comment thread pkg/codegen/templates/initiator.tmpl
Comment thread pkg/codegen/templates/initiator.tmpl
Comment thread pkg/codegen/operations.go
Comment thread pkg/codegen/operations.go

@jamietanna jamietanna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@mromaszewicz

Copy link
Copy Markdown
Member Author

I'm fixing the code review issues, and still investigating whether to move a lot of the hooks.tmpl content into Go, but I thnik it's better in the templates.

@mromaszewicz mromaszewicz changed the title refactor(templates): unify router codegen into shared skeletons with … refactor(templates): unify router codegen into shared skeletons Jul 19, 2026
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>
Comment thread pkg/codegen/operations.go
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>
Comment thread pkg/codegen/operations.go Outdated
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants