From 918d4844d9cbbda870683684edccc478252b6ef9 Mon Sep 17 00:00:00 2001 From: Ferdi Koomen Date: Tue, 26 Oct 2021 18:31:38 +0200 Subject: [PATCH] - Fixed #868 where (newly) required properties from base models would not get specified --- .../v2/parser/getEnumFromDescription.ts | 3 ++ src/openApi/v2/parser/getModel.ts | 14 ++++---- src/openApi/v2/parser/getModelComposition.ts | 32 +++++++++++++++---- .../getRequiredPropertiesFromComposition.ts | 28 ++++++++++++++++ .../v3/parser/getEnumFromDescription.ts | 3 ++ src/openApi/v3/parser/getModel.ts | 14 ++++---- src/openApi/v3/parser/getModelComposition.ts | 31 ++++++++++++++---- .../getRequiredPropertiesFromComposition.ts | 28 ++++++++++++++++ src/openApi/v3/parser/getType.ts | 1 + test/__snapshots__/index.spec.js.snap | 20 ++++++++++-- 10 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts create mode 100644 src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts diff --git a/src/openApi/v2/parser/getEnumFromDescription.ts b/src/openApi/v2/parser/getEnumFromDescription.ts index ed33e1a2..d1154e6e 100644 --- a/src/openApi/v2/parser/getEnumFromDescription.ts +++ b/src/openApi/v2/parser/getEnumFromDescription.ts @@ -1,5 +1,8 @@ import type { Enum } from '../../../client/interfaces/Enum'; +/** + * @deprecated + */ export function getEnumFromDescription(description: string): Enum[] { // Check if we can find this special format string: // None=0,Something=1,AnotherThing=2 diff --git a/src/openApi/v2/parser/getModel.ts b/src/openApi/v2/parser/getModel.ts index 5c3c5ac8..dd341e38 100644 --- a/src/openApi/v2/parser/getModel.ts +++ b/src/openApi/v2/parser/getModel.ts @@ -133,13 +133,13 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti model.base = 'any'; if (definition.properties) { - const properties = getModelProperties(openApi, definition, getModel); - properties.forEach(property => { - model.imports.push(...property.imports); - model.enums.push(...property.enums); - model.properties.push(property); - if (property.export === 'enum') { - model.enums.push(property); + const modelProperties = getModelProperties(openApi, definition, getModel); + modelProperties.forEach(modelProperty => { + model.imports.push(...modelProperty.imports); + model.enums.push(...modelProperty.enums); + model.properties.push(modelProperty); + if (modelProperty.export === 'enum') { + model.enums.push(modelProperty); } }); } diff --git a/src/openApi/v2/parser/getModelComposition.ts b/src/openApi/v2/parser/getModelComposition.ts index 493ce272..f506eb31 100644 --- a/src/openApi/v2/parser/getModelComposition.ts +++ b/src/openApi/v2/parser/getModelComposition.ts @@ -1,8 +1,10 @@ +import type { Model } from '../../../client/interfaces/Model'; import type { ModelComposition } from '../../../client/interfaces/ModelComposition'; import type { OpenApi } from '../interfaces/OpenApi'; import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; import type { getModel } from './getModel'; import { getModelProperties } from './getModelProperties'; +import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFromComposition'; // Fix for circular dependency export type GetModelFn = typeof getModel; @@ -15,8 +17,10 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, properties: [], }; - const models = definitions.map(definition => getModel(openApi, definition)); - models + const properties: Model[] = []; + + definitions + .map(definition => getModel(openApi, definition)) .filter(model => { const hasProperties = model.properties.length; const hasEnums = model.enums.length; @@ -30,12 +34,25 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, composition.properties.push(model); }); - if (definition.properties) { - const properties = getModelProperties(openApi, definition, getModel); - properties.forEach(property => { - composition.imports.push(...property.imports); - composition.enums.push(...property.enums); + if (definition.required) { + const requiredProperties = getRequiredPropertiesFromComposition(openApi, definition.required, definitions, getModel); + requiredProperties.forEach(requiredProperty => { + composition.imports.push(...requiredProperty.imports); + composition.enums.push(...requiredProperty.enums); }); + properties.push(...requiredProperties); + } + + if (definition.properties) { + const modelProperties = getModelProperties(openApi, definition, getModel); + modelProperties.forEach(modelProperty => { + composition.imports.push(...modelProperty.imports); + composition.enums.push(...modelProperty.enums); + }); + properties.push(...modelProperties); + } + + if (properties) { composition.properties.push({ name: 'properties', export: 'interface', @@ -54,5 +71,6 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, properties, }); } + return composition; } diff --git a/src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts b/src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts new file mode 100644 index 00000000..25672cc5 --- /dev/null +++ b/src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts @@ -0,0 +1,28 @@ +import type { Model } from '../../../client/interfaces/Model'; +import type { OpenApi } from '../interfaces/OpenApi'; +import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; +import type { getModel } from './getModel'; +import { getRef } from './getRef'; + +// Fix for circular dependency +export type GetModelFn = typeof getModel; + +export function getRequiredPropertiesFromComposition(openApi: OpenApi, required: string[], definitions: OpenApiSchema[], getModel: GetModelFn): Model[] { + return definitions + .reduce((properties, definition) => { + if (definition.$ref) { + const schema = getRef(openApi, definition); + return [...properties, ...getModel(openApi, schema).properties]; + } + return [...properties, ...getModel(openApi, definition).properties]; + }, [] as Model[]) + .filter(property => { + return !property.isRequired && required.includes(property.name); + }) + .map(property => { + return { + ...property, + isRequired: true, + }; + }); +} diff --git a/src/openApi/v3/parser/getEnumFromDescription.ts b/src/openApi/v3/parser/getEnumFromDescription.ts index ed33e1a2..d1154e6e 100644 --- a/src/openApi/v3/parser/getEnumFromDescription.ts +++ b/src/openApi/v3/parser/getEnumFromDescription.ts @@ -1,5 +1,8 @@ import type { Enum } from '../../../client/interfaces/Enum'; +/** + * @deprecated + */ export function getEnumFromDescription(description: string): Enum[] { // Check if we can find this special format string: // None=0,Something=1,AnotherThing=2 diff --git a/src/openApi/v3/parser/getModel.ts b/src/openApi/v3/parser/getModel.ts index cac718ce..b5be173a 100644 --- a/src/openApi/v3/parser/getModel.ts +++ b/src/openApi/v3/parser/getModel.ts @@ -160,13 +160,13 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti model.default = getModelDefault(definition, model); if (definition.properties) { - const properties = getModelProperties(openApi, definition, getModel); - properties.forEach(property => { - model.imports.push(...property.imports); - model.enums.push(...property.enums); - model.properties.push(property); - if (property.export === 'enum') { - model.enums.push(property); + const modelProperties = getModelProperties(openApi, definition, getModel); + modelProperties.forEach(modelProperty => { + model.imports.push(...modelProperty.imports); + model.enums.push(...modelProperty.enums); + model.properties.push(modelProperty); + if (modelProperty.export === 'enum') { + model.enums.push(modelProperty); } }); } diff --git a/src/openApi/v3/parser/getModelComposition.ts b/src/openApi/v3/parser/getModelComposition.ts index 72c30329..3c0f497f 100644 --- a/src/openApi/v3/parser/getModelComposition.ts +++ b/src/openApi/v3/parser/getModelComposition.ts @@ -1,8 +1,10 @@ +import type { Model } from '../../../client/interfaces/Model'; import type { ModelComposition } from '../../../client/interfaces/ModelComposition'; import type { OpenApi } from '../interfaces/OpenApi'; import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; import type { getModel } from './getModel'; import { getModelProperties } from './getModelProperties'; +import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFromComposition'; // Fix for circular dependency export type GetModelFn = typeof getModel; @@ -15,8 +17,10 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, properties: [], }; - const models = definitions.map(definition => getModel(openApi, definition)); - models + const properties: Model[] = []; + + definitions + .map(definition => getModel(openApi, definition)) .filter(model => { const hasProperties = model.properties.length; const hasEnums = model.enums.length; @@ -30,12 +34,25 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, composition.properties.push(model); }); - if (definition.properties) { - const properties = getModelProperties(openApi, definition, getModel); - properties.forEach(property => { - composition.imports.push(...property.imports); - composition.enums.push(...property.enums); + if (definition.required) { + const requiredProperties = getRequiredPropertiesFromComposition(openApi, definition.required, definitions, getModel); + requiredProperties.forEach(requiredProperty => { + composition.imports.push(...requiredProperty.imports); + composition.enums.push(...requiredProperty.enums); }); + properties.push(...requiredProperties); + } + + if (definition.properties) { + const modelProperties = getModelProperties(openApi, definition, getModel); + modelProperties.forEach(modelProperty => { + composition.imports.push(...modelProperty.imports); + composition.enums.push(...modelProperty.enums); + }); + properties.push(...modelProperties); + } + + if (properties.length) { composition.properties.push({ name: 'properties', export: 'interface', diff --git a/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts b/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts new file mode 100644 index 00000000..25672cc5 --- /dev/null +++ b/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts @@ -0,0 +1,28 @@ +import type { Model } from '../../../client/interfaces/Model'; +import type { OpenApi } from '../interfaces/OpenApi'; +import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; +import type { getModel } from './getModel'; +import { getRef } from './getRef'; + +// Fix for circular dependency +export type GetModelFn = typeof getModel; + +export function getRequiredPropertiesFromComposition(openApi: OpenApi, required: string[], definitions: OpenApiSchema[], getModel: GetModelFn): Model[] { + return definitions + .reduce((properties, definition) => { + if (definition.$ref) { + const schema = getRef(openApi, definition); + return [...properties, ...getModel(openApi, schema).properties]; + } + return [...properties, ...getModel(openApi, definition).properties]; + }, [] as Model[]) + .filter(property => { + return !property.isRequired && required.includes(property.name); + }) + .map(property => { + return { + ...property, + isRequired: true, + }; + }); +} diff --git a/src/openApi/v3/parser/getType.ts b/src/openApi/v3/parser/getType.ts index e1764153..e2a87748 100644 --- a/src/openApi/v3/parser/getType.ts +++ b/src/openApi/v3/parser/getType.ts @@ -5,6 +5,7 @@ import { stripNamespace } from './stripNamespace'; function encode(value: string): string { return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_'); } + /** * Parse any string value into a type object. * @param values String or String[] value like "integer", "Link[Model]" or ["string", "null"] diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index f46f87e5..8c196b29 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -811,7 +811,7 @@ import type { ModelWithString } from './ModelWithString'; export type ModelThatExtends = (ModelWithString & { propExtendsA?: string; propExtendsB?: ModelWithString; -}); +} & any); " `; @@ -829,7 +829,7 @@ import type { ModelWithString } from './ModelWithString'; export type ModelThatExtendsExtends = (ModelWithString & ModelThatExtends & { propExtendsC?: string; propExtendsD?: ModelWithString; -}); +} & any); " `; @@ -1478,6 +1478,9 @@ export const $ModelThatExtends = { type: 'ModelWithString', }, }, + }, { + properties: { + }, }], };" `; @@ -1501,6 +1504,9 @@ export const $ModelThatExtendsExtends = { type: 'ModelWithString', }, }, + }, { + properties: { + }, }], };" `; @@ -3201,6 +3207,8 @@ import type { CompositionBaseModel } from './CompositionBaseModel'; * This is a model that extends the base model */ export type CompositionExtendedModel = (CompositionBaseModel & { + firstName: string; + lastname: string; age: number; }); " @@ -4055,6 +4063,14 @@ export const $CompositionExtendedModel = { type: 'CompositionBaseModel', }, { properties: { + firstName: { + type: 'string', + isRequired: true, + }, + lastname: { + type: 'string', + isRequired: true, + }, age: { type: 'number', isRequired: true,