From ba1f3d3d32690879fbed156d53570b8652e254ed Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:37:36 -0500 Subject: [PATCH] perf(codemode): batch OpenAPI query parameters (#36978) --- packages/codemode/src/openapi/runtime.ts | 46 ++++++++++++++---------- packages/codemode/test/openapi.test.ts | 42 ++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/packages/codemode/src/openapi/runtime.ts b/packages/codemode/src/openapi/runtime.ts index 2b2dcd1e51..d64d03e552 100644 --- a/packages/codemode/src/openapi/runtime.ts +++ b/packages/codemode/src/openapi/runtime.ts @@ -68,14 +68,16 @@ const buildRequest = ( } let request = HttpClientRequest.make(plan.operation.method as HttpMethod.HttpMethod)(url) + const query: Array = [] for (const field of plan.fields) { if (field.location !== "query") continue const item = own(input, field.inputName) if (item === undefined) continue - const serialized = serializeQuery(request, field, item) + const serialized = serializeQuery(field, item) if (serialized instanceof ToolError) return yield* Effect.fail(serialized) - request = serialized + for (const parameter of serialized) query.push(parameter) } + if (query.length > 0) request = HttpClientRequest.appendUrlParams(request, query) request = HttpClientRequest.setHeaders(request, plan.headers) for (const field of plan.fields) { @@ -246,40 +248,46 @@ const serializeSimple = ( } const serializeQuery = ( - request: HttpClientRequest.HttpClientRequest, field: Plan["fields"][number], value: unknown, -): HttpClientRequest.HttpClientRequest | ToolError => { +): ReadonlyArray | ToolError => { if (field.style === "deepObject") { if (!isRecord(value)) return toolError(`Deep-object parameter '${field.inputName}' must be an object.`) - return Object.entries(value).reduce((current, [name, item]) => { - if (current instanceof ToolError) return current + const parameters: Array = [] + for (const [name, item] of Object.entries(value)) { if (item === undefined || (item !== null && typeof item === "object")) { return toolError(`Deep-object parameter '${field.inputName}' contains an unsupported nested value.`) } - return HttpClientRequest.appendUrlParam(current, `${field.name}[${name}]`, String(item)) - }, request) + parameters.push([`${field.name}[${name}]`, String(item)]) + } + return parameters } if (Array.isArray(value)) { - const rendered = serializeSimple(field, value, String) - if (rendered instanceof ToolError) return rendered - if (!field.explode) return HttpClientRequest.appendUrlParam(request, field.name, rendered) - if (value.some((item) => item === undefined || (item !== null && typeof item === "object"))) { - return toolError(`Query parameter '${field.inputName}' contains an unsupported nested value.`) + if (!field.explode) { + const rendered = serializeSimple(field, value, String) + return rendered instanceof ToolError ? rendered : [[field.name, rendered]] } - return value.reduce((current, item) => HttpClientRequest.appendUrlParam(current, field.name, String(item)), request) + const parameters: Array = [] + for (const item of value) { + if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") { + return toolError(`Parameter '${field.inputName}' contains an unsupported nested value.`) + } + parameters.push([field.name, String(item)]) + } + return parameters } if (isRecord(value) && field.explode) { - return Object.entries(value).reduce((current, [name, item]) => { - if (current instanceof ToolError) return current + const parameters: Array = [] + for (const [name, item] of Object.entries(value)) { if (item === undefined || (item !== null && typeof item === "object")) { return toolError(`Query parameter '${field.inputName}' contains an unsupported nested value.`) } - return HttpClientRequest.appendUrlParam(current, name, String(item)) - }, request) + parameters.push([name, String(item)]) + } + return parameters } const rendered = serializeSimple(field, value, String) - return rendered instanceof ToolError ? rendered : HttpClientRequest.appendUrlParam(request, field.name, rendered) + return rendered instanceof ToolError ? rendered : [[field.name, rendered]] } const readResponseBody = ( diff --git a/packages/codemode/test/openapi.test.ts b/packages/codemode/test/openapi.test.ts index 2d256393a2..3901ea8836 100644 --- a/packages/codemode/test/openapi.test.ts +++ b/packages/codemode/test/openapi.test.ts @@ -495,6 +495,48 @@ describe("OpenAPI.fromSpec", () => { ) }) + test("preserves ordered exploded and deep-object query parameters", async () => { + const client = recordingClient(() => json({ ok: true })) + const tool = toolAt( + OpenAPI.fromSpec({ + baseUrl, + spec: singleOperation({ + parameters: [ + { name: "tags", in: "query", style: "form", explode: true, schema: { type: "array" } }, + { name: "filter", in: "query", style: "form", explode: true, schema: { type: "object" } }, + { name: "location", in: "query", style: "deepObject", explode: true, schema: { type: "object" } }, + ], + }), + }).tools, + "test", + ) + if (!Tool.isDefinition(tool)) throw new Error("test was not generated") + + await Effect.runPromise( + tool + .run({ + tags: ["first value", "second&value"], + filter: { state: "open now", page: 2 }, + location: { directory: "/tmp/a b", workspace: "work&1" }, + }) + .pipe(Effect.provide(client.layer)), + ) + + expect(client.requests[0]?.url).toBe( + `${baseUrl}/test?tags=first+value&tags=second%26value&state=open+now&page=2&location%5Bdirectory%5D=%2Ftmp%2Fa+b&location%5Bworkspace%5D=work%261`, + ) + await expect( + Effect.runPromise(tool.run({ tags: [{}] }).pipe(Effect.provide(client.layer))), + ).rejects.toThrow("Parameter 'tags' contains an unsupported nested value.") + await expect( + Effect.runPromise(tool.run({ filter: { state: {} } }).pipe(Effect.provide(client.layer))), + ).rejects.toThrow("Query parameter 'filter' contains an unsupported nested value.") + await expect( + Effect.runPromise(tool.run({ location: { directory: [] } }).pipe(Effect.provide(client.layer))), + ).rejects.toThrow("Deep-object parameter 'location' contains an unsupported nested value.") + expect(client.requests).toHaveLength(1) + }) + test("skips unsupported parameter encodings and malformed security", () => { const result = OpenAPI.fromSpec({ baseUrl,