Skip to content

Run Prism behind a proxy for array query params - #127

Merged
ark-archastro merged 3 commits into
mainfrom
fix/prism-required-array-query-params
Nov 3, 2026
Merged

ark-archastro merged 3 commits into
mainfrom
fix/prism-required-array-query-params

Conversation

@ark-archastro

@ark-archastro ark-archastro commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review on ArchCode

Problem and intent

Generated SDK contract tests start a Prism mock from the OpenAPI spec and call it with the generated clients. The SDK runtimes send array query params as name[]=value, which is what Platform expects. Prism looks query params up by their exact name, so it never sees tags[]=x as tags.

SDK client  -> DELETE .../tags?tags[]=x
Prism       -> looks up query param "tags", finds nothing under that exact name
Prism       -> 422 missing required param

This causes two problems:

  • A required array param always fails. DELETE /api/v1/threads/{thread}/tags is the only one in the spec, and it fails 4 contract tests on archastro-js #85. That blocks the SDK release ArchDev web needs to drop an HTTP workaround for tag removal. The Python SDK would fail 8 tests on its next regeneration.
  • Prism has never validated optional array params. There are 57 array query params in the spec, and an invalid enum value such as group_by[]=bogus-value gets a 200.

Prism 5.14.2 and 5.16.0 behave the same, so upgrading doesn't help. Naming the param tags[] in the spec gives a 422 for every form.

The goal is contract tests that accept correctly encoded requests and still reject wrong ones, without changing the published contract.

What changed

Before: Prism listens on the test port and receives the SDK's name[]= params under names it doesn't recognise.

After: the generated setup runs prism-proxy.mjs:

  1. It starts Prism on a free port.
  2. Once Prism answers, it listens on the test port.
  3. It rewrites name[]= to name= in the query string before forwarding. Paths, values, headers, bodies and status codes pass through unchanged.
  4. It exits with Prism's exit code, and stops Prism when it is terminated.

The TypeScript and Python setups emit the same script from prism-proxy.ts. The Python writer now uses // for the content-hash line of .mjs files.

This PR has two commits. The first relaxed required arrays in a copy of the spec, and the second replaces that with the proxy.

Unchanged:

  • The published spec.
  • The channel harness.
  • The existing readiness checks and fast-fail on Prism exit, which now see the proxy.
  • Go, Swift, Rust and Elixir, whose Prism setup is hand-written in their own repos.

Scope: generator contract-test emitters only, with no SDK runtime or API change.

Risk: low. This affects generated test setup only. The proxy adds a process to each contract run.

User impact: none directly. It unblocks the SDK release.

Testing

Each request below went through the proxy to Prism with the unmodified spec:

Request Direct to Prism Through the proxy
tags[]=x 422 200
tags[]=x&tags[]=y 422 200
No tags 422 422
Misnamed tag[]=x 422 422
group_by[]=bogus-value 200 422
Bare tags=x 200 200

Canonical end-to-end proof: no test in this repo runs real Prism. The proof is a manual run of each SDK's generated contract suite against real Prism, using a local build of this generator and the 0ce34f0 spec. npm run test:contract in archastro-js is the JS run, and pytest tests/contract in archastro-python is the Python one. Generated clients send real HTTP requests through the proxy, and the tests assert on Prism's responses.

Suite Result
archastro-js at #85 head, contract tests regenerated 1769 of 1769 pass (1765 before)
archastro-js, with tags.remove changed to drop tags 4 tag-removal tests fail
archastro-python, regenerated 3538 of 3538 pass

Automatic coverage: this repo's ci.yml runs npm test. That includes __tests__/backends/prism-proxy.test.ts, which runs the emitted script against a stand-in for Prism and checks four things:

  • The query rewrite, with paths, values, headers and body left alone.
  • That it waits for Prism before listening.
  • That it passes on Prism's exit code.
  • That it stops Prism when terminated.

The emitter tests check that both setups launch the proxy and that the proxy is omitted when there are no REST operations. The contract suites run in each SDK repo's CI on its regeneration PR, after a generator release.

Integration matrix:

Entry point Required array name[]= Optional array name[]= Missing or misnamed required array Channel harness
TS generated suite covered covered covered (negative control) unchanged
Python generated suite covered covered probed directly unchanged

Fidelity: Prism and the generated clients run for real, and Prism returns mock responses instead of calling Platform.

Not proven here:

  • Platform's handling of the wire form. tags_test.exs in firstlanding covers the single-value tags[]= form.
  • Prism being left running if the proxy itself is SIGKILLed. Python's teardown only does that after a 10-second SIGTERM timeout.

Known limitations

  • A bare tags=x still passes. From reading the code, Go, Swift and Elixir send that form. The proxy could reject bare array names by reading the spec, but that would fail those SDKs until they're fixed, so it's a follow-up.
  • Go, Swift, Rust and Elixir don't use the proxy yet, because their Prism setup is hand-written.

Follow-ups

🤖 Generated with Claude Code

ark-archastro and others added 2 commits November 2, 2026 17:40
The SDKs send array query params as `name[]=value`, but Prism looks them
up under the exact name `name`. A required array param is therefore
reported as missing and the generated contract test gets a 422, which
fails archastro-js #85 on DELETE /api/v1/threads/{thread}/tags.

The generated TypeScript and Python contract setup now starts Prism from
a temporary spec copy with array query params optional. The published
spec and the channel harness are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace the relaxed spec copy with a proxy, so Prism validates array
query params against the unmodified spec.

The SDKs send array query params as `name[]=value`, but Prism looks
params up by their exact name. Relaxing required arrays in a spec copy
got the requests through, but it stopped the contract tests from
catching an SDK that drops a required array param, and Prism still never
validated optional array params.

Generated contract setup now runs `prism-proxy.mjs`. It starts Prism on
a free port, listens on the test port once Prism answers, and rewrites
`name[]=` to `name=` in the query before forwarding. TypeScript and
Python emit the same script. The Python writer now uses `//` for the
content-hash line of `.mjs` files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ark-archastro ark-archastro changed the title Let Prism accept required array query params Run Prism behind a proxy for array query params Nov 3, 2026
When a signal stopped Prism, the proxy exited with code 0, so setup
errors read "exited with code 0". The proxy now exits with 128 plus the
signal number, the usual shell convention: SIGKILL gives 137 and SIGTERM
gives 143.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ark-archastro
ark-archastro merged commit c72d647 into main Nov 3, 2026
2 checks passed
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