Skip to content

Commit e19bae0

Browse files
dinwwwhclaude
andauthored
docs(downgrader): note cycle order, $defs size, swagger_dart_code_generator, swagger_parser and OpenApiSpex quirks (#137)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H41Joj1bcf6cWAwxSpC2c7
1 parent 81010aa commit e19bae0

1 file changed

Lines changed: 5 additions & 2 deletions

File tree

‎packages/downgrader/README.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,12 @@ Accepts the default OAS dialect, which adds `discriminator`, `xml`, `externalDoc
247247

248248
#### Limitations
249249

250-
- A `not` or `oneOf` inside a cycle of schemas, over a `$ref` back into that cycle to a schema that lost a restriction, can still reject values the original accepts.
250+
- A `not` or `oneOf` inside a cycle of schemas, over a `$ref` back into that cycle to a schema that lost a restriction, can still reject values the original accepts. So can one outside the cycle, over a `$ref` to a schema of the cycle that lost a restriction only through such a `$ref`, depending on which schema of the cycle comes first.
251251
- `$ref`s to an `$anchor`, or written relative to an `$id`, are left as written and dangle. Every `$ref` is read as a JSON Pointer from the document root, and one into `$defs` is recognized by its `/$defs/` segment.
252252
- An `anyOf` or `oneOf` of `{ type: "null" }` and a branch that a `$ref` points into stays as written, so the `$ref` still resolves, and code generators read it as a union.
253253
- `$ref`s into a keyword 3.0 removes, such as `prefixItems/0` or `propertyNames`, or into a `oneOf` that becomes `anyOf`, are left as written and dangle.
254254
- Recursion through `$defs` is cut to `{}` after one level. Where several `$ref`s enter the same cycle, the first copy made is reused, so a later one can be cut sooner.
255+
- Each `$ref` into `$defs` repeats its target once serialized, so definitions that reference each other in many layers make a large document, or one too large to serialize: SchemaStore's Kestra flow schema, 4 MB as compact JSON, would serialize to about 84 TB. In a document, move such definitions to `components/schemas`, where `$ref`s stay as written.
255256
- Older drafts are not supported. `definitions` is read as `$defs`, but other keywords that only draft-07 or 2019-09 define, such as `dependencies` and `additionalItems`, become extensions unconverted, so `$ref`s into them dangle, and array-form `items` passes through. Convert such schemas to 2020-12 first.
256257
- `patternProperties` that matches every name, as TypeBox emits for a record, gives `additionalProperties: true`, so its value schema is lost.
257258
- A record keyed by a union of literals, which Zod and Valibot write as an `anyOf` of `const` branches in `propertyNames`, loses its key names, so 3.0 allows any key. Key it by an enum instead, such as `z.enum` or `v.picklist`.
@@ -267,13 +268,15 @@ Accepts the default OAS dialect, which adds `discriminator`, `xml`, `externalDoc
267268
- Speakeasy, like Kiota, reads the `"null"` branch's `type: "string"` as a string, so a nullable `$ref` or enum also takes any string, and a nullable union of scalars with a string becomes `any`, since it then holds two strings. It also fails on a recursive component, reached from another schema, with a property of `type: "null"` alone, such as Zod's `z.null()`. Speakeasy reads 3.1 too, which keeps their types.
268269
- Hey API's Zod and Valibot plugins read the `"null"` branch as any value, so a union with null, such as a nullable `$ref`, accepts every value. Hey API reads 3.1 too, which keeps the `"null"` type.
269270
- openapi-codegen 11.1's TypeScript generator types the `null` in the `"null"` branch's `enum` as `any`, so a union with null, such as a nullable `$ref`, becomes `any`. It reads 3.1's `{ type: "null" }`, but types `const` and `type` lists there as `void`.
271+
- swagger_dart_code_generator takes only `{ type: "null" }` as null, so it types a nullable `$ref` or enum, which becomes a `oneOf` with the `"null"` branch, as `dynamic`. It reads 3.1 too, which keeps the class, but it stops on a boolean schema, such as a tuple's `items: false`, and fails to build a nullable array, as Zod's `.array().nullable()` writes.
272+
- swagger_parser makes every property of an object required and non-null when the object has no `required` list and one of its properties is an `allOf`, as a `$ref` with siblings becomes, so decoding a value that leaves out an optional property throws. swagger_parser reads 3.1 too, which keeps the `$ref` beside its siblings, but it stops on a boolean schema, such as a tuple's `items: false`.
270273
- Kubb leaves null out of the types and Zod schemas of an array item, a map value, or an `anyOf` branch with `nullable: true`, as a nullable value there becomes. Its Zod plugin also makes each object `$ref` in a `oneOf` strict, so a nullable `$ref`, which becomes a `oneOf` with the `"null"` branch, rejects keys that its schema does not list, although the schema allows them. Kubb reads 3.1 too, which keeps the `anyOf` with `{ type: "null" }`.
271274
- typed-openapi reads `format: byte`, which `contentEncoding: base64` becomes, as a file, so its types and Zod schemas take a `Blob` in place of the base64 string. typed-openapi reads 3.1 too, which keeps the string.
272275
- swagger-typescript-api, like typed-openapi, types `format: byte` as a `Blob`. It also drops `nullable: true` from a record that is a component, and from an array whose items may be null, so their types leave out null. swagger-typescript-api reads 3.1 too, which keeps the string and the `anyOf` with `{ type: "null" }`.
273276
- TypeSpec's `tsp-openapi3` turns a component that is a nullable object into a union of only `null`, so its properties are lost. It also reads the `"null"` branch as `null` twice, so once TypeSpec emits OpenAPI 3.1 again, a nullable `$ref`, which is a `oneOf`, rejects null. TypeSpec reads 3.1 too, which avoids both, but fails on a component that is a `const`, such as a Zod literal with an id.
274277
- ogen reads the `"null"` branch's `type: "string"` as a second string, so it fails, or skips the operation with `ignore_not_implemented`, on a `oneOf` or `anyOf` that also holds a string or a `$ref` to a string schema, as a nullable union of scalars or a nullable string enum becomes. ogen reads 3.1's `{ type: "null" }` branch, but not its numeric `exclusiveMinimum` or its `type` lists.
275278
- Jane's 3.0 generator throws a `TypeError` on an `anyOf`, in a property, with an object branch that requires a property of `type: "null"` alone, such as Zod's `z.null()`, since it writes each value in that property's `enum` as a PHP string, and `null` is not one. Jane reads 3.1 too, with `jane-php/open-api-3-1`, which keeps the `"null"` type.
276-
- kin-openapi, and Ruby's openapi_parser, which committee uses, reject null for an untyped schema such as the `{}` a nullable unknown becomes, although 3.0 lets it accept every value. 3.0 admits null by name only through `nullable`, which Redocly's recommended rules reject without a `type`.
279+
- kin-openapi, OpenApiSpex, and Ruby's openapi_parser, which committee uses, reject null for an untyped schema such as the `{}` a nullable unknown becomes, although 3.0 lets it accept every value. 3.0 admits null by name only through `nullable`, which Redocly's recommended rules reject without a `type`.
277280
- express-openapi and jest-openapi throw on the `true` that 3.0 writes in `exclusiveMinimum` or `exclusiveMaximum` beside `minimum` or `maximum`, as a numeric bound such as Zod's `.positive()` becomes. openapi-schema-validator and openapi-response-validator, which both use, and openapi-request-validator, which express-openapi uses too, read those keywords as draft 7 numbers.
278281
- FastMCP's `from_openapi` copies the `true` that 3.0 writes in `exclusiveMinimum` or `exclusiveMaximum` into its tools' JSON Schemas, where JSON Schema 2020-12 wants a number. MCP clients that check a tool's output schema reject it: the Python SDK fails each call to that tool, and the TypeScript SDK fails to list any of the server's tools. FastMCP reads 3.1 too, but fails to load a document with a boolean schema, such as a tuple's `items: false`.
279282
- NSwag reads an `allOf` that holds an inline scalar or enum schema, which a `$ref` with siblings into `$defs` or `definitions` becomes, as a class that derives from `string` or from the enum, which does not compile. It does the same for such an `allOf` in 3.1.

0 commit comments

Comments
 (0)