fix: handle duplicate path parameters in OpenAPI specs - #2220
Merged
mromaszewicz merged 1 commit intoFeb 15, 2026
Conversation
Some real-world OpenAPI specs (e.g. Keycloak) reuse the same path
parameter more than once in a single URI, such as:
/clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}
Previously this caused a "has 4 positional parameters, but spec has 3
declared" error because SortParamsByPath compared raw URI placeholder
count against unique declared parameters.
Fix this by deduplicating the path parameters extracted from the URI
(preserving first-occurrence order) before matching them against the
spec declared parameters. This is the right level to fix the issue
rather than scattering dedup logic across template helpers.
Fixes oapi-codegen#1574
Supersedes oapi-codegen#2175
Co-Authored-By: Junior Rantila <junior.rantila@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jamietanna
approved these changes
Feb 15, 2026
mromaszewicz
added a commit
to sterligov/oapi-codegen
that referenced
this pull request
Aug 1, 2026
A path parameter may appear more than once in one path, as in the
Keycloak-style /admin/realms/{realm}/clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}
that motivated oapi-codegen#2220. SortParamsByPath deduplicates such parameters and
the client template declares one variable per deduplicated parameter,
but GenPathString numbered variables by occurrence, so the second
{client-uuid} referred to a pathParam that was never declared and the
generated client failed to compile.
Key the variable index by parameter name so repeats resolve back to the
first occurrence, and treat the prefixed and exploded forms {.param} and
{;param*} as the same parameter, matching OrderedParamsFromUri.
This also corrects the output rather than only restoring compilation.
The previous fmt.Sprintf form emitted one %s per occurrence but only one
argument per unique parameter, so the generated code failed go vet and
put a literal %!s(MISSING) in the request path at runtime.
mromaszewicz
added a commit
that referenced
this pull request
Aug 1, 2026
feat(client): build request paths by concatenation instead of fmt.Sprintf
Generated clients built operationPath with fmt.Sprintf and one %s per path
parameter. The format string is fully known at generation time, so the
formatting machinery is pure overhead at runtime; plain concatenation
produces the same string without the allocation.
BenchmarkFmtPath1param-10 21433833 49.99 ns/op 16 B/op 1 allocs/op
BenchmarkConcatPath1param-10 73644105 16.27 ns/op 0 B/op 0 allocs/op
BenchmarkFmtPath2param-10 18985250 62.78 ns/op 16 B/op 1 allocs/op
BenchmarkConcatPath2param-10 55796110 21.45 ns/op 0 B/op 0 allocs/op
BenchmarkFmtPath3param-10 15205623 79.05 ns/op 24 B/op 1 allocs/op
BenchmarkConcatPath3param-10 41759646 28.44 ns/op 0 B/op 0 allocs/op
ReplacePathParamsWithStr gives way to GenPathString, which emits the whole
concatenation expression rather than a format string. It builds that
expression from the regexp match offsets, quoting each literal segment with
strconv.Quote and keying parameter variables by name. That also fixes
several cases where the format-string form produced wrong output:
- A percent sign in a path was read as a format verb, so a legal
percent-encoded path such as /search/%20/{id} generated a corrupt
string literal. A literal %s was worse still: it was counted as a
parameter, yielding a reference to a variable that was never declared.
- Literal segments were interpolated into the generated source unquoted,
so a path containing a double quote or a backslash produced code that
either did not compile or escaped incorrectly.
- A path parameter repeated within a single path, as in the Keycloak-style
path that motivated #2220, emitted one %s per occurrence but only one
argument per unique parameter. The result failed go vet and put a
literal %!s(MISSING) into the request path at runtime. Repeats now
resolve to the variable bound to the first occurrence, and the prefixed
and exploded forms {.param} and {;param*} are recognised as the same
parameter.
The generated API surface is unchanged. Only function bodies differ, along
with the fmt import where it is no longer needed.
---------
Co-authored-by: Sterligov Denis <d.sterligov@tinkoff.ru>
Co-authored-by: mromaszewicz <marcinr@gmail.com>
Co-authored-by: Claude Opus 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.
Some real-world OpenAPI specs (e.g. Keycloak) reuse the same path parameter more than once in a single URI, such as: /clients/{client-uuid}/roles/{role-name}/composites/clients/{client-uuid}
Previously this caused a "has 4 positional parameters, but spec has 3 declared" error because SortParamsByPath compared raw URI placeholder count against unique declared parameters.
Fix this by deduplicating the path parameters extracted from the URI (preserving first-occurrence order) before matching them against the spec declared parameters. This is the right level to fix the issue rather than scattering dedup logic across template helpers.
Fixes #1574
Supersedes #2175