fix(downgrader): keep binary content in 3.1 to 3.0 - #22
Conversation
Raw binary (`contentMediaType` without `contentEncoding`) now becomes
`type: string` + `format: binary` for every media type, not just
`application/octet-stream`, and `contentEncoding: base64` now also gets
`type: string` beside `format: byte`, following the spec's binary
migration table. Before, `contentMediaType: image/png` became `{}`.
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.
ℹ️ No critical issues — minor soundness suggestion inline.
Reviewed changes
- Content keyword conversion — new
getContentFormatplus a reworkedconvertContentKeywordsnow mapcontentEncoding: base64→format: byteand any stringcontentMediaType→format: binary, addingtype: stringwhen the source omitstypeand preserving an existingformator non-stringtype. - Shared type helper — new
hasTypepowers the content-keyword guard and the XMLwrappedcheck (semantics unchanged). - Tests — unit matrix for raw, encoded, nullable, multi-type, existing-format, non-string-
type, andbase64urlinputs, plus a new e2e test that downgrades raw and base64 bodies and validates the result as 3.0. - README — schema table and "Removed" list updated to describe the new mappings.
The spec's binary migration table (OAS 3.1.1 §4.4.2.1) matches the mappings, and pnpm test passes locally (360 tests).
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏
Adding type: string next to a non-string const or enum made the 3.0 schema accept nothing, while the 3.1 source accepted those values. format is still added, since it only applies to strings.
There was a problem hiding this comment.
ℹ️ Prior feedback addressed for
const/enum; one residual case inline.
Reviewed changes
- Guarded the
type: 'string'injection — newhasNonStringValuekeeps the injectedtype: 'string'off schemas whoseconstorenumcarries a non-string value, fixing the unsatisfiable outputs flagged last review. All-string enums still gettype: 'string'. - Tests — the content-keyword matrix now covers an all-string enum, a non-string
const, and an enum with a non-string entry alongside the binary/byte formats. - README — the
contentEncoding: base64row documents the new "everyconst/enumvalue is a string" condition, and the "Removed" list names a non-base64contentEncodingwith itscontentMediaType.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏
$ref, allOf, anyOf, oneOf and not already limit the value type, so adding type: string beside them could make the 3.0 schema accept nothing or drop values the source accepted, such as null in a nullable anyOf. format is still added.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Skipped
type: stringbeside type-constraining siblings —hasNonStringValuebecamehasTypeConstraint, now also true for$ref,allOf,anyOf,oneOf, andnot, so the injectedtype: 'string'no longer sits beside a combinator/ref that pins the value type.formatis still emitted at the parent level (non-validating in 3.0). - Tests — exact unit assertions pin the
allOf, nullableanyOf, and$refcases; each would fail without the guard. - README — the
contentEncoding: base64row now lists the$ref/ combinator keywords alongside the non-stringconst/enumcondition.
This closes the residual unsatisfiable-output class raised in both prior threads. pnpm test passes locally (365 tests) and the checked-out converter confirms the new outputs.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏
Drop the guard that skipped type: string beside $ref, composition keywords, and non-string const or enum. It only protected self-contradictory schemas (binary content that is also a number or an object) that generators do not emit, so the simpler rule wins.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (664d285), commit 33b3d5d replaced the hasTypeConstraint guard with a single rule.
- Dropped the type-constraint guard —
convertContentKeywordsnow injectstype: stringwhenever the source omitstype; the$ref/allOf/anyOf/oneOf/notand non-stringconst/enumchecks are gone. - Pruned the guard tests — the six rows pinning the skipped-injection cases (non-string
const, mixedenum, all-stringenum,allOf, nullableanyOf,$ref) were removed. - Simplified the README row — the
contentEncoding: base64description now reads "plustype: stringwhentypeis missing".
This intentionally reintroduces the unsatisfiable-output behavior the two prior threads raised: { const: 42, contentEncoding: 'base64' }, { contentMediaType: 'image/png', enum: ['x', null] }, and { contentMediaType: 'image/png', allOf: [{ type: 'object' }] } again emit type: string beside a non-string constraint. The author documented the rationale in-thread (contradictory source intent, generators don't emit these shapes, shorter code), so no change is requested. The realistic nullable shape type: ['string', 'null'] still converts to { format: 'binary', nullable: true, type: 'string' }, and pnpm test passes locally (359 tests).
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

The 3.1 → 3.0 downgrader no longer drops binary content.
contentMediaType: image/pngused to come out as{}, and onlyapplication/octet-streambecameformat: binary. Now raw and base64-encoded binary map to 3.0'sformat: binaryandformat: bytewithtype: string, following the spec's binary migration table. Upload schemas now reach 3.0 code generators as files or byte fields instead ofany.Fixes
{ contentMediaType: 'image/png' }{}{ type: 'string', format: 'binary' }{ contentMediaType: 'application/octet-stream' }{ format: 'binary' }{ type: 'string', format: 'binary' }{ contentEncoding: 'base64' }{ format: 'byte' }{ type: 'string', format: 'byte' }type: stringis added only whentypeis missing, and an existingformatis kept.typeexcludesstring(e.g.object) get neither keyword, since content keywords only apply to strings.base64urland other encodings are still dropped: 3.0'sbyteis RFC 4648 section 4 base64 only.For reviewers
type: stringto a schema with notypetightens validation, the one place this converter does. It is what the spec's table prescribes, and 3.0 tools ignoreformatwithouttype.binary, including text ones. So a JSON-in-a-string schema like{ type: 'string', contentMediaType: 'application/json' }also becomesformat: binary. Telling text and binary media types apart would need a guessed list, so this keeps one rule.Testing
format, non-stringtype, andbase64url.image/pngbody and a base64text/plainbody and validates the result against the 3.0 schema.pnpm test(360 tests),pnpm lintandpnpm type:checkpass. Existing snapshots are unchanged.