Skip to content

Name path-level parameter helper types per operation#2466

Merged
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2090
Jul 13, 2026
Merged

Name path-level parameter helper types per operation#2466
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2090

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

Closes: #2090

A path-level parameter (declared under a path item's parameters, shared by every method on the path) was described once with no operation prefix, so any helper types generated for its schema — e.g. the member types of an anyOf — were named with only the parameter name (Id0, Id1). Because the path-level parameter is folded into every method on the path, those bare types were declared once per method, producing "Id0 redeclared in this block" and failing to compile when a path had more than one method.

Describe path-level parameters inside the per-operation loop with the same <OperationId>Params prefix already used for method-level parameters, so their helper types are named per operation (GetResourceParamsId0, PostResourceParamsId0, ...) and no longer collide. The same fix is applied to the three sites that share this pattern: regular paths, webhooks, and callbacks. Parameters with no generated helper type (plain strings, integers, etc.) are unaffected.

Adds internal/test/parameters/shared_anyof, exercising a path-level anyOf parameter shared across multiple methods on a regular path, a webhook, and a callback; the generated file compiling as part of the test module is the regression guard.

@mromaszewicz
mromaszewicz requested a review from a team as a code owner July 13, 2026 21:31
@mromaszewicz mromaszewicz added the bug Something isn't working label Jul 13, 2026
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a compile-time redeclaration bug (issue #2090) where a path-level anyOf/oneOf parameter shared by multiple HTTP methods on the same path would generate duplicate helper type declarations (Id0, Id1, …), one per method.

  • Core fix (operations.go): Adds a Shared boolean to ParameterDefinition, marks path-item-level parameters with markShared, collects their helper types once via sharedParameterTypeDefs, and emits them on the first operation of the path item; GenerateTypeDefsForOperation skips shared params to avoid per-operation redeclaration. The same pattern is applied symmetrically to webhooks and callbacks.
  • Component parameter fix (codegen.go): GenerateTypesForParameters now passes the component type name as the schema path prefix so helper types are named WidgetId0/WidgetId1 (consistent with accessors), and explicitly appends AdditionalTypes to the component output so they are declared once rather than per referencing operation.
  • Pre-generation validator (validate_spec.go): checkSharedPathParamTypeCollisions detects when two path items (paths, webhooks, or callbacks) would both emit a helper type of the same name, reporting a clear error before codegen begins.

Confidence Score: 5/5

Safe to merge. The fix is well-scoped and the generated output for the new test fixture compiles and reflects the intended behavior.

The core fix correctly deduplicates shared parameter helper types across all three generation sites (paths, webhooks, callbacks), the component-parameter fix aligns naming with union accessors, and the pre-generation validator catches the remaining cross-path collision case. The two open points are non-blocking quality issues, not defects in the generated output.

No files require special attention beyond the noted improvements to validate_spec.go error messaging and the corresponding test fixture coverage for webhook/callback collision scenarios.

Important Files Changed

Filename Overview
pkg/codegen/operations.go Adds Shared flag to ParameterDefinition, markShared/sharedParameterTypeDefs helpers, and per-path-item emit-once logic for path-level shared parameter helper types. Logic is sound for regular paths, webhooks, and callbacks.
pkg/codegen/codegen.go Fixes component-level parameters: passes []string{goTypeName} to paramToGoType so helper types are named after the component type (e.g. WidgetId0), and explicitly appends AdditionalTypes to the component types slice so they are declared once with the component rather than per-operation.
pkg/codegen/validate_spec.go Adds pre-generation collision detection for path-level anyOf/oneOf parameters across path items. All three domains (paths, webhooks, callbacks) are covered in code, but the error message wording does not account for webhooks/callbacks.
internal/test/parameters/shared_anyof/shared_anyof.gen.go New regression fixture. Id0/Id1 declared once; Whid0/Whid1 once for webhooks; Cbid0/Cbid1 once for callbacks. WidgetId0/WidgetId1 appear with the component, not per-operation.
internal/test/parameters/shared_anyof/spec.yaml Test spec covers multi-method path-level anyOf param, webhook, callback, and component parameter. Distinct parameter names deliberately avoid cross-domain collision.
internal/test/spec_validation/spec_validation_test.go Adds rejection test for two regular paths with same-named anyOf path-level parameter. No tests for webhook-to-webhook or path-to-webhook collision scenarios.
internal/test/spec_validation/testdata/shared_path_param_type_collision.yaml New rejection fixture: two regular paths each with an inline anyOf id path parameter, correctly exercising the new collision validator.
internal/test/parameters/shared_anyof/shared_anyof_test.go Regression guard asserting Id0/Id1, Whid0, Cbid0, WidgetId0/WidgetId1 are each declared exactly once.

Reviews (2): Last reviewed commit: "Generate shared path-level parameter hel..." | Re-trigger Greptile

Comment thread pkg/codegen/operations.go Outdated
@mromaszewicz

Copy link
Copy Markdown
Member Author

This isn't the right fix.

The parameters should be defined once globally, not specialized once per operation.

There's a problem with that, though. if there are two parameters named Id on two different pathItems and each of those is an anyOf, we will still have conflicts between them, as I have no way to fix this without prefixing the parameters somehow. I think in this case, the user will have to rename the Go model for that parameter via x-go-type-name.

Closes: oapi-codegen#2090

A parameter declared at the path-item level is inherited by every method on
the path. Its helper types — the member types of an inline anyOf/oneOf,
named from the parameter alone (Id0, Id1, ...) — were generated once per
operation, so a path with more than one method declared them several times
and the output failed to compile ("Id0 redeclared in this block").

Fix the root cause: mark path-item-level parameters as shared and skip their
helper types when collecting an operation's type definitions, emitting them
once for the path item (attributed to its first operation) instead. Applied
to the regular-path, webhook, and callback operation processors. A shared
parameter used by several methods on one path now just works.

Also fix referencing a shared parameter from #/components/parameters, which
was independently broken for anyOf: the component's union member types were
named from an empty path (N0/N1) and never declared, while each referencing
operation redeclared its own inline members. Now GenerateTypesForParameters
roots the schema path at the parameter's type name and declares its helper
types once, and a $ref parameter drops the inline schema it would otherwise
redeclare. Referencing a component parameter is a clean way to share one.

Two different path items that each declare a shared parameter of the same
name still collide (both emit the same type once). The pre-generation
validation pass detects this and rejects it, naming the paths and the fixes:
define the parameter under #/components/parameters and $ref it, rename it
with x-go-type-name or a spec overlay, or move it into each operation.

Adds internal/test/parameters/shared_anyof covering a shared anyOf parameter
across multiple methods on a regular path, a webhook, a callback, and a
component parameter referenced from two paths; plus a spec_validation
testdata case for the cross-path rejection and a multi-method valid.yaml
case for the accepted shared case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mromaszewicz
mromaszewicz merged commit a07731d into oapi-codegen:main Jul 13, 2026
14 checks passed
@alekgit

alekgit commented Jul 16, 2026

Copy link
Copy Markdown

Hi,

This PR introduced an unintended breaking change in parameter type naming.

Before this PR, when a component parameter's schema is a $ref to a named schema (e.g. $ref: "#/components/schemas/Status"), oapi-codegen generated a dedicated helper type named after the operation — e.g. GetItemsParamsStatus.

After this PR, pd.Schema.AdditionalTypes = nil clears those inline types, and the referenced schema name is used directly — e.g. Status.

This silently renames generated types, breaking existing code without any indication in the changelog. Is this intentional? If so, it should be documented as a breaking change.

@mromaszewicz

Copy link
Copy Markdown
Member Author

No, it's not intentional. Thanks for pointing it out.

@mromaszewicz
mromaszewicz deleted the fix/issue-2090 branch July 16, 2026 21:18
mromaszewicz added a commit that referenced this pull request Jul 17, 2026
…isions (#2476)

* Revert "Generate shared path-level parameter helper types once (#2466)"

This reverts commit a07731d.

* Name shared path parameter helper types once, hashing cross-path collisions

Closes: #2090

A parameter declared at the path-item level is inherited by every method on
the path. Its hoisted helper types -- the members of an inline anyOf/oneOf and
similar -- were named from the parameter alone and declared once per
operation, so a path with more than one method redeclared them ("Id0
redeclared in this block"). The same bare names also collided across different
paths that reuse the same parameter, which is the common REST case of an {id}
shared by sibling paths.

Resolve path-item-level parameters in a single pre-pass
(resolveSharedParameters): describe each scope's shared parameters once, count
how many scopes hoist a helper type under each name, and emit each scope's
helper types once for its path item rather than once per operation. A name
produced by two or more scopes is disambiguated by prefixing that scope's
parameters with a short, stable FNV hash of the scope, extended to the full
hash only if two scopes' short hashes clash. A parameter that does not collide
keeps its historical undecorated name, so existing generated code is
unaffected -- the only outputs that change are specs that previously failed to
compile.

Applied uniformly to regular paths, webhooks, and callbacks, which share the
one global Go type namespace. Disambiguated types carry a doc comment
explaining the hash prefix and pointing back to the source path.

Adds internal/test/parameters/shared_collision covering a shared anyOf
parameter across multiple methods on a path (bare, emitted once), the same
parameter reused across sibling paths (hash-disambiguated), and a
non-colliding single-method parameter (unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

anyOf in parameters is not well supported

2 participants