diff --git a/.changeset/openapi-vendor-json-content-type.md b/.changeset/openapi-vendor-json-content-type.md new file mode 100644 index 0000000000..24c4da544e --- /dev/null +++ b/.changeset/openapi-vendor-json-content-type.md @@ -0,0 +1,5 @@ +--- +"@executor-js/plugin-openapi": patch +--- + +Preserve vendor +json Content-Type on OpenAPI object request bodies. diff --git a/packages/plugins/openapi/src/sdk/invoke.ts b/packages/plugins/openapi/src/sdk/invoke.ts index 148b4a052a..2f9bd0f6f5 100644 --- a/packages/plugins/openapi/src/sdk/invoke.ts +++ b/packages/plugins/openapi/src/sdk/invoke.ts @@ -838,12 +838,8 @@ const applyRequestBody = ( }); if (isJsonContentType(contentType)) { - // Pre-serialized JSON strings pass through with the declared media - // type preserved (important for `application/vnd.foo+json` etc.). - if (typeof bodyValue === "string") { - return sent(HttpClientRequest.bodyText(request, bodyValue, contentType)); - } - return sent(HttpClientRequest.bodyJsonUnsafe(request, bodyValue)); + const text = typeof bodyValue === "string" ? bodyValue : JSON.stringify(bodyValue); + return sent(HttpClientRequest.bodyText(request, text, contentType)); } if (isFormUrlEncoded(contentType)) { diff --git a/packages/plugins/openapi/src/sdk/non-json-body.test.ts b/packages/plugins/openapi/src/sdk/non-json-body.test.ts index 87deeaba98..d8619e13fe 100644 --- a/packages/plugins/openapi/src/sdk/non-json-body.test.ts +++ b/packages/plugins/openapi/src/sdk/non-json-body.test.ts @@ -447,6 +447,36 @@ describe("OpenAPI non-JSON request body dispatch", () => { }), ); + it.effect("application/vnd.api+json: object body keeps the declared content type", () => + Effect.gen(function* () { + const { server, captured } = yield* startEchoServer({ + name: "createNote", + path: "/notes", + payload: JsonNameObject, + transformSpec: replaceRequestBodyContent("/notes", "post", { + "application/vnd.api+json": { + schema: { + type: "object", + properties: { + name: { type: "string" }, + }, + }, + }, + }), + }); + + const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); + const conn = yield* addOpenApiTestConnection(executor, server, { slug: "notes-jsonapi" }); + + yield* executor.execute(conn.address("body.createNote"), { + body: { name: "Acme" }, + }); + + expect(captured.contentType).toBe("application/vnd.api+json"); + expect(decodeJsonNameBody(captured.body.toString("utf8"))).toEqual({ name: "Acme" }); + }), + ); + it.effect("multipart/form-data: only encoder-supported file shapes are advertised", () => Effect.gen(function* () { const { server } = yield* startEchoServer({