fix(downgrader): keep untyped multipart parts sent as octet-stream in 3.1 to 3.0 - #28
Conversation
… 3.1 to 3.0 In 3.1, a multipart or URL-encoded request body part with no `type`, or a string with `contentEncoding`, defaults to `application/octet-stream`. 3.0 has no default for untyped parts and sends such strings as `text/plain`, so a file part downgraded to 3.0 lost its content type. The converter now sets `contentType: application/octet-stream` in the part's Encoding Object, unless 3.0's defaults already give it or the Encoding Object sets a content type or RFC6570-style fields.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
ℹ️ Minor suggestions only.
Reviewed changes
- Form-body detection:
convertRequestMediaTyperoutesmultipart/*andapplication/x-www-form-urlencodedrequest bodies through a newfinishFormMediaType; other request media types keep the old path.FORM_MEDIA_TYPE_FIELDSis a distinct copy ofMEDIA_TYPE_FIELDSso a media type shared by a request and a response converts independently. - Part classification:
subschemas/formPartsgather top-level part schemas through$ref,allOf,anyOf,oneOf;defaultsToOctetStreamdecides whether 3.1.2 would default the part toapplication/octet-stream, anddefaultsToOctetStreamIn30whether the converted part already defaults there in 3.0.4. shared.ts:map()now forwards each entry's key so the request-body converter can tell form media types apart.- Tests/README: 29 new unit and e2e cases (validated against the 3.0 schema) plus a README row.
I verified the load-bearing spec claims: 3.1.2 defaults a part with no type, or a string with contentEncoding, to application/octet-stream; 3.0.4 defaults a string with format: binary/byte to application/octet-stream. The classification logic matches both, and pnpm test, pnpm lint, pnpm type:check pass.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏
The 3.0-default check only saw properties declared directly on the body schema, so a form behind a `$ref` got an explicit `contentType` for binary parts while the same form inlined got none. Every part that 3.1 sends as `application/octet-stream` now gets the explicit `contentType`, whether the body schema is inline or a reference. What is sent does not change.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Removed the 3.0-default guard:
141b7f1dropsdefaultsToOctetStreamIn30, so every form part whose 3.1 default isapplication/octet-streamnow gets an explicitcontentType, whether it is reached inline or through$ref/composition. This closes the inline-vs-reference inconsistency flagged in the prior review. - Documented the wider behavior: the README row no longer exempts parts that already convert to a
binary/bytestring, matching the emitted output. - Regression tests: a new case asserts the same
encodingfor inline and referenced body schemas, and the raw-binary / base64 / array-of-binary cases moved into the "sets contentType" group. I confirmed these fail with the old guard restored, so they are live coverage rather than theatre.
I re-ran the touched suites and repo checks on 141b7f1: pnpm test (downgrader unit + shared + e2e, 317 passed), pnpm lint, and pnpm type:check all pass. The explicit contentType restates 3.0.4's default for format: binary/byte parts, so the added output is redundant-but-consistent rather than a semantic change.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

The 3.1 → 3.0 downgrader now keeps the content type of file parts in multipart and URL-encoded request bodies. In 3.1, a part with no
type(the spec's way to describe raw binary) is sent asapplication/octet-stream, but 3.0 defines no default for untyped parts, so a downgraded upload field was left for each tool to guess. The converter now writescontentType: application/octet-streaminto the part's Encoding Object, so 3.0 tools send the part exactly as 3.1 did.Fixes
profileImage: {}encoding.profileImage.contentType: application/octet-streamfiles: { type: array, items: {} }token: { type: string, contentEncoding: base64url }text/plainapplication/octet-streambinaryorbytestring, such ascontentMediaType: image/png, get the same explicitcontentType, which restates 3.0's default. The output is therefore the same whether the body schema is inline or a$ref. Existing snapshots are unchanged.contentType, orstyle/explode/allowReserved(which make the spec ignorecontentType), is left as written. Existingheadersare kept beside the addedcontentType.encodingapplies only to request bodies in 3.0 and 3.1.For reviewers
$ref,allOf,anyOf, andoneOf. A part whose type is unknown (external or missing$ref,false, several types) and nested arrays are left alone rather than guessed.{ type: string, contentMediaType: image/png }is unchanged on purpose: 3.1.0 sends it as octet-stream while 3.1.1+ saystext/plain, and the existingformat: binaryoutput matches the author's likely intent.map()inshared.tsnow passes each entry's key to its converter, so request body content can tell form media types apart.Testing
contentTypefail onmain.pnpm test(517 tests),pnpm lint, andpnpm type:checkpass.