fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #574 from hey-api/fix/maximum-call-stack-exceeded

chore: pass types across parser

authored by

Lubos and committed by
GitHub
94ef0155 56e31bf0

+604 -300
+3 -3
packages/openapi-ts/src/openApi/common/parser/getDefault.ts
··· 30 30 return value; 31 31 } 32 32 return definition.default; 33 - case 'string': 34 - return definition.default; 35 33 case 'array': 36 34 case 'boolean': 37 35 case 'object': 36 + case 'string': 38 37 return definition.default; 38 + default: 39 + return undefined; 39 40 } 40 - return undefined; 41 41 };
+1 -4
packages/openapi-ts/src/openApi/common/parser/getRef.ts
··· 12 12 if (item.$ref) { 13 13 // Fetch the paths to the definitions, this converts: 14 14 // "#/components/schemas/Form" to ["components", "schemas", "Form"] 15 - const paths = item.$ref 16 - .replace(/^#/g, '') 17 - .split('/') 18 - .filter((item) => item); 15 + const paths = item.$ref.replace(/^#/g, '').split('/').filter(Boolean); 19 16 20 17 // Try to find the reference by walking down the path, 21 18 // if we cannot find it, then we throw an error.
+3 -3
packages/openapi-ts/src/openApi/v2/index.ts
··· 13 13 export const parse = (openApi: OpenApi): Client => { 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 - const models = getModels(openApi); 17 - const services = getServices(openApi); 16 + const { models, types } = getModels(openApi); 17 + const services = getServices({ openApi, types }); 18 18 19 19 return { 20 20 models, 21 21 server, 22 22 services, 23 - types: {}, 23 + types, 24 24 version, 25 25 }; 26 26 };
+13
packages/openapi-ts/src/openApi/v2/interfaces/Model.ts
··· 1 + import type { Client } from '../../../types/client'; 2 + import type { Model, ModelMeta } from '../../common/interfaces/client'; 3 + import type { OpenApi } from './OpenApi'; 4 + import type { OpenApiSchema } from './OpenApiSchema'; 5 + 6 + export type GetModelFn = ( 7 + args: Pick<Client, 'types'> & { 8 + definition: OpenApiSchema; 9 + isDefinition?: boolean; 10 + meta?: ModelMeta; 11 + openApi: OpenApi; 12 + }, 13 + ) => Model;
+18 -15
packages/openapi-ts/src/openApi/v2/parser/__tests__/getServices.spec.ts
··· 23 23 }); 24 24 25 25 const services = getServices({ 26 - info: { 27 - title: 'x', 28 - version: '1', 29 - }, 30 - paths: { 31 - '/api/trips': { 32 - get: { 33 - responses: { 34 - 200: { 35 - description: 'x', 26 + openApi: { 27 + info: { 28 + title: 'x', 29 + version: '1', 30 + }, 31 + paths: { 32 + '/api/trips': { 33 + get: { 34 + responses: { 35 + 200: { 36 + description: 'x', 37 + }, 38 + default: { 39 + description: 'default', 40 + }, 36 41 }, 37 - default: { 38 - description: 'default', 39 - }, 42 + tags: [], 40 43 }, 41 - tags: [], 42 44 }, 43 45 }, 46 + swagger: '2.0', 44 47 }, 45 - swagger: '2.0', 48 + types: {}, 46 49 }); 47 50 48 51 expect(services).toHaveLength(1);
+21 -8
packages/openapi-ts/src/openApi/v2/parser/getModel.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { Model, ModelMeta } from '../../common/interfaces/client'; 2 3 import { getEnums } from '../../common/parser/getEnums'; 3 4 import { getPattern } from '../../common/parser/getPattern'; ··· 12 13 isDefinition = false, 13 14 meta, 14 15 openApi, 15 - }: { 16 + types, 17 + }: Pick<Client, 'types'> & { 16 18 definition: OpenApiSchema; 17 19 isDefinition?: boolean; 18 20 meta?: ModelMeta; ··· 83 85 model.imports.push(...arrayItems.imports); 84 86 return model; 85 87 } else { 86 - const arrayItems = getModel({ definition: definition.items, openApi }); 88 + const arrayItems = getModel({ 89 + definition: definition.items, 90 + openApi, 91 + types, 92 + }); 87 93 model.export = 'array'; 88 94 model.type = arrayItems.type; 89 95 model.base = arrayItems.base; ··· 112 118 const additionalProperties = getModel({ 113 119 definition: definition.additionalProperties, 114 120 openApi, 121 + types, 115 122 }); 116 123 model.export = 'dictionary'; 117 124 model.type = additionalProperties.type; ··· 124 131 } 125 132 126 133 if (definition.allOf?.length) { 127 - const composition = getModelComposition( 128 - openApi, 134 + const composition = getModelComposition({ 129 135 definition, 130 - definition.allOf, 131 - 'all-of', 136 + definitions: definition.allOf, 132 137 getModel, 133 - ); 138 + openApi, 139 + type: 'all-of', 140 + types, 141 + }); 134 142 model.export = composition.export; 135 143 model.imports.push(...composition.imports); 136 144 model.properties.push(...composition.properties); ··· 144 152 model.base = 'unknown'; 145 153 146 154 if (definition.properties) { 147 - const modelProperties = getModelProperties(openApi, definition, getModel); 155 + const modelProperties = getModelProperties({ 156 + definition, 157 + getModel, 158 + openApi, 159 + types, 160 + }); 148 161 modelProperties.forEach((modelProperty) => { 149 162 model.imports.push(...modelProperty.imports); 150 163 model.enums = [...model.enums, ...modelProperty.enums];
+29 -17
packages/openapi-ts/src/openApi/v2/parser/getModelComposition.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { Model, ModelComposition } from '../../common/interfaces/client'; 3 + import type { GetModelFn } from '../interfaces/Model'; 2 4 import type { OpenApi } from '../interfaces/OpenApi'; 3 5 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 - import type { getModel } from './getModel'; 5 6 import { getModelProperties } from './getModelProperties'; 6 7 import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFromComposition'; 7 8 8 - // Fix for circular dependency 9 - export type GetModelFn = typeof getModel; 10 - 11 - export const getModelComposition = ( 12 - openApi: OpenApi, 13 - definition: OpenApiSchema, 14 - definitions: OpenApiSchema[], 15 - type: 'one-of' | 'any-of' | 'all-of', 16 - getModel: GetModelFn, 17 - ): ModelComposition => { 9 + export const getModelComposition = ({ 10 + definition, 11 + definitions, 12 + getModel, 13 + openApi, 14 + type, 15 + types, 16 + }: { 17 + openApi: OpenApi; 18 + definition: OpenApiSchema; 19 + definitions: OpenApiSchema[]; 20 + type: 'one-of' | 'any-of' | 'all-of'; 21 + getModel: GetModelFn; 22 + types: Client['types']; 23 + }): ModelComposition => { 18 24 const composition: ModelComposition = { 19 25 $refs: [], 20 26 enums: [], ··· 26 32 const properties: Model[] = []; 27 33 28 34 definitions 29 - .map((definition) => getModel({ definition, openApi })) 35 + .map((definition) => getModel({ definition, openApi, types })) 30 36 .filter((model) => { 31 37 const hasProperties = model.properties.length; 32 38 const hasEnums = model.enums.length; ··· 41 47 }); 42 48 43 49 if (definition.required) { 44 - const requiredProperties = getRequiredPropertiesFromComposition( 45 - openApi, 46 - definition.required, 50 + const requiredProperties = getRequiredPropertiesFromComposition({ 47 51 definitions, 48 52 getModel, 49 - ); 53 + openApi, 54 + required: definition.required, 55 + types, 56 + }); 50 57 requiredProperties.forEach((requiredProperty) => { 51 58 composition.imports.push(...requiredProperty.imports); 52 59 composition.enums.push(...requiredProperty.enums); ··· 55 62 } 56 63 57 64 if (definition.properties) { 58 - const modelProperties = getModelProperties(openApi, definition, getModel); 65 + const modelProperties = getModelProperties({ 66 + definition, 67 + getModel, 68 + openApi, 69 + types, 70 + }); 59 71 modelProperties.forEach((modelProperty) => { 60 72 composition.imports.push(...modelProperty.imports); 61 73 composition.enums.push(...modelProperty.enums);
+17 -11
packages/openapi-ts/src/openApi/v2/parser/getModelProperties.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { escapeName } from '../../../utils/escape'; 2 3 import type { Model } from '../../common/interfaces/client'; 3 4 import { getPattern } from '../../common/parser/getPattern'; 4 5 import { getType } from '../../common/parser/type'; 6 + import type { GetModelFn } from '../interfaces/Model'; 5 7 import type { OpenApi } from '../interfaces/OpenApi'; 6 8 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 7 - import type { getModel } from './getModel'; 8 9 9 - // Fix for circular dependency 10 - export type GetModelFn = typeof getModel; 11 - 12 - export const getModelProperties = ( 13 - openApi: OpenApi, 14 - definition: OpenApiSchema, 15 - getModel: GetModelFn, 16 - ): Model[] => { 10 + export const getModelProperties = ({ 11 + definition, 12 + getModel, 13 + openApi, 14 + types, 15 + }: { 16 + definition: OpenApiSchema; 17 + getModel: GetModelFn; 18 + openApi: OpenApi; 19 + types: Client['types']; 20 + }): Model[] => { 17 21 const models: Model[] = []; 18 22 19 23 Object.entries(definition.properties ?? {}).forEach( 20 24 ([propertyName, property]) => { 21 - const propertyRequired = !!definition.required?.includes(propertyName); 25 + const propertyRequired = Boolean( 26 + definition.required?.includes(propertyName), 27 + ); 22 28 if (property.$ref) { 23 29 const model = getType({ type: property.$ref }); 24 30 models.push({ ··· 54 60 uniqueItems: property.uniqueItems, 55 61 }); 56 62 } else { 57 - const model = getModel({ definition: property, openApi }); 63 + const model = getModel({ definition: property, openApi, types }); 58 64 models.push({ 59 65 $refs: [], 60 66 base: model.base,
+18 -8
packages/openapi-ts/src/openApi/v2/parser/getModels.ts
··· 1 - import type { Model } from '../../common/interfaces/client'; 1 + import type { Client } from '../../../types/client'; 2 2 import { reservedWords } from '../../common/parser/reservedWords'; 3 3 import { getType } from '../../common/parser/type'; 4 4 import type { OpenApi } from '../interfaces/OpenApi'; 5 5 import { getModel } from './getModel'; 6 6 7 - export const getModels = (openApi: OpenApi): Model[] => { 8 - let models: Model[] = []; 7 + export const getModels = ( 8 + openApi: OpenApi, 9 + ): Pick<Client, 'models' | 'types'> => { 10 + const types: Client['types'] = {}; 11 + let models: Client['models'] = []; 9 12 10 13 Object.entries(openApi.definitions ?? {}).forEach( 11 14 ([definitionName, definition]) => { 12 15 const definitionType = getType({ type: definitionName }); 16 + const name = definitionType.base.replace(reservedWords, '_$1'); 17 + const meta = { 18 + $ref: `#/definitions/${definitionName}`, 19 + name, 20 + }; 21 + types[name] = meta; 13 22 const model = getModel({ 14 23 definition, 15 24 isDefinition: true, 16 - meta: { 17 - $ref: `#/definitions/${definitionName}`, 18 - name: definitionType.base.replace(reservedWords, '_$1'), 19 - }, 25 + meta, 20 26 openApi, 27 + types, 21 28 }); 22 29 models = [...models, model]; 23 30 }, 24 31 ); 25 32 26 - return models; 33 + return { 34 + models, 35 + types, 36 + }; 27 37 };
+28 -10
packages/openapi-ts/src/openApi/v2/parser/getOperation.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { getOperationResults } from '../../../utils/operation'; 2 3 import type { 3 4 Operation, ··· 15 16 import { getOperationParameters } from './getOperationParameters'; 16 17 import { getOperationResponses } from './getOperationResponses'; 17 18 18 - export const getOperation = ( 19 - openApi: OpenApi, 20 - url: string, 21 - method: Lowercase<Operation['method']>, 22 - tag: string, 23 - op: OpenApiOperation, 24 - pathParams: OperationParameters, 25 - ): Operation => { 19 + export const getOperation = ({ 20 + method, 21 + op, 22 + openApi, 23 + pathParams, 24 + tag, 25 + types, 26 + url, 27 + }: { 28 + openApi: OpenApi; 29 + url: string; 30 + method: Lowercase<Operation['method']>; 31 + tag: string; 32 + op: OpenApiOperation; 33 + pathParams: OperationParameters; 34 + types: Client['types']; 35 + }): Operation => { 26 36 const serviceName = getServiceName(tag); 27 37 const name = getOperationName(url, method, op.operationId); 28 38 ··· 51 61 52 62 // Parse the operation parameters (path, query, body, etc). 53 63 if (op.parameters) { 54 - const parameters = getOperationParameters(openApi, op.parameters); 64 + const parameters = getOperationParameters({ 65 + openApi, 66 + parameters: op.parameters, 67 + types, 68 + }); 55 69 operation.imports.push(...parameters.imports); 56 70 operation.parameters.push(...parameters.parameters); 57 71 operation.parametersPath.push(...parameters.parametersPath); ··· 64 78 65 79 // Parse the operation responses. 66 80 if (op.responses) { 67 - const operationResponses = getOperationResponses(openApi, op.responses); 81 + const operationResponses = getOperationResponses({ 82 + openApi, 83 + responses: op.responses, 84 + types, 85 + }); 68 86 const operationResults = getOperationResults(operationResponses); 69 87 operation.errors = getOperationErrors(operationResponses); 70 88 operation.responseHeader = getOperationResponseHeader(operationResults);
+11 -5
packages/openapi-ts/src/openApi/v2/parser/getOperationParameter.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationParameter } from '../../common/interfaces/client'; 2 3 import { getDefault } from '../../common/parser/getDefault'; 3 4 import { getEnums } from '../../common/parser/getEnums'; ··· 10 11 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 11 12 import { getModel } from './getModel'; 12 13 13 - export const getOperationParameter = ( 14 - openApi: OpenApi, 15 - parameter: OpenApiParameter, 16 - ): OperationParameter => { 14 + export const getOperationParameter = ({ 15 + openApi, 16 + parameter, 17 + types, 18 + }: { 19 + openApi: OpenApi; 20 + parameter: OpenApiParameter; 21 + types: Client['types']; 22 + }): OperationParameter => { 17 23 const operationParameter: OperationParameter = { 18 24 $refs: [], 19 25 base: 'unknown', ··· 114 120 operationParameter.default = getDefault(parameter, operationParameter); 115 121 return operationParameter; 116 122 } else { 117 - const model = getModel({ definition: schema, openApi }); 123 + const model = getModel({ definition: schema, openApi, types }); 118 124 operationParameter.export = model.export; 119 125 operationParameter.type = model.type; 120 126 operationParameter.base = model.base;
+15 -5
packages/openapi-ts/src/openApi/v2/parser/getOperationParameters.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationParameters } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 3 4 import type { OpenApi } from '../interfaces/OpenApi'; 4 5 import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 5 6 import { getOperationParameter } from './getOperationParameter'; 6 7 7 - export const getOperationParameters = ( 8 - openApi: OpenApi, 9 - parameters: OpenApiParameter[], 10 - ): OperationParameters => { 8 + export const getOperationParameters = ({ 9 + openApi, 10 + parameters, 11 + types, 12 + }: { 13 + openApi: OpenApi; 14 + parameters: OpenApiParameter[]; 15 + types: Client['types']; 16 + }): OperationParameters => { 11 17 const operationParameters: OperationParameters = { 12 18 $refs: [], 13 19 imports: [], ··· 26 32 openApi, 27 33 parameterOrReference, 28 34 ); 29 - const parameter = getOperationParameter(openApi, parameterDef); 35 + const parameter = getOperationParameter({ 36 + openApi, 37 + parameter: parameterDef, 38 + types, 39 + }); 30 40 31 41 // We ignore the "api-version" param, since we do not want to add this 32 42 // as the first / default parameter for each of the service calls.
+13 -6
packages/openapi-ts/src/openApi/v2/parser/getOperationResponse.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationResponse } from '../../common/interfaces/client'; 2 3 import { getPattern } from '../../common/parser/getPattern'; 3 4 import { getRef } from '../../common/parser/getRef'; ··· 7 8 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 8 9 import { getModel } from './getModel'; 9 10 10 - export const getOperationResponse = ( 11 - openApi: OpenApi, 12 - response: OpenApiResponse, 13 - code: number | 'default', 14 - ): OperationResponse => { 11 + export const getOperationResponse = ({ 12 + code, 13 + openApi, 14 + response, 15 + types, 16 + }: { 17 + openApi: OpenApi; 18 + response: OpenApiResponse; 19 + code: number | 'default'; 20 + types: Client['types']; 21 + }): OperationResponse => { 15 22 const operationResponse: OperationResponse = { 16 23 $refs: [], 17 24 base: code !== 204 ? 'unknown' : 'void', ··· 51 58 operationResponse.imports.push(...model.imports); 52 59 return operationResponse; 53 60 } else { 54 - const model = getModel({ definition: schema, openApi }); 61 + const model = getModel({ definition: schema, openApi, types }); 55 62 operationResponse.export = model.export; 56 63 operationResponse.type = model.type; 57 64 operationResponse.base = model.base;
+14 -7
packages/openapi-ts/src/openApi/v2/parser/getOperationResponses.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationResponse } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 3 4 import { getOperationResponseCode } from '../../common/parser/operation'; ··· 6 7 import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 7 8 import { getOperationResponse } from './getOperationResponse'; 8 9 9 - export const getOperationResponses = ( 10 - openApi: OpenApi, 11 - responses: OpenApiResponses, 12 - ): OperationResponse[] => { 10 + export const getOperationResponses = ({ 11 + openApi, 12 + responses, 13 + types, 14 + }: { 15 + openApi: OpenApi; 16 + responses: OpenApiResponses; 17 + types: Client['types']; 18 + }): OperationResponse[] => { 13 19 const operationResponses: OperationResponse[] = []; 14 20 15 21 // Iterate over each response code and get the ··· 19 25 const responseCode = getOperationResponseCode(code); 20 26 21 27 if (responseCode) { 22 - const operationResponse = getOperationResponse( 28 + const operationResponse = getOperationResponse({ 29 + code: responseCode, 23 30 openApi, 24 31 response, 25 - responseCode, 26 - ); 32 + types, 33 + }); 27 34 operationResponses.push(operationResponse); 28 35 } 29 36 });
+20 -12
packages/openapi-ts/src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { Model } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 4 + import type { GetModelFn } from '../interfaces/Model'; 3 5 import type { OpenApi } from '../interfaces/OpenApi'; 4 6 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 5 - import type { getModel } from './getModel'; 6 7 7 - // Fix for circular dependency 8 - export type GetModelFn = typeof getModel; 9 - 10 - export const getRequiredPropertiesFromComposition = ( 11 - openApi: OpenApi, 12 - required: string[], 13 - definitions: OpenApiSchema[], 14 - getModel: GetModelFn, 15 - ): Model[] => 8 + export const getRequiredPropertiesFromComposition = ({ 9 + definitions, 10 + getModel, 11 + openApi, 12 + required, 13 + types, 14 + }: { 15 + openApi: OpenApi; 16 + required: string[]; 17 + definitions: OpenApiSchema[]; 18 + getModel: GetModelFn; 19 + types: Client['types']; 20 + }): Model[] => 16 21 definitions 17 22 .reduce((properties, definition) => { 18 23 if (definition.$ref) { 19 24 const schema = getRef<OpenApiSchema>(openApi, definition); 20 25 return [ 21 26 ...properties, 22 - ...getModel({ definition: schema, openApi }).properties, 27 + ...getModel({ definition: schema, openApi, types }).properties, 23 28 ]; 24 29 } 25 - return [...properties, ...getModel({ definition, openApi }).properties]; 30 + return [ 31 + ...properties, 32 + ...getModel({ definition, openApi, types }).properties, 33 + ]; 26 34 }, [] as Model[]) 27 35 .filter( 28 36 (property) => !property.isRequired && required.includes(property.name),
+19 -7
packages/openapi-ts/src/openApi/v2/parser/getServices.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { unique } from '../../../utils/unique'; 2 3 import type { Service } from '../../common/interfaces/client'; 3 4 import type { OpenApi } from '../interfaces/OpenApi'; ··· 7 8 /** 8 9 * Get the OpenAPI services 9 10 */ 10 - export const getServices = (openApi: OpenApi): Service[] => { 11 + export const getServices = ({ 12 + openApi, 13 + types, 14 + }: { 15 + openApi: OpenApi; 16 + types: Client['types']; 17 + }): Service[] => { 11 18 const services = new Map<string, Service>(); 12 19 13 20 Object.entries(openApi.paths).forEach(([url, path]) => { 14 21 // Grab path and parse any global path parameters 15 - const pathParams = getOperationParameters(openApi, path.parameters || []); 22 + const pathParams = getOperationParameters({ 23 + openApi, 24 + parameters: path.parameters || [], 25 + types, 26 + }); 16 27 17 28 Object.keys(path).forEach((method) => { 18 29 // Parse all the methods for this path ··· 28 39 const op = path[method]!; 29 40 const tags = op.tags?.length ? op.tags.filter(unique) : ['Default']; 30 41 tags.forEach((tag) => { 31 - const operation = getOperation( 32 - openApi, 33 - url, 42 + const operation = getOperation({ 34 43 method, 35 - tag, 36 44 op, 45 + openApi, 37 46 pathParams, 38 - ); 47 + tag, 48 + types, 49 + url, 50 + }); 39 51 40 52 // If we have already declared a service, then we should fetch that and 41 53 // append the new method to it. Otherwise we should create a new service object.
+3 -3
packages/openapi-ts/src/openApi/v3/index.ts
··· 13 13 export const parse = (openApi: OpenApi): Client => { 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 - const models = getModels(openApi); 17 - const services = getServices(openApi); 16 + const { models, types } = getModels(openApi); 17 + const services = getServices({ openApi, types }); 18 18 19 19 return { 20 20 models, 21 21 server, 22 22 services, 23 - types: {}, 23 + types, 24 24 version, 25 25 }; 26 26 };
+18
packages/openapi-ts/src/openApi/v3/interfaces/Model.ts
··· 1 + import type { Client } from '../../../types/client'; 2 + import type { Model, ModelMeta } from '../../common/interfaces/client'; 3 + import type { OpenApi } from './OpenApi'; 4 + import type { OpenApiSchema } from './OpenApiSchema'; 5 + 6 + export type GetModelFn = ( 7 + args: Pick<Client, 'types'> & { 8 + definition: OpenApiSchema; 9 + /** 10 + * Pass through initial model values 11 + */ 12 + initialValues?: Partial<Model>; 13 + isDefinition?: boolean; 14 + meta?: ModelMeta; 15 + openApi: OpenApi; 16 + parentDefinition?: OpenApiSchema | null; 17 + }, 18 + ) => Model;
+2
packages/openapi-ts/src/openApi/v3/parser/__tests__/getModel.spec.ts
··· 99 99 name: definitionType.base.replace(reservedWords, '_$1'), 100 100 }, 101 101 openApi, 102 + types: {}, 102 103 }); 103 104 expect(model.properties[0].properties.length).toBe(2); 104 105 }); ··· 114 115 name: definitionType.base.replace(reservedWords, '_$1'), 115 116 }, 116 117 openApi, 118 + types: {}, 117 119 }); 118 120 expect(model.properties[0].properties.length).toBe(3); 119 121 });
+18 -15
packages/openapi-ts/src/openApi/v3/parser/__tests__/getServices.spec.ts
··· 23 23 }); 24 24 25 25 const services = getServices({ 26 - info: { 27 - title: 'x', 28 - version: '1', 29 - }, 30 - openapi: '3.0.0', 31 - paths: { 32 - '/api/trips': { 33 - get: { 34 - responses: { 35 - 200: { 36 - description: 'x', 26 + openApi: { 27 + info: { 28 + title: 'x', 29 + version: '1', 30 + }, 31 + openapi: '3.0.0', 32 + paths: { 33 + '/api/trips': { 34 + get: { 35 + responses: { 36 + 200: { 37 + description: 'x', 38 + }, 39 + default: { 40 + description: 'default', 41 + }, 37 42 }, 38 - default: { 39 - description: 'default', 40 - }, 43 + tags: [], 41 44 }, 42 - tags: [], 43 45 }, 44 46 }, 45 47 }, 48 + types: {}, 46 49 }); 47 50 48 51 expect(services).toHaveLength(1);
+23 -10
packages/openapi-ts/src/openApi/v3/parser/getModel.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { enumMeta } from '../../../utils/enum'; 2 3 import type { Model, ModelMeta } from '../../common/interfaces/client'; 3 4 import { getDefault } from '../../common/parser/getDefault'; ··· 27 28 meta, 28 29 openApi, 29 30 parentDefinition = null, 30 - }: { 31 + types, 32 + }: Pick<Client, 'types'> & { 31 33 definition: OpenApiSchema; 32 34 /** 33 35 * Pass through initial model values ··· 129 131 definition: definition.items, 130 132 openApi, 131 133 parentDefinition: definition, 134 + types, 132 135 }); 133 136 } 134 137 } ··· 146 149 definition: arrayItemsDefinition, 147 150 openApi, 148 151 parentDefinition: definition, 152 + types, 149 153 }); 150 154 model.base = arrayItems.base; 151 155 model.export = 'array'; ··· 166 170 getModel, 167 171 model, 168 172 openApi, 173 + types, 169 174 }); 170 175 return { ...model, ...composition }; 171 176 } ··· 177 182 model.type = 'unknown'; 178 183 model.default = getDefault(definition, model); 179 184 180 - const modelProperties = getModelProperties( 181 - openApi, 185 + const modelProperties = getModelProperties({ 182 186 definition, 183 187 getModel, 184 - model, 185 - ); 188 + openApi, 189 + parent: model, 190 + types, 191 + }); 186 192 modelProperties.forEach((modelProperty) => { 187 193 model.$refs = [...model.$refs, ...modelProperty.$refs]; 188 194 model.enums = [...model.enums, ...modelProperty.enums]; 189 195 model.imports = [...model.imports, ...modelProperty.imports]; 190 - model.properties.push(modelProperty); 196 + model.properties = [...model.properties, modelProperty]; 191 197 if (modelProperty.export === 'enum') { 192 198 model.enums = [...model.enums, modelProperty]; 193 199 } 194 200 }); 195 201 196 202 if (definition.additionalProperties) { 197 - const modelProperty = getAdditionalPropertiesModel( 198 - openApi, 203 + const modelProperty = getAdditionalPropertiesModel({ 199 204 definition, 200 205 getModel, 201 206 model, 202 - ); 207 + openApi, 208 + types, 209 + }); 203 210 model.properties.push(modelProperty); 204 211 } 205 212 206 213 return model; 207 214 } 208 215 209 - return getAdditionalPropertiesModel(openApi, definition, getModel, model); 216 + return getAdditionalPropertiesModel({ 217 + definition, 218 + getModel, 219 + model, 220 + openApi, 221 + types, 222 + }); 210 223 } 211 224 212 225 if (definition.const !== undefined) {
+57 -43
packages/openapi-ts/src/openApi/v3/parser/getModelComposition.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { Model, ModelComposition } from '../../common/interfaces/client'; 3 + import type { GetModelFn } from '../interfaces/Model'; 2 4 import type { OpenApi } from '../interfaces/OpenApi'; 3 5 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 - import type { getModel } from './getModel'; 5 6 import { getModelProperties } from './getModelProperties'; 6 7 import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFromComposition'; 7 - 8 - // Fix for circular dependency 9 - export type GetModelFn = typeof getModel; 10 8 11 9 type Composition = { 12 10 definitions: OpenApiSchema[]; ··· 45 43 model, 46 44 openApi, 47 45 type, 48 - }: Composition & { 49 - definition: OpenApiSchema; 50 - getModel: GetModelFn; 51 - model: Model; 52 - openApi: OpenApi; 53 - }): ModelComposition => { 46 + types, 47 + }: Composition & 48 + Pick<Client, 'types'> & { 49 + definition: OpenApiSchema; 50 + getModel: GetModelFn; 51 + model: Model; 52 + openApi: OpenApi; 53 + }): ModelComposition => { 54 54 const composition: ModelComposition = { 55 55 $refs: model.$refs, 56 56 enums: model.enums, ··· 59 59 properties: model.properties, 60 60 }; 61 61 62 - const properties: Model[] = []; 62 + let properties: Model[] = []; 63 63 64 64 definitions 65 65 .map((def) => 66 - getModel({ definition: def, openApi, parentDefinition: definition }), 66 + getModel({ 67 + definition: def, 68 + openApi, 69 + parentDefinition: definition, 70 + types, 71 + }), 67 72 ) 68 73 .forEach((model) => { 69 74 composition.$refs = [...composition.$refs, ...model.$refs]; 70 75 composition.imports = [...composition.imports, ...model.imports]; 71 - composition.enums.push(...model.enums); 72 - composition.properties.push(model); 76 + composition.enums = [...composition.enums, ...model.enums]; 77 + composition.properties = [...composition.properties, model]; 73 78 }); 74 79 75 80 if (definition.required) { 76 - const requiredProperties = getRequiredPropertiesFromComposition( 77 - openApi, 78 - definition.required, 81 + const requiredProperties = getRequiredPropertiesFromComposition({ 79 82 definitions, 80 83 getModel, 81 - ); 84 + openApi, 85 + required: definition.required, 86 + types, 87 + }); 82 88 requiredProperties.forEach((requiredProperty) => { 83 89 composition.$refs = [...composition.$refs, ...requiredProperty.$refs]; 84 90 composition.imports = [ 85 91 ...composition.imports, 86 92 ...requiredProperty.imports, 87 93 ]; 88 - composition.enums.push(...requiredProperty.enums); 94 + composition.enums = [...composition.enums, ...requiredProperty.enums]; 89 95 }); 90 - properties.push(...requiredProperties); 96 + properties = [...properties, ...requiredProperties]; 91 97 } 92 98 93 99 if (definition.properties) { 94 - const modelProperties = getModelProperties(openApi, definition, getModel); 100 + const modelProperties = getModelProperties({ 101 + definition, 102 + getModel, 103 + openApi, 104 + types, 105 + }); 95 106 modelProperties.forEach((modelProperty) => { 96 107 composition.$refs = [...composition.$refs, ...modelProperty.$refs]; 97 108 composition.imports = [...composition.imports, ...modelProperty.imports]; 98 - composition.enums.push(...modelProperty.enums); 109 + composition.enums = [...composition.enums, ...modelProperty.enums]; 99 110 if (modelProperty.export === 'enum') { 100 - composition.enums.push(modelProperty); 111 + composition.enums = [...composition.enums, modelProperty]; 101 112 } 102 113 }); 103 - properties.push(...modelProperties); 114 + properties = [...properties, ...modelProperties]; 104 115 } 105 116 106 117 if (properties.length) { 107 118 const foundComposition = findModelComposition(definition); 108 119 if (foundComposition?.type === 'one-of') { 109 120 composition.properties.forEach((property) => { 110 - property.properties.push(...properties); 121 + property.properties = [...property.properties, ...properties]; 111 122 }); 112 123 } else { 113 - composition.properties.push({ 114 - $refs: [], 115 - base: 'unknown', 116 - description: '', 117 - enum: [], 118 - enums: [], 119 - export: 'interface', 120 - imports: [], 121 - isDefinition: false, 122 - isNullable: false, 123 - isReadOnly: false, 124 - isRequired: false, 125 - link: null, 126 - name: 'properties', 127 - properties, 128 - template: null, 129 - type: 'unknown', 130 - }); 124 + composition.properties = [ 125 + ...composition.properties, 126 + { 127 + $refs: [], 128 + base: 'unknown', 129 + description: '', 130 + enum: [], 131 + enums: [], 132 + export: 'interface', 133 + imports: [], 134 + isDefinition: false, 135 + isNullable: false, 136 + isReadOnly: false, 137 + isRequired: false, 138 + link: null, 139 + name: 'properties', 140 + properties, 141 + template: null, 142 + type: 'unknown', 143 + }, 144 + ]; 131 145 } 132 146 } 133 147
+33 -17
packages/openapi-ts/src/openApi/v3/parser/getModelProperties.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { escapeName } from '../../../utils/escape'; 2 3 import { unique } from '../../../utils/unique'; 3 4 import type { Model } from '../../common/interfaces/client'; 4 5 import { getDefault } from '../../common/parser/getDefault'; 5 6 import { getPattern } from '../../common/parser/getPattern'; 6 7 import { getType } from '../../common/parser/type'; 8 + import type { GetModelFn } from '../interfaces/Model'; 7 9 import type { OpenApi } from '../interfaces/OpenApi'; 8 10 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 9 11 import { 10 12 findOneOfParentDiscriminator, 11 13 mapPropertyValue, 12 14 } from './discriminator'; 13 - import type { getModel } from './getModel'; 14 15 import { isDefinitionNullable } from './inferType'; 15 16 16 - // Fix for circular dependency 17 - export type GetModelFn = typeof getModel; 18 - 19 - export const getAdditionalPropertiesModel = ( 20 - openApi: OpenApi, 21 - definition: OpenApiSchema, 22 - getModel: GetModelFn, 23 - model: Model, 24 - ): Model => { 17 + export const getAdditionalPropertiesModel = ({ 18 + definition, 19 + getModel, 20 + model, 21 + openApi, 22 + types, 23 + }: { 24 + openApi: OpenApi; 25 + definition: OpenApiSchema; 26 + getModel: GetModelFn; 27 + model: Model; 28 + types: Client['types']; 29 + }): Model => { 25 30 const ap = 26 31 typeof definition.additionalProperties === 'object' 27 32 ? definition.additionalProperties ··· 30 35 definition: ap, 31 36 openApi, 32 37 parentDefinition: definition, 38 + types, 33 39 }); 34 40 35 41 if (ap.$ref) { ··· 72 78 return model; 73 79 }; 74 80 75 - export const getModelProperties = ( 76 - openApi: OpenApi, 77 - definition: OpenApiSchema, 78 - getModel: GetModelFn, 79 - parent?: Model, 80 - ): Model[] => { 81 + export const getModelProperties = ({ 82 + definition, 83 + getModel, 84 + openApi, 85 + parent, 86 + types, 87 + }: { 88 + definition: OpenApiSchema; 89 + getModel: GetModelFn; 90 + openApi: OpenApi; 91 + parent?: Model; 92 + types: Client['types']; 93 + }): Model[] => { 81 94 let models: Model[] = []; 82 95 const discriminator = findOneOfParentDiscriminator(openApi, parent); 83 96 84 97 Object.entries(definition.properties ?? {}).forEach( 85 98 ([propertyName, property]) => { 86 - const propertyRequired = !!definition.required?.includes(propertyName); 99 + const propertyRequired = Boolean( 100 + definition.required?.includes(propertyName), 101 + ); 87 102 const propertyValues: Omit< 88 103 Model, 89 104 | '$refs' ··· 164 179 initialValues: propertyValues, 165 180 openApi, 166 181 parentDefinition: definition, 182 + types, 167 183 }); 168 184 model.isNullable = model.isNullable || isDefinitionNullable(property); 169 185 models = [...models, model];
+45 -28
packages/openapi-ts/src/openApi/v3/parser/getModels.ts
··· 1 - import type { Model } from '../../common/interfaces/client'; 1 + import type { Client } from '../../../types/client'; 2 2 import { reservedWords } from '../../common/parser/reservedWords'; 3 3 import { getType } from '../../common/parser/type'; 4 4 import type { OpenApi } from '../interfaces/OpenApi'; 5 5 import { getModel } from './getModel'; 6 6 7 - export const getModels = (openApi: OpenApi): Model[] => { 7 + export const getModels = ( 8 + openApi: OpenApi, 9 + ): Pick<Client, 'models' | 'types'> => { 10 + const types: Client['types'] = {}; 11 + let models: Client['models'] = []; 12 + 8 13 if (!openApi.components) { 9 - return []; 14 + return { 15 + models, 16 + types, 17 + }; 10 18 } 11 19 12 - let models: Model[] = []; 13 - 14 20 Object.entries(openApi.components.schemas ?? {}).forEach( 15 21 ([definitionName, definition]) => { 16 22 const definitionType = getType({ type: definitionName }); 23 + const name = definitionType.base.replace(reservedWords, '_$1'); 24 + const meta = { 25 + $ref: `#/components/schemas/${definitionName}`, 26 + name, 27 + }; 28 + types[name] = meta; 17 29 const model = getModel({ 18 30 definition, 19 31 isDefinition: true, 20 - meta: { 21 - $ref: `#/components/schemas/${definitionName}`, 22 - name: definitionType.base.replace(reservedWords, '_$1'), 23 - }, 32 + meta, 24 33 openApi, 34 + types, 25 35 }); 26 36 models = [...models, model]; 27 37 }, ··· 35 45 } 36 46 37 47 const definitionType = getType({ type: definitionName }); 48 + /** 49 + * Prefix parameter names to avoid name conflicts with schemas. 50 + * Assuming people are mostly interested in importing schema types 51 + * and don't care about this name as much. It should be resolved in 52 + * a cleaner way, there just isn't a good deduplication strategy 53 + * today. This is a workaround in the meantime, hopefully reducing 54 + * the chance of conflicts. 55 + * 56 + * Example where this would break: schema named `ParameterFoo` and 57 + * parameter named `Foo` (this would transform to `ParameterFoo`) 58 + * 59 + * Note: there's a related code to this workaround in `getType()` 60 + * method that needs to be cleaned up when this is addressed. 61 + */ 62 + const name = `Parameter${definitionType.base.replace(reservedWords, '_$1')}`; 63 + const meta = { 64 + $ref: `#/components/parameters/${definitionName}`, 65 + name, 66 + }; 67 + types[name] = meta; 38 68 const model = getModel({ 39 69 definition: schema, 40 70 isDefinition: true, 41 - meta: { 42 - $ref: `#/components/parameters/${definitionName}`, 43 - /** 44 - * Prefix parameter names to avoid name conflicts with schemas. 45 - * Assuming people are mostly interested in importing schema types 46 - * and don't care about this name as much. It should be resolved in 47 - * a cleaner way, there just isn't a good deduplication strategy 48 - * today. This is a workaround in the meantime, hopefully reducing 49 - * the chance of conflicts. 50 - * 51 - * Example where this would break: schema named `ParameterFoo` and 52 - * parameter named `Foo` (this would transform to `ParameterFoo`) 53 - * 54 - * Note: there's a related code to this workaround in `getType()` 55 - * method that needs to be cleaned up when this is addressed. 56 - */ 57 - name: `Parameter${definitionType.base.replace(reservedWords, '_$1')}`, 58 - }, 71 + meta, 59 72 openApi, 73 + types, 60 74 }); 61 75 model.deprecated = definition.deprecated; 62 76 model.description = definition.description || null; ··· 64 78 }, 65 79 ); 66 80 67 - return models; 81 + return { 82 + models, 83 + types, 84 + }; 68 85 };
+11 -5
packages/openapi-ts/src/openApi/v3/parser/getOperationParameter.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { enumMeta } from '../../../utils/enum'; 2 3 import type { OperationParameter } from '../../common/interfaces/client'; 3 4 import { getDefault } from '../../common/parser/getDefault'; ··· 11 12 import { getModel } from './getModel'; 12 13 import { isDefinitionNullable } from './inferType'; 13 14 14 - export const getOperationParameter = ( 15 - openApi: OpenApi, 16 - parameter: OpenApiParameter, 17 - ): OperationParameter => { 15 + export const getOperationParameter = ({ 16 + openApi, 17 + parameter, 18 + types, 19 + }: { 20 + openApi: OpenApi; 21 + parameter: OpenApiParameter; 22 + types: Client['types']; 23 + }): OperationParameter => { 18 24 let operationParameter: OperationParameter = { 19 25 $refs: [], 20 26 base: 'unknown', ··· 74 80 operationParameter.default = getDefault(schema); 75 81 return operationParameter; 76 82 } else { 77 - const model = getModel({ definition: schema, openApi }); 83 + const model = getModel({ definition: schema, openApi, types }); 78 84 operationParameter = { 79 85 ...operationParameter, 80 86 $refs: [...operationParameter.$refs, ...model.$refs],
+15 -5
packages/openapi-ts/src/openApi/v3/parser/getOperationParameters.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationParameters } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 3 4 import type { OpenApi } from '../interfaces/OpenApi'; ··· 6 7 7 8 const allowedIn = ['cookie', 'formData', 'header', 'path', 'query'] as const; 8 9 9 - export const getOperationParameters = ( 10 - openApi: OpenApi, 11 - parameters: OpenApiParameter[], 12 - ): OperationParameters => { 10 + export const getOperationParameters = ({ 11 + openApi, 12 + parameters, 13 + types, 14 + }: { 15 + openApi: OpenApi; 16 + parameters: OpenApiParameter[]; 17 + types: Client['types']; 18 + }): OperationParameters => { 13 19 const operationParameters: OperationParameters = { 14 20 $refs: [], 15 21 imports: [], ··· 27 33 openApi, 28 34 parameterOrReference, 29 35 ); 30 - const parameter = getOperationParameter(openApi, parameterDef); 36 + const parameter = getOperationParameter({ 37 + openApi, 38 + parameter: parameterDef, 39 + types, 40 + }); 31 41 32 42 const defIn = parameterDef.in as (typeof allowedIn)[number]; 33 43
+11 -5
packages/openapi-ts/src/openApi/v3/parser/getOperationRequestBody.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationParameter } from '../../common/interfaces/client'; 2 3 import { getPattern } from '../../common/parser/getPattern'; 3 4 import { getType } from '../../common/parser/type'; ··· 6 7 import { getContent } from './getContent'; 7 8 import { getModel } from './getModel'; 8 9 9 - export const getOperationRequestBody = ( 10 - openApi: OpenApi, 11 - body: OpenApiRequestBody, 12 - ): OperationParameter => { 10 + export const getOperationRequestBody = ({ 11 + body, 12 + openApi, 13 + types, 14 + }: { 15 + body: OpenApiRequestBody; 16 + openApi: OpenApi; 17 + types: Client['types']; 18 + }): OperationParameter => { 13 19 const name = body['x-body-name'] ?? 'requestBody'; 14 20 15 21 const requestBody: OperationParameter = { ··· 57 63 requestBody.imports = [...requestBody.imports, ...model.imports]; 58 64 return requestBody; 59 65 } else { 60 - const model = getModel({ definition: content.schema, openApi }); 66 + const model = getModel({ definition: content.schema, openApi, types }); 61 67 requestBody.export = model.export; 62 68 requestBody.type = model.type; 63 69 requestBody.base = model.base;
+13 -6
packages/openapi-ts/src/openApi/v3/parser/getOperationResponse.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationResponse } from '../../common/interfaces/client'; 2 3 import { getPattern } from '../../common/parser/getPattern'; 3 4 import { getRef } from '../../common/parser/getRef'; ··· 8 9 import { getContent } from './getContent'; 9 10 import { getModel } from './getModel'; 10 11 11 - export const getOperationResponse = ( 12 - openApi: OpenApi, 13 - response: OpenApiResponse, 14 - code: number | 'default', 15 - ): OperationResponse => { 12 + export const getOperationResponse = ({ 13 + code, 14 + openApi, 15 + response, 16 + types, 17 + }: { 18 + openApi: OpenApi; 19 + response: OpenApiResponse; 20 + code: number | 'default'; 21 + types: Client['types']; 22 + }): OperationResponse => { 16 23 const operationResponse: OperationResponse = { 17 24 $refs: [], 18 25 base: code !== 204 ? 'unknown' : 'void', ··· 53 60 operationResponse.type = model.type; 54 61 return operationResponse; 55 62 } else { 56 - const model = getModel({ definition: content.schema, openApi }); 63 + const model = getModel({ definition: content.schema, openApi, types }); 57 64 operationResponse.export = model.export; 58 65 operationResponse.type = model.type; 59 66 operationResponse.base = model.base;
+14 -7
packages/openapi-ts/src/openApi/v3/parser/getOperationResponses.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { OperationResponse } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 3 4 import { getOperationResponseCode } from '../../common/parser/operation'; ··· 6 7 import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 7 8 import { getOperationResponse } from './getOperationResponse'; 8 9 9 - export const getOperationResponses = ( 10 - openApi: OpenApi, 11 - responses: OpenApiResponses, 12 - ): OperationResponse[] => { 10 + export const getOperationResponses = ({ 11 + openApi, 12 + responses, 13 + types, 14 + }: { 15 + openApi: OpenApi; 16 + responses: OpenApiResponses; 17 + types: Client['types']; 18 + }): OperationResponse[] => { 13 19 const operationResponses: OperationResponse[] = []; 14 20 15 21 // Iterate over each response code and get the ··· 19 25 const responseCode = getOperationResponseCode(code); 20 26 21 27 if (responseCode) { 22 - const operationResponse = getOperationResponse( 28 + const operationResponse = getOperationResponse({ 29 + code: responseCode, 23 30 openApi, 24 31 response, 25 - responseCode, 26 - ); 32 + types, 33 + }); 27 34 operationResponses.push(operationResponse); 28 35 } 29 36 });
+44 -14
packages/openapi-ts/src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import type { Model } from '../../common/interfaces/client'; 2 3 import { getRef } from '../../common/parser/getRef'; 4 + import { getType } from '../../common/parser/type'; 5 + import type { GetModelFn } from '../interfaces/Model'; 3 6 import type { OpenApi } from '../interfaces/OpenApi'; 4 7 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 5 - import type { getModel } from './getModel'; 6 8 7 - // Fix for circular dependency 8 - export type GetModelFn = typeof getModel; 9 - 10 - export const getRequiredPropertiesFromComposition = ( 11 - openApi: OpenApi, 12 - required: string[], 13 - definitions: OpenApiSchema[], 14 - getModel: GetModelFn, 15 - ): Model[] => 16 - definitions 9 + export const getRequiredPropertiesFromComposition = ({ 10 + definitions, 11 + getModel, 12 + openApi, 13 + required, 14 + types, 15 + }: { 16 + openApi: OpenApi; 17 + required: string[]; 18 + definitions: OpenApiSchema[]; 19 + getModel: GetModelFn; 20 + types: Client['types']; 21 + }): Model[] => { 22 + const requiredProperties = definitions 17 23 .reduce((properties, definition) => { 18 24 if (definition.$ref) { 25 + const type = getType({ type: definition.$ref }); 26 + // avoid circular references if two refs reference each other 27 + // if (types[type.base] && types[type.base].$ref === definition.$ref) { 28 + // const schema = getRef<OpenApiSchema>(openApi, definition); 29 + // return [...properties] 30 + // } 31 + 32 + const meta = { 33 + $ref: definition.$ref, 34 + name: type.base, 35 + }; 36 + types[type.base] = meta; 19 37 const schema = getRef<OpenApiSchema>(openApi, definition); 20 38 return [ 21 39 ...properties, 22 - ...getModel({ definition: schema, openApi }).properties, 40 + ...getModel({ 41 + definition: schema, 42 + meta, 43 + openApi, 44 + types, 45 + }).properties, 23 46 ]; 24 47 } 48 + 25 49 return [ 26 50 ...properties, 27 - ...getModel({ definition, openApi, parentDefinition: definition }) 28 - .properties, 51 + ...getModel({ 52 + definition, 53 + openApi, 54 + parentDefinition: definition, 55 + types, 56 + }).properties, 29 57 ]; 30 58 }, [] as Model[]) 31 59 .filter( ··· 35 63 ...property, 36 64 isRequired: true, 37 65 })); 66 + return requiredProperties; 67 + };
+16 -3
packages/openapi-ts/src/openApi/v3/parser/getServices.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { unique } from '../../../utils/unique'; 2 3 import type { Operation, Service } from '../../common/interfaces/client'; 3 4 import type { OpenApi } from '../interfaces/OpenApi'; ··· 21 22 operations: [], 22 23 }); 23 24 24 - export const getServices = (openApi: OpenApi): Service[] => { 25 + export const getServices = ({ 26 + openApi, 27 + types, 28 + }: { 29 + openApi: OpenApi; 30 + types: Client['types']; 31 + }): Service[] => { 25 32 const services = new Map<string, Service>(); 26 33 27 34 for (const url in openApi.paths) { 28 35 const path = openApi.paths[url]; 29 - const pathParams = getOperationParameters(openApi, path.parameters ?? []); 36 + const pathParams = getOperationParameters({ 37 + openApi, 38 + parameters: path.parameters ?? [], 39 + types, 40 + }); 30 41 31 42 for (const key in path) { 32 43 const method = key as Lowercase<Operation['method']>; ··· 34 45 const op = path[method]!; 35 46 const tags = op.tags?.length ? op.tags.filter(unique) : ['Default']; 36 47 tags.forEach((tag) => { 37 - const operation = getOperation(openApi, { 48 + const operation = getOperation({ 38 49 method, 39 50 op, 51 + openApi, 40 52 pathParams, 41 53 tag, 54 + types, 42 55 url, 43 56 }); 44 57 const service =
+33 -14
packages/openapi-ts/src/openApi/v3/parser/operation.ts
··· 1 + import type { Client } from '../../../types/client'; 1 2 import { getOperationResults } from '../../../utils/operation'; 2 3 import type { 3 4 Operation, ··· 40 41 return mergedParameters; 41 42 }; 42 43 43 - export const getOperation = ( 44 - openApi: OpenApi, 45 - data: { 46 - method: Lowercase<Operation['method']>; 47 - op: OpenApiOperation; 48 - pathParams: OperationParameters; 49 - tag: string; 50 - url: string; 51 - }, 52 - ): Operation => { 53 - const { method, op, pathParams, tag, url } = data; 44 + export const getOperation = ({ 45 + method, 46 + op, 47 + openApi, 48 + pathParams, 49 + tag, 50 + types, 51 + url, 52 + }: { 53 + method: Lowercase<Operation['method']>; 54 + op: OpenApiOperation; 55 + openApi: OpenApi; 56 + pathParams: OperationParameters; 57 + tag: string; 58 + types: Client['types']; 59 + url: string; 60 + }): Operation => { 54 61 const service = getServiceName(tag); 55 62 const name = getOperationName(url, method, op.operationId); 56 63 ··· 77 84 }; 78 85 79 86 if (op.parameters) { 80 - const parameters = getOperationParameters(openApi, op.parameters); 87 + const parameters = getOperationParameters({ 88 + openApi, 89 + parameters: op.parameters, 90 + types, 91 + }); 81 92 operation.$refs = [...operation.$refs, ...parameters.$refs]; 82 93 operation.imports = [...operation.imports, ...parameters.imports]; 83 94 operation.parameters = [...operation.parameters, ...parameters.parameters]; ··· 106 117 107 118 if (op.requestBody) { 108 119 const requestBodyDef = getRef<OpenApiRequestBody>(openApi, op.requestBody); 109 - const requestBody = getOperationRequestBody(openApi, requestBodyDef); 120 + const requestBody = getOperationRequestBody({ 121 + body: requestBodyDef, 122 + openApi, 123 + types, 124 + }); 110 125 operation.$refs = [...operation.$refs, ...requestBody.$refs]; 111 126 operation.imports = [...operation.imports, ...requestBody.imports]; 112 127 operation.parameters = [...operation.parameters, requestBody]; ··· 114 129 } 115 130 116 131 if (op.responses) { 117 - const operationResponses = getOperationResponses(openApi, op.responses); 132 + const operationResponses = getOperationResponses({ 133 + openApi, 134 + responses: op.responses, 135 + types, 136 + }); 118 137 const operationResults = getOperationResults(operationResponses); 119 138 operation.errors = getOperationErrors(operationResponses); 120 139 operation.responseHeader = getOperationResponseHeader(operationResults);
+1 -1
packages/openapi-ts/test/__snapshots__/test/generated/v3/types.gen.ts.snap
··· 2081 2081 }; 2082 2082 }; 2083 2083 }; 2084 - }; 2084 + };
+4 -3
packages/openapi-ts/test/sample.cjs
··· 5 5 const config = { 6 6 client: 'fetch', 7 7 input: './test/spec/v3.json', 8 + // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 8 9 output: { 9 10 path: './test/generated/v3/', 10 11 }, 11 12 schemas: { 12 - // export: false, 13 + export: false, 13 14 }, 14 15 services: { 15 - // export: false, 16 + export: false, 16 17 }, 17 18 types: { 18 19 enums: 'typescript', 19 - // include: '^NestedAnyOfArraysNullable', 20 + // include: '^CloudProvider', 20 21 // name: 'PascalCase', 21 22 }, 22 23 // useOptions: false,