Skip to content

fix: path parameters with same name as query parameters are always required in generated TypeScript interfaces#150

Merged
Himenon merged 4 commits into
mainfrom
fix/issue-148-2
May 26, 2026
Merged

fix: path parameters with same name as query parameters are always required in generated TypeScript interfaces#150
Himenon merged 4 commits into
mainfrom
fix/issue-148-2

Conversation

@Himenon
Copy link
Copy Markdown
Owner

@Himenon Himenon commented May 26, 2026

Summary

  • Added a Kubernetes-style scenario to test/path-parameter/index.yml that reproduces the pattern found in connectCoreV1HeadNodeProxyWithPath (/api/v1/nodes/{name}/proxy/{path}) from the Kubernetes OpenAPI spec
  • Fixed a bug in generatePropertySignatures where a same-named query parameter would overwrite a path parameter when building the TypeScript interface, causing path?: string instead of the correct path: string
  • Updated snapshots across all code generators (class, functional, currying-functional) to reflect the corrected output

Root Cause

generatePropertySignatures in Parameter.ts used the parameter name alone as the deduplication key when building a typeElementMap. When a PathItem defined both a path parameter path (required) and a query parameter path (optional) with the same name — as seen in the Kubernetes spec — the last parameter processed won, and the optional query parameter overwrote the required path parameter.

Fix

Sort parameters before reducing so that path parameters are processed last. Since the reduce uses the parameter name as the key, path parameters now always overwrite same-named non-path parameters, preserving the required status in the generated TypeScript interface.

+ const sorted = [...parameters].sort((a, b): number => {
+   const aIsPath = !Guard.isReference(a) && a.in === "path";
+   const bIsPath = !Guard.isReference(b) && b.in === "path";
+   if (aIsPath && !bIsPath) return 1;
+   if (!aIsPath && bIsPath) return -1;
+   return 0;
+ });
  const typeElementMap = sorted.reduce<Record<string, string>>((all, parameter) => {

Test plan

  • pnpm test:code:gen:class — regenerate code passes
  • pnpm test:code:gen:function — regenerate code passes
  • pnpm test:code:gen:currying-function — regenerate code passes
  • pnpm test:snapshot — all 53 snapshot tests pass

🤖 Generated with Claude Code

Himenon and others added 4 commits May 27, 2026 08:16
…-named query parameter

Add a Kubernetes-style scenario to the path-parameter fixture where:
- Path parameters are defined at the PathItem level (not per-operation)
- A query parameter shares the same name as one of the path parameters (`path`)
- Multiple HTTP methods (GET, HEAD) inherit parameters from the PathItem

This reproduces the pattern found in `connectCoreV1HeadNodeProxyWithPath`
(/api/v1/nodes/{name}/proxy/{path}) from the Kubernetes OpenAPI spec, and
captures the current code generation behavior as snapshot baselines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…enerated TypeScript interfaces

When a PathItem defines both a path parameter and a query parameter with the
same name (e.g. Kubernetes /api/v1/nodes/{name}/proxy/{path}), the previous
implementation used the parameter name alone as the deduplication key in
generatePropertySignatures. This caused the optional query parameter to
overwrite the required path parameter, producing `path?: string` instead of
the correct `path: string`.

Fix: sort parameters before reducing so that path parameters are processed last.
Since they win in the final reduce assignment, their required status is always
preserved in the generated TypeScript interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…chema

The normalization that forced required: true on path parameters in
generateValidRootSchema was not needed for correct TypeScript type generation.
Parameter.ts already handles this via isPathProperty in generatePropertySignatureObject,
which always produces a required property for path parameters regardless of the
required field in the spec.

Remove normalizePathParameters and its unit tests, keeping generateValidRootSchema
minimal. Update parameter.json snapshots to reflect the absence of the injected
required: true field (TypeScript interface output is unchanged).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test files

- PathParameter-test.ts: extract p() and tpl() helpers to build expected
  template literal strings without embedding raw \${...} in regular string literals
- factory.test.ts: add biome-ignore for the line that intentionally tests
  escaping of template literal placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Himenon Himenon changed the title test: add snapshot tests for PathItem-level path parameters with same-named query parameter fix: path parameters with same name as query parameters are always required in generated TypeScript interfaces May 26, 2026
@Himenon Himenon merged commit fb586ad into main May 26, 2026
2 checks passed
@Himenon Himenon deleted the fix/issue-148-2 branch May 26, 2026 23:55
Himenon added a commit that referenced this pull request May 27, 2026
… same-named query parameters (#151)

## Summary

- Migrated snapshot tests from `toMatchSnapshot` to
`toMatchFileSnapshot` for better diff readability
- Excluded snapshot files from TypeScript type checking to prevent false
errors
- Added Kubernetes v1.28.6 OpenAPI spec as a snapshot test target
- Fixed a bug in `generatePropertySignatures` where same-named
parameters were reordered

## Problem

The previous fix for same-named path/query parameter conflicts (#150)
used `.sort()` to push path parameters to the end of the array before
reducing into a `Record`. This caused all path parameters to appear last
in the generated TypeScript interface, regardless of their position in
the OpenAPI spec.

For example, when the spec defines:
```yaml
parameters:
  - $ref: '#/components/parameters/zone_id'  # path parameter (first)
  - name: bins
    in: query                                 # query parameter (second)
```

The previous output was:
```typescript
export interface Parameter$... {
    bins?: string;    // ← wrong: appeared before zone_id
    zone_id: string;  // ← moved to last by sort
}
```

Additionally, the Kubernetes v1.28.6 spec exposes a second pattern: both
the path parameter and the query parameter with the same name (`path`)
are defined as `$ref` references. The previous sort only resolved `in`
for inline parameters, so both references were treated as non-path and
the query parameter reference (appearing last) won.

## Fix

Replaced the sort + `Record` reduce approach with a `Map`-based
iteration that preserves insertion order:

- Parameters are processed in their original spec order
- When two parameters share the same name, the path parameter's entry
wins by overwriting the existing value at its **original position** in
the Map (JavaScript `Map` preserves insertion order on `set()` of an
existing key)
- `resolveParameterIn` now resolves `$ref` references via
`store.getParameter()` so both inline and referenced path parameters are
correctly identified

```typescript
// Before: sort-based (broke insertion order)
const sorted = [...parameters].sort((a, b) => { /* path last */ });
const typeElementMap = sorted.reduce<Record<string, string>>(...);
return Object.values(typeElementMap);

// After: Map-based (preserves spec order)
const typeElementMap = new Map<string, string>();
for (const parameter of parameters) {
  const { name, typeElement } = generatePropertySignatureObject(...);
  const isPath = resolveParameterIn(store, parameter) === "path";
  if (!typeElementMap.has(name) || isPath) {
    typeElementMap.set(name, typeElement);
  }
}
return [...typeElementMap.values()];
```

## Test plan

- [x] `pnpm test:code:gen:class` — regenerate code passes
- [x] `pnpm test:code:gen:function` — regenerate code passes
- [x] `pnpm test:code:gen:currying-function` — regenerate code passes
- [x] `pnpm test:snapshot` — all 54 snapshot tests pass
- [x] New `$ref`-based path/query conflict scenario added to
`test/path-parameter/index.yml`
- [x] Kubernetes v1.28.6 snapshot added

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant