diff --git a/.changeset/eleven-sloths-boil.md b/.changeset/eleven-sloths-boil.md new file mode 100644 index 000000000..2855e0e86 --- /dev/null +++ b/.changeset/eleven-sloths-boil.md @@ -0,0 +1,5 @@ +--- +"@kubb/plugin-oas": patch +--- + +remove duplicated keys when using `allOf` and applying required on fields diff --git a/docs/changelog.md b/docs/changelog.md index 0e9d1b135..d473bd8f6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,7 @@ title: Changelog ## 3.5.2 - [`plugin-faker`](/plugins/plugin-faker): `faker.number.float` with default min `Number.MIN_VALUE` and max set to `Number.MAX_VALUE`. +- [`plugin-oas`](/plugins/plugin-oas): remove duplicated keys when using `allOf` and applying required on fields ## 3.5.1 - [`core`](/plugins/core): build of `@kubb/core` with correct types diff --git a/examples/typescript/package.json b/examples/typescript/package.json index 585aedf80..8d94ea504 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -15,6 +15,7 @@ "build": "tsup", "clean": "npx rimraf ./dist", "generate": "kubb generate", + "generate:debug": "NODE_OPTIONS='--inspect-brk' kubb generate", "start": "tsup --watch", "test": "vitest", "typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false" diff --git a/examples/typescript/petStore.yaml b/examples/typescript/petStore.yaml index fab92fa99..5a896a2d5 100644 --- a/examples/typescript/petStore.yaml +++ b/examples/typescript/petStore.yaml @@ -689,9 +689,10 @@ components: Address: type: object properties: - street: + streetName: + type: string + streetNumber: type: string - example: 437 Lytton city: type: string example: Palo Alto @@ -832,6 +833,15 @@ components: readOnly: true bark: type: string + FullAddress: + properties: + streetName: + type: string + allOf: + - $ref: "#/components/schemas/Address" + required: + - streetName + - streetNumber AddPetRequest: required: - name diff --git a/examples/typescript/src/gen/index.ts b/examples/typescript/src/gen/index.ts index 550a14efe..22c761b9f 100644 --- a/examples/typescript/src/gen/index.ts +++ b/examples/typescript/src/gen/index.ts @@ -32,6 +32,7 @@ export type { FindPetsByTagsQueryResponse, FindPetsByTagsQuery, } from './ts/models/FindPetsByTags.ts' +export type { FullAddress } from './ts/models/FullAddress.ts' export type { GetInventory200, GetInventoryQueryResponse, GetInventoryQuery } from './ts/models/GetInventory.ts' export type { GetOrderByIdPathParams, diff --git a/examples/typescript/src/gen/models.ts b/examples/typescript/src/gen/models.ts index 37bd9ba8e..5e1c64ac9 100644 --- a/examples/typescript/src/gen/models.ts +++ b/examples/typescript/src/gen/models.ts @@ -62,7 +62,11 @@ export interface Address { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ @@ -240,6 +244,18 @@ export interface Dog { bark?: string } +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} + export enum AddPetRequestStatusEnum { available = 'available', pending = 'pending', diff --git a/examples/typescript/src/gen/modelsConst.ts b/examples/typescript/src/gen/modelsConst.ts index 64785306c..305834860 100644 --- a/examples/typescript/src/gen/modelsConst.ts +++ b/examples/typescript/src/gen/modelsConst.ts @@ -66,7 +66,11 @@ export type Address = { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ @@ -252,6 +256,18 @@ export type Dog = { bark?: string } +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} + export const addPetRequestStatusEnum = { available: 'available', pending: 'pending', diff --git a/examples/typescript/src/gen/modelsConstEnum.ts b/examples/typescript/src/gen/modelsConstEnum.ts index 94b3c4ada..3abbd4d83 100644 --- a/examples/typescript/src/gen/modelsConstEnum.ts +++ b/examples/typescript/src/gen/modelsConstEnum.ts @@ -62,7 +62,11 @@ export type Address = { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ @@ -240,6 +244,18 @@ export type Dog = { bark?: string } +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} + export enum AddPetRequestStatusEnum { available = 'available', pending = 'pending', diff --git a/examples/typescript/src/gen/modelsLiteral.ts b/examples/typescript/src/gen/modelsLiteral.ts index 4b2ab5ed7..6e64c9cfb 100644 --- a/examples/typescript/src/gen/modelsLiteral.ts +++ b/examples/typescript/src/gen/modelsLiteral.ts @@ -54,7 +54,11 @@ export type Address = { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ @@ -221,6 +225,18 @@ export type Dog = { bark?: string } +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} + export type AddPetRequestStatusEnum = 'available' | 'pending' | 'sold' export type AddPetRequest = { diff --git a/examples/typescript/src/gen/modelsPascalConst.ts b/examples/typescript/src/gen/modelsPascalConst.ts index 64785306c..305834860 100644 --- a/examples/typescript/src/gen/modelsPascalConst.ts +++ b/examples/typescript/src/gen/modelsPascalConst.ts @@ -66,7 +66,11 @@ export type Address = { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ @@ -252,6 +256,18 @@ export type Dog = { bark?: string } +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} + export const addPetRequestStatusEnum = { available: 'available', pending: 'pending', diff --git a/examples/typescript/src/gen/schemas/Address.json b/examples/typescript/src/gen/schemas/Address.json index 21f73590a..a6a0e8f07 100644 --- a/examples/typescript/src/gen/schemas/Address.json +++ b/examples/typescript/src/gen/schemas/Address.json @@ -1,7 +1,8 @@ { "type": "object", "properties": { - "street": { "type": "string", "example": "437 Lytton" }, + "streetName": { "type": "string" }, + "streetNumber": { "type": "string" }, "city": { "type": "string", "example": "Palo Alto" }, "state": { "type": "string", "example": "CA" }, "zip": { "type": "string", "example": "94301" } diff --git a/examples/typescript/src/gen/schemas/Customer.json b/examples/typescript/src/gen/schemas/Customer.json index 854aa64af..b8288a0d9 100644 --- a/examples/typescript/src/gen/schemas/Customer.json +++ b/examples/typescript/src/gen/schemas/Customer.json @@ -9,7 +9,8 @@ "items": { "type": "object", "properties": { - "street": { "type": "string", "example": "437 Lytton" }, + "streetName": { "type": "string" }, + "streetNumber": { "type": "string" }, "city": { "type": "string", "example": "Palo Alto" }, "state": { "type": "string", "example": "CA" }, "zip": { "type": "string", "example": "94301" } diff --git a/examples/typescript/src/gen/schemas/fullAddress.json b/examples/typescript/src/gen/schemas/fullAddress.json new file mode 100644 index 000000000..82623a733 --- /dev/null +++ b/examples/typescript/src/gen/schemas/fullAddress.json @@ -0,0 +1,19 @@ +{ + "properties": { "streetName": { "type": "string" } }, + "allOf": [ + { + "type": "object", + "properties": { + "streetName": { "type": "string" }, + "streetNumber": { "type": "string" }, + "city": { "type": "string", "example": "Palo Alto" }, + "state": { "type": "string", "example": "CA" }, + "zip": { "type": "string", "example": "94301" } + }, + "xml": { "name": "address" }, + "x-readme-ref-name": "Address" + } + ], + "required": ["streetName", "streetNumber"], + "x-readme-ref-name": "FullAddress" +} diff --git a/examples/typescript/src/gen/ts/models/Address.ts b/examples/typescript/src/gen/ts/models/Address.ts index eb578826d..644b504c2 100644 --- a/examples/typescript/src/gen/ts/models/Address.ts +++ b/examples/typescript/src/gen/ts/models/Address.ts @@ -2,7 +2,11 @@ export type Address = { /** * @type string | undefined */ - street?: string + streetName?: string + /** + * @type string | undefined + */ + streetNumber?: string /** * @type string | undefined */ diff --git a/examples/typescript/src/gen/ts/models/FullAddress.ts b/examples/typescript/src/gen/ts/models/FullAddress.ts new file mode 100644 index 000000000..779041fda --- /dev/null +++ b/examples/typescript/src/gen/ts/models/FullAddress.ts @@ -0,0 +1,13 @@ +import type { Address } from './Address.ts' + +export type FullAddress = Address & { + /** + * @type string + */ + streetNumber: string +} & { + /** + * @type string + */ + streetName: string +} diff --git a/examples/typescript/src/gen/ts/models/index.ts b/examples/typescript/src/gen/ts/models/index.ts index 85b9da672..8443fdad1 100644 --- a/examples/typescript/src/gen/ts/models/index.ts +++ b/examples/typescript/src/gen/ts/models/index.ts @@ -26,6 +26,7 @@ export type { FindPetsByStatusQuery, } from './FindPetsByStatus.ts' export type { FindPetsByTagsQueryParams, FindPetsByTags200, FindPetsByTags400, FindPetsByTagsQueryResponse, FindPetsByTagsQuery } from './FindPetsByTags.ts' +export type { FullAddress } from './FullAddress.ts' export type { GetInventory200, GetInventoryQueryResponse, GetInventoryQuery } from './GetInventory.ts' export type { GetOrderByIdPathParams, GetOrderById200, GetOrderById400, GetOrderById404, GetOrderByIdQueryResponse, GetOrderByIdQuery } from './GetOrderById.ts' export type { GetPetByIdPathParams, GetPetById200, GetPetById400, GetPetById404, GetPetByIdQueryResponse, GetPetByIdQuery } from './GetPetById.ts' diff --git a/examples/typescript/src/gen/ts/models/oas.ts b/examples/typescript/src/gen/ts/models/oas.ts index 2597ac83a..2d592694f 100644 --- a/examples/typescript/src/gen/ts/models/oas.ts +++ b/examples/typescript/src/gen/ts/models/oas.ts @@ -1004,9 +1004,11 @@ export const oas = { Address: { type: 'object', properties: { - street: { + streetName: { + type: 'string', + }, + streetNumber: { type: 'string', - example: '437 Lytton', }, city: { type: 'string', @@ -1199,6 +1201,19 @@ export const oas = { }, required: ['type'], }, + FullAddress: { + properties: { + streetName: { + type: 'string', + }, + }, + allOf: [ + { + $ref: '#/components/schemas/Address', + }, + ], + required: ['streetName', 'streetNumber'], + }, AddPetRequest: { required: ['name', 'photoUrls'], type: 'object', diff --git a/packages/plugin-oas/src/SchemaGenerator.ts b/packages/plugin-oas/src/SchemaGenerator.ts index f58b7661f..895da0f56 100644 --- a/packages/plugin-oas/src/SchemaGenerator.ts +++ b/packages/plugin-oas/src/SchemaGenerator.ts @@ -564,10 +564,15 @@ export class SchemaGenerator< .filter(Boolean) const items = schemaWithoutAllOf.required - .filter((key) => + .filter((key) => { // filter out keys that are already part of the properties(reduce duplicated keys(https://github.com/kubb-labs/kubb/issues/1492) - schemaWithoutAllOf.properties ? !Object.keys(schemaWithoutAllOf.properties).includes(key) : false, - ) + if (schemaWithoutAllOf.properties) { + return !Object.keys(schemaWithoutAllOf.properties).includes(key) + } + + // schema should include required fields when necessary https://github.com/kubb-labs/kubb/issues/1522 + return true + }) .map((key) => { const schema = schemas.find((item) => item.properties && Object.keys(item.properties).find((propertyKey) => propertyKey === key)) diff --git a/packages/plugin-oas/src/__snapshots__/SchemaGenerator.test.ts.snap b/packages/plugin-oas/src/__snapshots__/SchemaGenerator.test.ts.snap index ea64b74bf..66d4bc9f7 100644 --- a/packages/plugin-oas/src/__snapshots__/SchemaGenerator.test.ts.snap +++ b/packages/plugin-oas/src/__snapshots__/SchemaGenerator.test.ts.snap @@ -12,6 +12,68 @@ exports[`SchemaGenerator core > 'FullAddress' 1`] = ` }, "keyword": "ref", }, + { + "args": { + "additionalProperties": [], + "properties": { + "streetName": [ + { + "keyword": "string", + }, + { + "args": { + "format": undefined, + "type": "string", + }, + "keyword": "schema", + }, + { + "args": "streetName", + "keyword": "name", + }, + ], + }, + }, + "keyword": "object", + }, + { + "args": { + "format": undefined, + "type": "object", + }, + "keyword": "schema", + }, + { + "args": { + "additionalProperties": [], + "properties": { + "streetNumber": [ + { + "keyword": "string", + }, + { + "args": { + "format": undefined, + "type": "string", + }, + "keyword": "schema", + }, + { + "args": "streetNumber", + "keyword": "name", + }, + ], + }, + }, + "keyword": "object", + }, + { + "args": { + "format": undefined, + "type": "object", + }, + "keyword": "schema", + }, ], "keyword": "and", },