From 72094f6490634821654875e8c7ca662d9d3f8c79 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 29 Jan 2024 09:06:25 -0500 Subject: [PATCH 1/3] Introduce a couple of broken schema parsing --- test/__snapshots__/index.spec.ts.snap | 78 +++++++++++++++++++++++++++ test/spec/v3.json | 44 ++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index ec4f8de9e..bc9d216b3 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -3690,6 +3690,8 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { _default } from './models/_default'; +export type { AnyOfAnyAndNull } from './models/AnyOfAnyAndNull'; +export type { AnyOfArrays } from './models/AnyOfArrays'; export type { ArrayWithArray } from './models/ArrayWithArray'; export type { ArrayWithBooleans } from './models/ArrayWithBooleans'; export type { ArrayWithNumbers } from './models/ArrayWithNumbers'; @@ -3764,6 +3766,8 @@ export type { SimpleString } from './models/SimpleString'; export type { SimpleStringWithPattern } from './models/SimpleStringWithPattern'; export { $_default } from './schemas/$_default'; +export { $AnyOfAnyAndNull } from './schemas/$AnyOfAnyAndNull'; +export { $AnyOfArrays } from './schemas/$AnyOfArrays'; export { $ArrayWithArray } from './schemas/$ArrayWithArray'; export { $ArrayWithBooleans } from './schemas/$ArrayWithBooleans'; export { $ArrayWithNumbers } from './schemas/$ArrayWithNumbers'; @@ -3873,6 +3877,34 @@ export type _default = { " `; +exports[`v3 should generate: test/generated/v3/models/AnyOfAnyAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type AnyOfAnyAndNull = { + data?: any | null; +}; + +" +`; + +exports[`v3 should generate: test/generated/v3/models/AnyOfArrays.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type AnyOfArrays = { + results?: Array<({ + onePro?: boolean; + } | { + anotherProp?: boolean; + })>; +}; + +" +`; + exports[`v3 should generate: test/generated/v3/models/ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5054,6 +5086,52 @@ export const $_default = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$AnyOfAnyAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $AnyOfAnyAndNull = { + properties: { + data: { + type: 'any-of', + contains: [{ + type: 'null', + }], + }, + }, +} as const; +" +`; + +exports[`v3 should generate: test/generated/v3/schemas/$AnyOfArrays.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $AnyOfArrays = { + properties: { + results: { + type: 'any-of', + contains: [{ + properties: { + onePro: { + type: 'boolean', + }, + }, + }, { + properties: { + anotherProp: { + type: 'boolean', + }, + }, + }], + }, + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ diff --git a/test/spec/v3.json b/test/spec/v3.json index e11af1eb2..2528e3120 100644 --- a/test/spec/v3.json +++ b/test/spec/v3.json @@ -2095,7 +2095,7 @@ } } }, - "Enum1":{ + "Enum1": { "enum": [ "Bird", "Dog" @@ -2290,6 +2290,48 @@ } } }, + "AnyOfAnyAndNull": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + } + }, + "AnyOfArrays": { + "type": "object", + "properties": { + "results": { + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "oneProp": { + "type": "boolean" + } + } + }, + { + "type": "object", + "properties": { + "anotherProp": { + "type": "boolean" + } + } + } + ] + }, + "type": "array", + "title": "Results" + } + } + }, "CompositionBaseModel": { "description": "This is a base model with two simple optional properties", "type": "object", From 4174eb76c5d17fca078b916b9bd7ab634a729408 Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 29 Jan 2024 16:42:40 +0000 Subject: [PATCH 2/3] chore(model): add another test case --- src/openApi/v3/parser/getModelComposition.ts | 4 +- test/__snapshots__/index.spec.ts.snap | 66 ++++++++++++++++++-- test/spec/v3.json | 46 ++++++++++++-- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/openApi/v3/parser/getModelComposition.ts b/src/openApi/v3/parser/getModelComposition.ts index 2c27d1815..afbd34087 100644 --- a/src/openApi/v3/parser/getModelComposition.ts +++ b/src/openApi/v3/parser/getModelComposition.ts @@ -30,9 +30,11 @@ export const getModelComposition = ( .filter(model => { const hasProperties = model.properties.length; const hasEnums = model.enums.length; + const hasLink = typeof model.link !== 'undefined' && model.link !== null; + console.log(model.name, model) const isObject = model.type === 'any'; const isDictionary = model.export === 'dictionary'; - const isEmpty = isObject && !hasProperties && !hasEnums; + const isEmpty = isObject && !hasProperties && !hasEnums && !hasLink; return !isEmpty || isDictionary; }) .forEach(model => { diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index bc9d216b3..281363bd5 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -3710,6 +3710,7 @@ export type { CompositionWithAllOfAndNullable } from './models/CompositionWithAl export type { CompositionWithAnyOf } from './models/CompositionWithAnyOf'; export type { CompositionWithAnyOfAndNullable } from './models/CompositionWithAnyOfAndNullable'; export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyOfAnonymous'; +export type { CompositionWithNestedAnyAndTypeNull } from './models/CompositionWithNestedAnyAndTypeNull'; export type { CompositionWithNestedAnyOfAndNull } from './models/CompositionWithNestedAnyOfAndNull'; export type { CompositionWithOneOf } from './models/CompositionWithOneOf'; export type { CompositionWithOneOfAndComplexArrayDictionary } from './models/CompositionWithOneOfAndComplexArrayDictionary'; @@ -3786,6 +3787,7 @@ export { $CompositionWithAllOfAndNullable } from './schemas/$CompositionWithAllO export { $CompositionWithAnyOf } from './schemas/$CompositionWithAnyOf'; export { $CompositionWithAnyOfAndNullable } from './schemas/$CompositionWithAnyOfAndNullable'; export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfAnonymous'; +export { $CompositionWithNestedAnyAndTypeNull } from './schemas/$CompositionWithNestedAnyAndTypeNull'; export { $CompositionWithNestedAnyOfAndNull } from './schemas/$CompositionWithNestedAnyOfAndNull'; export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf'; export { $CompositionWithOneOfAndComplexArrayDictionary } from './schemas/$CompositionWithOneOfAndComplexArrayDictionary'; @@ -3894,11 +3896,14 @@ exports[`v3 should generate: test/generated/v3/models/AnyOfArrays.ts 1`] = ` /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +/** + * This is a simple array with any of properties + */ export type AnyOfArrays = { results?: Array<({ - onePro?: boolean; + foo?: string; } | { - anotherProp?: boolean; + bar?: string; })>; }; @@ -4167,6 +4172,23 @@ export type CompositionWithAnyOfAnonymous = { " `; +exports[`v3 should generate: test/generated/v3/models/CompositionWithNestedAnyAndTypeNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: ((ModelWithDictionary | null) | (ModelWithArray | null)); +}; + +" +`; + exports[`v3 should generate: test/generated/v3/models/CompositionWithNestedAnyOfAndNull.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5110,19 +5132,20 @@ exports[`v3 should generate: test/generated/v3/schemas/$AnyOfArrays.ts 1`] = ` /* tslint:disable */ /* eslint-disable */ export const $AnyOfArrays = { + description: \`This is a simple array with any of properties\`, properties: { results: { type: 'any-of', contains: [{ properties: { - onePro: { - type: 'boolean', + foo: { + type: 'string', }, }, }, { properties: { - anotherProp: { - type: 'boolean', + bar: { + type: 'string', }, }, }], @@ -5464,6 +5487,37 @@ export const $CompositionWithAnyOfAnonymous = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$CompositionWithNestedAnyAndTypeNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithNestedAnyAndTypeNull = { + description: \`This is a model with nested 'any of' property with a type null\`, + properties: { + propA: { + type: 'any-of', + contains: [{ + type: 'any-of', + contains: [{ + type: 'ModelWithDictionary', + }, { + type: 'null', + }], + }, { + type: 'any-of', + contains: [{ + type: 'ModelWithArray', + }, { + type: 'null', + }], + }], + }, + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$CompositionWithNestedAnyOfAndNull.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ diff --git a/test/spec/v3.json b/test/spec/v3.json index 2528e3120..95c98243d 100644 --- a/test/spec/v3.json +++ b/test/spec/v3.json @@ -2095,6 +2095,43 @@ } } }, + "CompositionWithNestedAnyAndTypeNull": { + "description": "This is a model with nested 'any of' property with a type null", + "type": "object", + "properties": { + "propA": { + "type": "object", + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelWithDictionary" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelWithArray" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + } + } + }, "Enum1": { "enum": [ "Bird", @@ -2304,6 +2341,7 @@ } }, "AnyOfArrays": { + "description": "This is a simple array with any of properties", "type": "object", "properties": { "results": { @@ -2312,16 +2350,16 @@ { "type": "object", "properties": { - "oneProp": { - "type": "boolean" + "foo": { + "type": "string" } } }, { "type": "object", "properties": { - "anotherProp": { - "type": "boolean" + "bar": { + "type": "string" } } } From f35bb1275b916d7b9dbba2a22fb3be59d016d9f1 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 1 Feb 2024 23:46:31 +0800 Subject: [PATCH 3/3] fix(any-of): handle more cases --- src/openApi/v3/parser/getModel.ts | 5 +- src/openApi/v3/parser/getModelComposition.ts | 12 +- test/__snapshots__/index.spec.ts.snap | 77 ++++++++++--- test/spec/v3.json | 109 +++++++++++-------- 4 files changed, 134 insertions(+), 69 deletions(-) diff --git a/src/openApi/v3/parser/getModel.ts b/src/openApi/v3/parser/getModel.ts index ad55d0fc7..b48d70344 100644 --- a/src/openApi/v3/parser/getModel.ts +++ b/src/openApi/v3/parser/getModel.ts @@ -13,7 +13,8 @@ export const getModel = ( openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, - name: string = '' + name: string = '', + parentDefinition: OpenApiSchema | null = null ): Model => { const model: Model = { name, @@ -82,7 +83,7 @@ export const getModel = ( model.imports.push(...arrayItems.imports); model.default = getModelDefault(definition, model); return model; - } else if (definition.items.anyOf) { + } else if (definition.items.anyOf && parentDefinition) { return getModel(openApi, definition.items); } else { const arrayItems = getModel(openApi, definition.items); diff --git a/src/openApi/v3/parser/getModelComposition.ts b/src/openApi/v3/parser/getModelComposition.ts index afbd34087..41dc4d321 100644 --- a/src/openApi/v3/parser/getModelComposition.ts +++ b/src/openApi/v3/parser/getModelComposition.ts @@ -26,17 +26,7 @@ export const getModelComposition = ( const properties: Model[] = []; definitions - .map(definition => getModel(openApi, definition)) - .filter(model => { - const hasProperties = model.properties.length; - const hasEnums = model.enums.length; - const hasLink = typeof model.link !== 'undefined' && model.link !== null; - console.log(model.name, model) - const isObject = model.type === 'any'; - const isDictionary = model.export === 'dictionary'; - const isEmpty = isObject && !hasProperties && !hasEnums && !hasLink; - return !isEmpty || isDictionary; - }) + .map(def => getModel(openApi, def, undefined, undefined, definition)) .forEach(model => { composition.imports.push(...model.imports); composition.enums.push(...model.enums); diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index 281363bd5..d86e8a7ac 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -3692,6 +3692,7 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { _default } from './models/_default'; export type { AnyOfAnyAndNull } from './models/AnyOfAnyAndNull'; export type { AnyOfArrays } from './models/AnyOfArrays'; +export type { ArrayWithAnyOfProperties } from './models/ArrayWithAnyOfProperties'; export type { ArrayWithArray } from './models/ArrayWithArray'; export type { ArrayWithBooleans } from './models/ArrayWithBooleans'; export type { ArrayWithNumbers } from './models/ArrayWithNumbers'; @@ -3769,6 +3770,7 @@ export type { SimpleStringWithPattern } from './models/SimpleStringWithPattern'; export { $_default } from './schemas/$_default'; export { $AnyOfAnyAndNull } from './schemas/$AnyOfAnyAndNull'; export { $AnyOfArrays } from './schemas/$AnyOfArrays'; +export { $ArrayWithAnyOfProperties } from './schemas/$ArrayWithAnyOfProperties'; export { $ArrayWithArray } from './schemas/$ArrayWithArray'; export { $ArrayWithBooleans } from './schemas/$ArrayWithBooleans'; export { $ArrayWithNumbers } from './schemas/$ArrayWithNumbers'; @@ -3885,7 +3887,7 @@ exports[`v3 should generate: test/generated/v3/models/AnyOfAnyAndNull.ts 1`] = ` /* tslint:disable */ /* eslint-disable */ export type AnyOfAnyAndNull = { - data?: any | null; + data?: (any | null); }; " @@ -3910,6 +3912,22 @@ export type AnyOfArrays = { " `; +exports[`v3 should generate: test/generated/v3/models/ArrayWithAnyOfProperties.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<({ + foo?: string; +} | { + bar?: string; +})>; +" +`; + exports[`v3 should generate: test/generated/v3/models/ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5118,6 +5136,9 @@ export const $AnyOfAnyAndNull = { data: { type: 'any-of', contains: [{ + properties: { + }, + }, { type: 'null', }], }, @@ -5135,26 +5156,56 @@ export const $AnyOfArrays = { description: \`This is a simple array with any of properties\`, properties: { results: { - type: 'any-of', - contains: [{ - properties: { - foo: { - type: 'string', + type: 'array', + contains: { + type: 'any-of', + contains: [{ + properties: { + foo: { + type: 'string', + }, }, - }, - }, { - properties: { - bar: { - type: 'string', + }, { + properties: { + bar: { + type: 'string', + }, }, - }, - }], + }], + }, }, }, } as const; " `; +exports[`v3 should generate: test/generated/v3/schemas/$ArrayWithAnyOfProperties.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithAnyOfProperties = { + type: 'array', + contains: { + type: 'any-of', + contains: [{ + properties: { + foo: { + type: 'string', + }, + }, + }, { + properties: { + bar: { + type: 'string', + }, + }, + }], + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ diff --git a/test/spec/v3.json b/test/spec/v3.json index 95c98243d..c901a6526 100644 --- a/test/spec/v3.json +++ b/test/spec/v3.json @@ -1683,6 +1683,72 @@ } } }, + "ArrayWithAnyOfProperties": { + "description": "This is a simple array with any of properties", + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + } + } + ] + } + }, + "AnyOfAnyAndNull": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + } + }, + "AnyOfArrays": { + "description": "This is a simple array with any of properties", + "type": "object", + "properties": { + "results": { + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + } + } + ] + }, + "type": "array" + } + } + }, "DictionaryWithString": { "description": "This is a string dictionary", "type": "object", @@ -2327,49 +2393,6 @@ } } }, - "AnyOfAnyAndNull": { - "type": "object", - "properties": { - "data": { - "anyOf": [ - {}, - { - "type": "null" - } - ] - } - } - }, - "AnyOfArrays": { - "description": "This is a simple array with any of properties", - "type": "object", - "properties": { - "results": { - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "foo": { - "type": "string" - } - } - }, - { - "type": "object", - "properties": { - "bar": { - "type": "string" - } - } - } - ] - }, - "type": "array", - "title": "Results" - } - } - }, "CompositionBaseModel": { "description": "This is a base model with two simple optional properties", "type": "object",