Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/downgrader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ Accepts the default OAS dialect, which adds `discriminator`, `xml`, `externalDoc
- datamodel-code-generator reads a required property with `nullable: true` as never null. Pass `--strict-nullable` to keep null.
- aiopenapi3 fails to load a 3.0 document that holds the `"null"` branch: it takes `null` out of a nullable `enum` and builds an empty `Literal` from what is left. aiopenapi3 reads 3.1 too, which keeps `{ type: "null" }`.
- oapi-codegen 2.8 rejects, and the OpenAPI Generator C# client breaks on, a string enum with a `format` such as `uuid`, which a `const` with a `format` becomes, as ArkType writes for `string.uuid`.
- Validators that coerce a value to each branch's `type`, as Prism, express-openapi-validator and openapi-backend do for parameters, and Fastify by default for bodies too, match a value such as `1` or `"1"` to two branches of the `oneOf` a union of scalar types becomes, and reject it. Fastify by default also wraps a value in an array, so it rejects a single value, and an array of one item, against the `oneOf` a union of a scalar type and an array becomes, as for `string | string[]`. It also coerces in place, so a branch of a `oneOf` can change the value that later branches and the handler see. A number in Effect's `Schema.Number`, a `oneOf` of a number and a string enum, arrives as a string, and null in a nullable enum, a nullable `$ref` to an enum, or a nullable union of literals becomes a value such as `""`, which the `"null"` branch rejects where 3.1's `{ type: "null" }` turns it back into null. Prism, express-openapi-validator, openapi-backend and fastify-openapi-glue read 3.1 too, which keeps the `type` list or the `anyOf`.
- Validators that coerce a value to each branch's `type`, as Prism, express-openapi-validator and openapi-backend do for parameters, and Fastify by default for bodies too, match a value such as `1` or `"1"` to two branches of the `oneOf` a union of scalar types becomes, and reject it. So a query flag that takes `"true"`, `"false"`, `"0"`, `"1"` or a boolean, as GrowthBook writes, rejects `?flag=true`, since `"true"` matches the string and, coerced, the boolean. Fastify by default also wraps a value in an array, so it rejects a single value, and an array of one item, against the `oneOf` a union of a scalar type and an array becomes, as for `string | string[]`. It also coerces in place, so a branch of a `oneOf` can change the value that later branches and the handler see. A number in Effect's `Schema.Number`, a `oneOf` of a number and a string enum, arrives as a string, and null in a nullable enum, a nullable `$ref` to an enum, or a nullable union of literals becomes a value such as `""`, which the `"null"` branch rejects where 3.1's `{ type: "null" }` turns it back into null. Prism, express-openapi-validator, openapi-backend and fastify-openapi-glue read 3.1 too, which keeps the `type` list or the `anyOf`.
- Kiota reads the `"null"` branch's `type: "string"` as a real type, so array items that may be null, and a union of several types with null, come out as strings. Kiota reads 3.1 too, which keeps their types.
- 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.
- 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.
Expand Down
22 changes: 22 additions & 0 deletions packages/downgrader/tests/v3.1-to-v3.0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,28 @@ describe('downgradeSchemaV31ToV30', () => {
{ title: 'C', type: 'string', enum: ['c'] },
],
})
// Enums of one type with another keyword among them, or with no `type`, stay.
expect(
downgradeSchemaV31ToV30({
anyOf: [{ const: 'a' }, { const: 'b', description: 'B' }, { const: 'c' }, { type: 'null' }],
}),
).toEqual({
anyOf: [
{ type: 'string', enum: ['a'] },
{ description: 'B', type: 'string', enum: ['b'] },
{ type: 'string', enum: ['c'] },
{ type: 'string', nullable: true, enum: [null] },
],
})
// A branch stays as written when no other enum shares its `type`.
const described: OpenAPIV3_1.SchemaObject = { type: 'string', enum: ['a'], description: 'D' }
expect(
downgradeSchemaV31ToV30({ description: 'D', anyOf: [described, { type: 'boolean' }] }),
).toEqual({ description: 'D', oneOf: [described, { type: 'boolean' }] })
const typeless: OpenAPIV3_1.SchemaObject = {
anyOf: [{ enum: ['a', 1] }, { enum: ['b', 2] }, { type: 'boolean' }],
}
expect(downgradeSchemaV31ToV30(typeless)).toEqual(typeless)
expect(
downgradeSchemaV31ToV30({ anyOf: [{ const: 'a' }, { const: 1 }, { const: 'a' }] }),
).toEqual({
Expand Down
Loading