Skip to content

fix: align HTTP channel parameter handling with the other protocols - #456

Open
jonaslagoni wants to merge 1 commit into
mainfrom
fix/http-parameter-consistency
Open

fix: align HTTP channel parameter handling with the other protocols#456
jonaslagoni wants to merge 1 commit into
mainfrom
fix/http-parameter-consistency

Conversation

@jonaslagoni

Copy link
Copy Markdown
Contributor

What

The HTTP protocols diverged from every other channel protocol in how they render and document parameters. This brings them back in line. All source changes are inside src/codegen/generators/typescript/channels/protocols/http/.

The parameters typing was already in sync — HTTP widens to Interface | Class and normalizes through the same shared parameterUnionType / renderParameterNormalization helpers as the brokers. It was the surrounding function shape and the generated helpers that had drifted.

1. Generated client helpers use object parameters

Ten helpers in the client's common types used positional signatures, which the rest of the generated code — including the HTTP server's own common types (sendResponse({...}), handleHandlerError({...})) — does not.

// before
buildUrlWithParameters(config.baseUrl, '/pet/{id}', parameters)
applyAuth(config.auth, headers, url)
handleHttpError(response.status, response.statusText, errorBody)

// after
buildUrlWithParameters({server: config.baseUrl, pathTemplate: '/pet/{id}', parameters})
applyAuth({auth: config.auth, headers, url})
handleHttpError({status: response.status, statusText: response.statusText, body: errorBody})

Also applyQueryParams, applyTypedHeaders, calculateBackoffDelay, shouldRetry, executeWithRetry, handleOAuth2TokenFlow, handleTokenRefresh. Single-arg helpers (sleep, extractHeaders, readOptionalJsonBody, validateOAuth2Config) stay positional per the rule's exception.

Not a breaking change — these are all module-private in the generated file; only the operation functions are exported.

2. The parameter model is referenced through type everywhere

The context interface rendered its type from model.type while the instanceof guard, the fromUrl call and the reported parameterType used model.name. Real Modelina object models have type === name so nothing was broken, but parameterType is documented as a type, the NATS client generator consumes it as one, and every other protocol reads .type.

3. HTTP functions document their parameters

Both renderers called renderChannelJSDoc without a parameters array, so a generated HTTP function documented nothing while a NATS function documents every argument. The single context object is documented as @param context.<field>:

/**
 * Find pets by status and category with additional filtering options
 *
 * @param context per-call request configuration
 * @param context.parameters for path and query parameter substitution
 * @param context.requestHeaders optional headers to send with the request
 */

4. parameters is optional when every parameter is optional

An OpenAPI operation declaring only optional query parameters forced callers to write {parameters: {}}. The field and the context are now optional in that case, so search() works. The optional path defaults through a named local (const parameterInput = context.parameters ?? {}) because the model constructor reads fields off its input and throws on undefined — and a local is also what lets TypeScript narrow the instanceof guard.

AsyncAPI is unaffected: its channel parameters are always required. A new contextOptional field on the render carries the optionality to the client-class wrapper so it declares the same signature as the function it delegates to.

Verification

  • npm run build, npm run lint:fix (incl. typecheck:test) — clean
  • Unit: 890 passed, including 3 new tests (optional parameters, required parameters, wrapper optionality)
  • Blackbox openapi-http + openapi-http-server — generated output type-checks
  • Runtime: 114 HTTP client, 15 HTTP server, 14 organization tests pass
  • Runtime output and examples regenerated via generate:assets

prepare:pr steps were run individually rather than via the script, since its npm ci step trips on pre-existing runtime lockfile drift. Broker-backed runtime suites (NATS/Kafka/MQTT/AMQP) were not run — they need Docker and nothing here touches those protocols.

Note for reviewers

While fixing (2) I found the channels.spec.ts fixtures build parameter models as new ConstrainedObjectModel('FindPetsByStatusAndCategoryParameters', undefined, {}, 'Parameter', {...}) — a type that differs from the name, which real Modelina output never produces. That artifact already shows up across the NATS/Kafka/AMQP snapshots (import {TestParameter} alongside parameters: ParameterInterface | Parameter). I updated the one affected assertion and left a comment rather than churning every snapshot; making the fixtures realistic would be a good separate cleanup.

Also out of scope here, from the same review: the NATS client class narrows parameters to the bare class (client/protocols/nats.ts forwards func.parameterType straight into the wrapper) while the channel function it delegates to accepts the Interface | Class union. HTTP gets this right by reusing the context interface. Left alone at @jonaslagoni's request since it moves NATS snapshots.

🤖 Generated with Claude Code

The HTTP protocols diverged from every other channel protocol in how they
render and document parameters. Four fixes, all inside protocols/http/:

Generated client helpers now take object parameters. Ten module-private
helpers in the client's common types (applyAuth, buildUrlWithParameters,
executeWithRetry, handleHttpError, shouldRetry, ...) used positional
signatures, which the rest of the generated code -- including the HTTP
server's own common types -- does not. They are not exported from the
generated module, so this is invisible to users.

The parameter model is referenced through `type` everywhere. The context
interface rendered the type from `model.type` while the instanceof guard,
the `fromUrl` call and the reported `parameterType` used `model.name`.
Real Modelina object models have `type === name`, so nothing was broken,
but `parameterType` is a type and every other protocol reads `.type`.

HTTP functions document their parameters. Both renderers called
renderChannelJSDoc without a `parameters` array, so the generated
functions documented nothing while a NATS function documents each
argument. The single `context` object is documented as `@param
context.<field>`.

The `parameters` field is optional when every parameter is optional. An
OpenAPI operation declaring only optional query parameters forced callers
to write `{parameters: {}}`. The context and the field are now optional in
that case, defaulted through a named local so the model constructor never
receives `undefined` and the instanceof guard still narrows. AsyncAPI
channel parameters are always required, so that path is unchanged. A new
`contextOptional` field on the render carries the optionality to the
client class wrapper so it matches the function it delegates to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jonaslagoni
jonaslagoni requested a review from ALagoni97 as a code owner August 2, 2026 09:50
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
the-codegen-project Ready Ready Preview Aug 2, 2026 9:50am
the-codegen-project-mcp Ready Ready Preview Aug 2, 2026 9:50am

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