History

fix(openapi): accept a string body for octet-stream uploads

A plain string `body` was being rejected for octet-stream operations with "request body must be bytes; provide bodyBase64", which broke existing callers (such as the Microsoft Graph drive item content upload) that send text content as a string. The request layer already handles a string body, so let it through as UTF-8 bytes. Binary uploads still use bodyBase64.

Rhys Sullivan committed 4 months ago

Commit 612f03e1 parent 529b9083 files · +25 −10Browse files

Unified · Split

3 files
  1. .changeset/octet-stream-string-body.md +5 −0
  2. packages/plugins/openapi/src/sdk/invoke.ts +10 −1
  3. packages/plugins/openapi/src/sdk/non-json-body.test.ts +10 −9
.changeset/octet-stream-string-body.md+5−0
@@ -0,0 +1,5 @@
1+---
2+"@executor-js/plugin-openapi": patch
3+---
4+
5+Allow a plain string `body` for octet-stream uploads again. Operations like Microsoft Graph's drive item content upload were rejecting string bodies with "request body must be bytes; provide bodyBase64", even though the request layer already sends a string through fine. String bodies now go through as UTF-8 bytes; binary content still uses `bodyBase64`.
packages/plugins/openapi/src/sdk/invoke.ts+10−1
@@ -782,7 +782,16 @@ export const invoke = Effect.fn("OpenApi.invoke")(function* ( · 781 unmodified lines
782782 binaryBody?.ok === true && !octetStreamContent && isOctetStream(rb.contentType)
783783 ? rb.contentType
784784 : (selected?.contentType ?? rb.contentType);
785− if (isOctetStream(chosenCt) && toUint8Array(bodyValue) === null) {
785+ // A `bodyBase64` arg already decoded to bytes above. A plain string
786+ // `body` is the long-standing way callers pass (text) octet-stream
787+ // upload content, and applyRequestBody sends it through as-is, so let it
788+ // past. Only a genuinely non-byte, non-string shape (an object, say)
789+ // can't be uploaded as octet-stream and needs `bodyBase64`.
790+ if (
791+ isOctetStream(chosenCt) &&
792+ typeof bodyValue !== "string" &&
793+ toUint8Array(bodyValue) === null
794+ ) {
786795 return yield* new OpenApiInvocationError({
787796 message: "application/octet-stream request body must be bytes; provide `bodyBase64`",
788797 statusCode: Option.none(),
packages/plugins/openapi/src/sdk/non-json-body.test.ts+10−9
@@ -368,7 +368,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 367 unmodified lines
368368 }),
369369 );
370370
371− it.effect("application/octet-stream: string body fails before dispatch", () =>
371+ it.effect("application/octet-stream: string body passes through as UTF-8 bytes", () =>
372372 Effect.gen(function* () {
373373 const { server, captured } = yield* startEchoServer({
374374 payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()),
@@ -380,15 +380,16 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 5 unmodified lines
380380 slug: "bin_string_body",
381381 });
382382
383− const exit = yield* executor
384− .execute(conn.address("body.submit"), {
385− body: "3q2+7w==",
386− })
387− .pipe(Effect.exit);
383+ // A plain string `body` is the long-standing way callers upload text
384+ // content to an octet-stream endpoint, so it must reach the wire rather
385+ // than failing. It is sent verbatim as UTF-8 bytes (not base64-decoded),
386+ // which preserves the pre-bodyBase64 behavior; binary uploads still use
387+ // `bodyBase64`.
388+ const text = "plain file contents, not base64";
389+ yield* executor.execute(conn.address("body.submit"), { body: text });
388390
389− expect(Exit.isFailure(exit)).toBe(true);
390− expect(captured.contentType).toBe("");
391− expect(captured.body.length).toBe(0);
391+ expect(captured.contentType).toBe("application/octet-stream");
392+ expect(captured.body.toString("utf8")).toBe(text);
392393 }),
393394 );
394395

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t