diff --git a/packages/core/src/__tests__/collection-model.spec.ts b/packages/core/src/__tests__/collection-model.spec.ts index 24a8fac2..702dd619 100644 --- a/packages/core/src/__tests__/collection-model.spec.ts +++ b/packages/core/src/__tests__/collection-model.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('array model', async() => { const result = await createTestDocument('collection-models/array-model-v3.yml') @@ -7,9 +8,10 @@ test('array model', async() => { const op1 = group1.operations[0] expect(op1.returnType).toBeUndefined() - expect(op1.queryParams?.length).toEqual(1) + expect(idx.size(op1.queryParams!)).toEqual(1) - const queryParam1 = op1.queryParams![0] + const queryParams = idx.values(op1.queryParams!) + const queryParam1 = queryParams[0] expect(queryParam1.name).toEqual('statuses') expect(queryParam1.nativeType.toString()).toEqual('array Statuses_enum') }) @@ -21,9 +23,10 @@ test('map model', async() => { const op1 = group1.operations[0] expect(op1.returnType).toBeUndefined() - expect(op1.queryParams?.length).toEqual(1) - - const queryParam1 = op1.queryParams![0] + expect(idx.size(op1.queryParams!)).toEqual(1) + + const queryParams = idx.values(op1.queryParams!) + const queryParam1 = queryParams[0] expect(queryParam1.name).toEqual('statuses') expect(queryParam1.nativeType.toString()).toEqual('map Statuses_model') }) diff --git a/packages/core/src/__tests__/default-value.spec.ts b/packages/core/src/__tests__/default-value.spec.ts index 04e8331d..1b476996 100644 --- a/packages/core/src/__tests__/default-value.spec.ts +++ b/packages/core/src/__tests__/default-value.spec.ts @@ -1,21 +1,24 @@ import { createTestDocument } from './common' import { CodegenPropertyType } from '@openapi-generator-plus/types' +import * as idx from '../indexed-type' test('array property', async() => { const result = await createTestDocument('default-value/arrays-v3.yml') - expect(result.models.length).toEqual(1) + expect(idx.size(result.models)).toEqual(1) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('Test') - expect(model1.properties?.length).toEqual(2) + expect(idx.size(model1.properties!)).toEqual(2) - const prop1 = model1.properties![0] + const model1Properties = idx.values(model1.properties!) + const prop1 = model1Properties[0] expect(prop1.name).toBe('arrayProperty') expect(prop1.propertyType).toEqual(CodegenPropertyType.ARRAY) expect(prop1.defaultValue).toEqual({ value: [], literalValue: '[]' }) - const prop2 = model1.properties![1] + const prop2 = model1Properties[1] expect(prop2.name).toBe('notRequiredArrayProperty') expect(prop2.propertyType).toEqual(CodegenPropertyType.ARRAY) expect(prop2.defaultValue).toEqual({ literalValue: 'undefined' }) diff --git a/packages/core/src/__tests__/inline-model-name-conflict.spec.ts b/packages/core/src/__tests__/inline-model-name-conflict.spec.ts index 36afcb86..4b8ad866 100644 --- a/packages/core/src/__tests__/inline-model-name-conflict.spec.ts +++ b/packages/core/src/__tests__/inline-model-name-conflict.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('inline model name conflict', async() => { const result = await createTestDocument('inline-model-name-conflict-v2.yml') @@ -9,9 +10,11 @@ test('inline model name conflict', async() => { expect(op1.returnType).toEqual('object') expect(op1.returnNativeType?.toString()).toEqual('getTest1_200_response_model1') - expect(result.models.length).toEqual(2) + expect(idx.size(result.models)).toEqual(2) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('getTest1_200_response_model') - expect(model1.properties![0].name).toEqual('prop2') + const model1Properties = idx.values(model1.properties!) + expect(model1Properties[0].name).toEqual('prop2') }) diff --git a/packages/core/src/__tests__/maps.spec.ts b/packages/core/src/__tests__/maps.spec.ts index 6fb671d8..6d567305 100644 --- a/packages/core/src/__tests__/maps.spec.ts +++ b/packages/core/src/__tests__/maps.spec.ts @@ -1,27 +1,32 @@ import { createTestDocument } from './common' import { CodegenPropertyType } from '@openapi-generator-plus/types' +import * as idx from '../indexed-type' test('string map', async() => { const result = await createTestDocument('maps/string-map-v2.yml') - expect(result.models.length).toEqual(1) + expect(idx.size(result.models)).toEqual(1) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('model1') - expect(model1.properties?.length).toEqual(1) - expect(model1.properties![0].propertyType).toEqual(CodegenPropertyType.MAP) + expect(idx.size(model1.properties!)).toEqual(1) + const model1Properties = idx.values(model1.properties!) + expect(model1Properties![0].propertyType).toEqual(CodegenPropertyType.MAP) }) test('object map', async() => { const result = await createTestDocument('maps/object-map-v2.yml') - expect(result.models.length).toEqual(2) + expect(idx.size(result.models)).toEqual(2) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('model1') - expect(model1.properties?.length).toEqual(1) + expect(idx.size(model1.properties!)).toEqual(1) - const prop1 = model1.properties![0] + const model1Properties = idx.values(model1.properties!) + const prop1 = model1Properties![0] expect(prop1.propertyType).toEqual(CodegenPropertyType.MAP) expect(prop1.nativeType.toString()).toEqual('map model2') @@ -34,13 +39,15 @@ test('object map', async() => { test('object map with no map parents', async() => { const result = await createTestDocument('maps/object-map-v2.yml') - expect(result.models.length).toEqual(2) + expect(idx.size(result.models)).toEqual(2) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('model1') - expect(model1.properties?.length).toEqual(1) + expect(idx.size(model1.properties!)).toEqual(1) - const prop1 = model1.properties![0] + const model1Properties = idx.values(model1.properties!) + const prop1 = model1Properties![0] expect(prop1.propertyType).toEqual(CodegenPropertyType.MAP) expect(prop1.nativeType.toString()).toEqual('map model2') diff --git a/packages/core/src/__tests__/odd-models.spec.ts b/packages/core/src/__tests__/odd-models.spec.ts index 30aee31e..d032f82f 100644 --- a/packages/core/src/__tests__/odd-models.spec.ts +++ b/packages/core/src/__tests__/odd-models.spec.ts @@ -1,11 +1,13 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('array of strings', async() => { const result = await createTestDocument('odd-models/array-of-strings-v2.yml', { collectionModelsAllowed: true, }) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('ArrayOfStrings') expect(model1.nativeType.toString()).toEqual('ArrayOfStrings') expect(model1.parent).toBeUndefined() @@ -17,7 +19,7 @@ test('uuid', async() => { const result = await createTestDocument('odd-models/uuid-v2.yml') /* We don't parse the UUID type as a model */ - expect(result.models.length).toEqual(0) + expect(idx.size(result.models)).toEqual(0) /* Note that there doesn't seem to be a way to _use_ schemas like this actually */ }) diff --git a/packages/core/src/__tests__/one-of.spec.ts b/packages/core/src/__tests__/one-of.spec.ts index 3a505731..8e8efd76 100644 --- a/packages/core/src/__tests__/one-of.spec.ts +++ b/packages/core/src/__tests__/one-of.spec.ts @@ -1,13 +1,15 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('one of discriminator', async() => { const result = await createTestDocument('one-of/one-of-discriminator.yml') - const model1 = result.models[0] - const model4 = result.models[3] + const models = idx.values(result.models) + const model1 = models[0] + const model4 = models[3] expect(model1.name).toEqual('Cat') - expect(model1.implements!.length).toBe(1) + expect(idx.size(model1.implements!)).toBe(1) expect(model4.name).toEqual('MyResponseType') expect(model4.discriminator!.name).toEqual('petType') expect(model4.discriminator!.references.length).toEqual(3) @@ -18,14 +20,17 @@ test('one of discriminator', async() => { test('one of no discriminator', async() => { const result = await createTestDocument('one-of/one-of-no-discriminator.yml') - const combinedModel = result.models[3] + const models = idx.values(result.models) + const combinedModel = models[3] expect(combinedModel.name).toEqual('MyResponseType') expect(combinedModel.isInterface).toBeFalsy() - expect(combinedModel.properties![0].name).toEqual('name') - expect(combinedModel.properties![1].name).toEqual('bark') - expect(combinedModel.properties![2].name).toEqual('lovesRocks') - const model1 = result.models[0] + const combinedModelProperties = idx.values(combinedModel.properties!) + expect(combinedModelProperties![0].name).toEqual('name') + expect(combinedModelProperties![1].name).toEqual('bark') + expect(combinedModelProperties![2].name).toEqual('lovesRocks') + + const model1 = models[0] expect(model1.isInterface).toBe(true) }) @@ -37,12 +42,13 @@ test('one of discriminator missing property', async() => { test('one of subclasses discriminator', async() => { const result = await createTestDocument('one-of/one-of-subclasses-discriminator.yml') - const model1 = result.models[0] - const model4 = result.models[3] + const models = idx.values(result.models) + const model1 = models[0] + const model4 = models[3] expect(model1.name).toEqual('Cat') expect(model4.name).toEqual('Pet') - expect(model4.children?.length).toEqual(3) + expect(idx.size(model4.children!)).toEqual(3) expect(model4.discriminator!.references.length).toEqual(3) expect(model4.isInterface).toBeFalsy() }) diff --git a/packages/core/src/__tests__/openapiv2.spec.ts b/packages/core/src/__tests__/openapiv2.spec.ts index 0c17b556..f8d50a77 100644 --- a/packages/core/src/__tests__/openapiv2.spec.ts +++ b/packages/core/src/__tests__/openapiv2.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument, createTestResult } from './common' +import * as idx from '../indexed-type' test('parse info', async() => { const result = await createTestDocument('openapiv2-1.yml') @@ -23,7 +24,7 @@ test('parse groups', async() => { const op1 = group1.operations[0] expect(op1.name).toEqual('getTest1') expect(op1.parameters).toBeDefined() - expect(op1.parameters!.length).toEqual(1) + expect(idx.size(op1.parameters!)).toEqual(1) expect(op1.returnType).toEqual('object') expect(op1.returnNativeType?.toString()).toEqual('Test1Response') @@ -34,7 +35,7 @@ test('parse groups', async() => { const op2 = group2.operations[0] expect(op2.name).toEqual(state.generator.toOperationName('/test2', 'GET', state)) /* Uses default name */ - expect(op2.parameters!.length).toEqual(1) + expect(idx.size(op2.parameters!)).toEqual(1) expect(op2.returnType).not.toBeDefined() expect(op2.returnNativeType).not.toBeDefined() }) diff --git a/packages/core/src/__tests__/openapiv3.spec.ts b/packages/core/src/__tests__/openapiv3.spec.ts index 5779de10..ceb0b02e 100644 --- a/packages/core/src/__tests__/openapiv3.spec.ts +++ b/packages/core/src/__tests__/openapiv3.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument, createTestResult } from './common' +import * as idx from '../indexed-type' test('process document', async() => { const result = await createTestDocument('openapiv3-1.yml') @@ -23,7 +24,7 @@ test('parse operation params', async() => { const op1 = group1.operations[0] expect(op1.name).toEqual('getTest1') expect(op1.parameters).toBeDefined() - expect(op1.parameters!.length).toEqual(2) + expect(idx.size(op1.parameters!)).toEqual(2) }) test('parse operation body params', async() => { @@ -31,7 +32,7 @@ test('parse operation body params', async() => { const group2 = result.groups[1] const op2 = group2.operations[0] - expect(op2.parameters!.length).toEqual(0) + expect(op2.parameters).toBeUndefined() expect(op2.requestBody).toBeDefined() expect(op2.requestBody!.type).toEqual('object') expect(op2.requestBody!.nativeType?.toString()).toEqual('Test2Request') @@ -72,7 +73,7 @@ test('parse groups', async() => { const op2 = group2.operations[0] expect(op2.name).toEqual(state.generator.toOperationName('/test2', 'POST', state)) /* Uses default name */ - // expect(op2.parameters!.length).toEqual(1) + // expect(op2.allParams!.length).toEqual(1) // expect(op2.returnType).not.toBeDefined() // expect(op2.returnNativeType).not.toBeDefined() }) diff --git a/packages/core/src/__tests__/parameter-inline-models.spec.ts b/packages/core/src/__tests__/parameter-inline-models.spec.ts index 69fb4847..535a01d7 100644 --- a/packages/core/src/__tests__/parameter-inline-models.spec.ts +++ b/packages/core/src/__tests__/parameter-inline-models.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('inline response model', async() => { const result = await createTestDocument('parameter-inline-models-v2.yml') @@ -6,14 +7,17 @@ test('inline response model', async() => { const group1 = result.groups[0] const op1 = group1.operations[0] - expect(op1.parameters!.length).toEqual(1) + expect(idx.size(op1.parameters!)).toEqual(1) - const param1 = op1.parameters![0] + const params = idx.values(op1.parameters!) + + const param1 = params[0] expect(param1.name).toEqual('arg1') expect(param1.nativeType.toString()).toEqual('getTest1_arg1_enum') - expect(result.models.length).toEqual(1) + expect(idx.size(result.models)).toEqual(1) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('getTest1_arg1_enum') }) diff --git a/packages/core/src/__tests__/response-inline-models.spec.ts b/packages/core/src/__tests__/response-inline-models.spec.ts index 0198ea90..bfdcfdb2 100644 --- a/packages/core/src/__tests__/response-inline-models.spec.ts +++ b/packages/core/src/__tests__/response-inline-models.spec.ts @@ -1,4 +1,5 @@ import { createTestDocument } from './common' +import * as idx from '../indexed-type' test('inline response model', async() => { const result = await createTestDocument('response-inline-models-v2.yml') @@ -9,8 +10,9 @@ test('inline response model', async() => { expect(op1.returnType).toEqual('object') expect(op1.returnNativeType?.toString()).toEqual('getTest1_200_response_model') - expect(result.models.length).toEqual(1) + expect(idx.size(result.models)).toEqual(1) - const model1 = result.models[0] + const models = idx.values(result.models) + const model1 = models[0] expect(model1.name).toEqual('getTest1_200_response_model') }) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2b4d0db9..a12832ba 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,6 +4,7 @@ import { defaultGeneratorOptions } from './generators' import SwaggerParser from '@apidevtools/swagger-parser' import { toSpecVersion } from './utils' import { InternalCodegenState } from './types' +import * as idx from './indexed-type' /** * Construct a CodegenGenerator from the given constructor. @@ -41,7 +42,7 @@ export function createCodegenDocument(input: CodegenInput, state: CodegenStat usedModelFullyQualifiedNames: {}, modelsBySchema: new Map(), reservedNames: {}, - models: [], + models: idx.create(), specVersion: toSpecVersion(input.root), } diff --git a/packages/core/src/indexed-type.ts b/packages/core/src/indexed-type.ts new file mode 100644 index 00000000..8cd0f08d --- /dev/null +++ b/packages/core/src/indexed-type.ts @@ -0,0 +1,6 @@ +/* +Enable switching between POJOs and Maps for our indexed object type. +You must also make the switch in @openapi-generator-plus/types types.ts +*/ +export * from './objects' +// export * from './maps' diff --git a/packages/core/src/maps.ts b/packages/core/src/maps.ts new file mode 100644 index 00000000..c5b768f5 --- /dev/null +++ b/packages/core/src/maps.ts @@ -0,0 +1,72 @@ +export function findEntry(map: Map, predicate: (value: V) => unknown): [K, V] | undefined { + for (const entry of map) { + if (predicate(entry[1])) { + return entry + } + } + return undefined +} + +export function find(map: Map, predicate: (value: V) => boolean | undefined): V | undefined { + for (const entry of map) { + if (predicate(entry[1])) { + return entry[1] + } + } + return undefined +} + +export function filter(map: Map, predicate: (value: V) => boolean | undefined): Map { + const result: Map = new Map() + for (const entry of map) { + if (predicate(entry[1])) { + result.set(entry[0], entry[1]) + } + } + return result +} + +export function filterToNothing(map: Map, predicate: (value: V) => boolean | undefined): Map | undefined { + const filtered = filter(map, predicate) + return empty(filtered) ? undefined : filtered +} + +export function empty(map: Map): boolean { + return size(map) === 0 +} + +export function sortValues(map: Map, compare: (a: V, b: V) => number): Map { + return new Map([...map.entries()].sort((a, b) => compare(a[1], b[1]))) +} + +export function iterable(map: Map): Iterable<[K, V]> { + return map +} + +export function remove(map: Map, key: K) { + map.delete(key) +} + +export function create(entries?: [K, V][]): Map { + if (!entries) { + return new Map() + } else { + return new Map(entries) + } +} + +export function set(map: Map, key: K, value: V) { + map.set(key, value) +} + +export function get(map: Map, key: K): V | undefined { + return map.get(key) +} + +export function values(map: Map): V[] { + return [...map.values()] +} + +export function size(map: Map): number { + return map.size +} diff --git a/packages/core/src/objects.ts b/packages/core/src/objects.ts new file mode 100644 index 00000000..c57b049a --- /dev/null +++ b/packages/core/src/objects.ts @@ -0,0 +1,119 @@ +interface IndexedObjectsType { + [key: string]: T +} + +export function findEntry(ob: IndexedObjectsType, predicate: (value: T) => unknown): [string, T] | undefined { + for (const key in ob) { + const value = ob[key] + if (predicate(value)) { + return [key, value] + } + } + return undefined +} + +export function find(ob: IndexedObjectsType, predicate: (value: T) => boolean | undefined): T | undefined { + for (const key in ob) { + const value = ob[key] + if (predicate(value)) { + return value + } + } + return undefined +} + +export function filter(ob: IndexedObjectsType, predicate: (value: T) => boolean | undefined): IndexedObjectsType { + const result: IndexedObjectsType = {} + for (const key in ob) { + const value = ob[key] + if (predicate(value)) { + result[key] = value + } + } + return result +} + +export function filterToNothing(ob: IndexedObjectsType, predicate: (value: T) => boolean | undefined): IndexedObjectsType | undefined { + const filtered = filter(ob, predicate) + return empty(filtered) ? undefined : filtered +} + +function empty(ob: IndexedObjectsType): boolean { + return size(ob) === 0 +} + +function objectEntries(ob: IndexedObjectsType): [string, T][] { + const entries: [string, T][] = [] + for (const key in ob) { + const value = ob[key] + entries.push([key, value]) + } + return entries +} + +export function sortValues(ob: IndexedObjectsType, compare: (a: T, b: T) => number): IndexedObjectsType { + const entries = objectEntries(ob) + entries.sort((a, b) => compare(a[1], b[1])) + return create(entries) +} + +export function iterable(ob: IndexedObjectsType): Iterable<[string, T]> { + return { + [Symbol.iterator]: (): Iterator<[string, T]> => { + let i = 0 + const keys = Object.keys(ob) + return { + next: (): IteratorResult<[string, T], undefined> => { + const entry: [string, T] = [keys[i], ob[keys[i]]] + i++ + + if (i <= keys.length) { + return { + value: entry, + done: false, + } + } else { + return { + value: undefined, + done: true, + } + } + }, + } + }, + } +} + +export function remove(ob: IndexedObjectsType, key: string) { + delete ob[key] +} + +export function create(entries?: [string, T][]): IndexedObjectsType { + const result: IndexedObjectsType = {} + if (entries) { + for (const entry of entries) { + result[entry[0]] = entry[1] + } + } + return result +} + +export function set(ob: IndexedObjectsType, key: string, value: T) { + ob[key] = value +} + +export function get(ob: IndexedObjectsType, key: string): T | undefined { + return ob[key] +} + +export function values(ob: IndexedObjectsType): T[] { + const result: T[] = [] + for (const key in ob) { + result.push(ob[key]) + } + return result +} + +export function size(ob: IndexedObjectsType): number { + return Object.keys(ob).length +} diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts index 26bfbd08..f13d1b24 100644 --- a/packages/core/src/process.ts +++ b/packages/core/src/process.ts @@ -1,10 +1,11 @@ import { OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types' -import { CodegenDocument, CodegenOperation, CodegenResponse, CodegenProperty, CodegenParameter, CodegenMediaType, CodegenVendorExtensions, CodegenModel, CodegenSecurityScheme, CodegenAuthScope as CodegenSecurityScope, CodegenOperationGroup, CodegenServer, CodegenOperationGroups, CodegenNativeType, CodegenTypePurpose, CodegenArrayTypePurpose, CodegenMapTypePurpose, CodegenContent, CodegenParameterIn, CodegenOAuthFlow, CodegenSecurityRequirement, CodegenPropertyType, CodegenLiteralValueOptions, CodegenTypeInfo, HttpMethods, CodegenDiscriminatorMappings, CodegenDiscriminator, CodegenEnumValue, CodegenGeneratorType, CodegenScope, CodegenSchema, CodegenExamples, CodegenRequestBody, CodegenExample } from '@openapi-generator-plus/types' +import { CodegenDocument, CodegenOperation, CodegenResponse, CodegenProperty, CodegenParameter, CodegenMediaType, CodegenVendorExtensions, CodegenModel, CodegenSecurityScheme, CodegenAuthScope as CodegenSecurityScope, CodegenOperationGroup, CodegenServer, CodegenOperationGroups, CodegenNativeType, CodegenTypePurpose, CodegenArrayTypePurpose, CodegenMapTypePurpose, CodegenContent, CodegenParameterIn, CodegenOAuthFlow, CodegenSecurityRequirement, CodegenPropertyType, CodegenLiteralValueOptions, CodegenTypeInfo, HttpMethods, CodegenDiscriminatorMappings, CodegenDiscriminator, CodegenGeneratorType, CodegenScope, CodegenSchema, CodegenExamples, CodegenRequestBody, CodegenExample, CodegenModels, CodegenParameters, CodegenResponses, CodegenProperties, CodegenEnumValues } from '@openapi-generator-plus/types' import { isOpenAPIV2ResponseObject, isOpenAPIReferenceObject, isOpenAPIV3ResponseObject, isOpenAPIV2GeneralParameterObject, isOpenAPIV2Document, isOpenAPIV3Operation, isOpenAPIV3Document, isOpenAPIV2SecurityScheme, isOpenAPIV3SecurityScheme, isOpenAPIV2ExampleObject, isOpenAPIV3ExampleObject, isOpenAPIv3SchemaObject } from './openapi-type-guards' import { OpenAPIX } from './types/patches' import _ from 'lodash' import { stringLiteralValueOptions } from './utils' import { InternalCodegenState } from './types' +import * as idx from './indexed-type' /** * Error thrown when a model cannot be generated because it doesn't represent a valid model in @@ -42,7 +43,7 @@ function processCodegenDocument(doc: CodegenDocument, state: InternalCodegenStat doc.groups.sort((a, b) => a.name.localeCompare(b.name)) /* Sort models */ - doc.models.sort((a, b) => a.name.localeCompare(b.name)) + doc.models = idx.sortValues(doc.models, (a, b) => a.name.localeCompare(b.name)) } function processCodegenOperationGroup(group: CodegenOperationGroup, state: InternalCodegenState) { @@ -58,14 +59,13 @@ function processCodegenOperationGroup(group: CodegenOperationGroup, state: Inter group.operations.sort((a, b) => a.name.localeCompare(b.name)) } -function processCodegenModels(models: CodegenModel[], state: InternalCodegenState) { - for (let i = 0; i < models.length; i++) { - const result = processCodegenModel(models[i], state) +function processCodegenModels(models: CodegenModels, state: InternalCodegenState) { + for (const entry of idx.iterable(models)) { + const result = processCodegenModel(entry[1], state) if (!result) { - models.splice(i, 1) - i-- + idx.remove(models, entry[0]) } else { - const subModels = models[i].models + const subModels = entry[1].models if (subModels) { processCodegenModels(subModels, state) } @@ -221,14 +221,15 @@ function toCodegenOperationName(path: string, method: string, operation: OpenAPI function toCodegenOperation(path: string, method: string, operation: OpenAPI.Operation, state: InternalCodegenState): CodegenOperation { const name = toCodegenOperationName(path, method, operation, state) - const responses: CodegenResponse[] | undefined = toCodegenResponses(operation, name, state) - const defaultResponse = responses ? responses.find(r => r.isDefault) : undefined + const responses: CodegenResponses | undefined = toCodegenResponses(operation, name, state) + const defaultResponse = responses ? idx.find(responses, r => r.isDefault) : undefined - let parameters: CodegenParameter[] | undefined + let parameters: CodegenParameters | undefined if (operation.parameters) { - parameters = [] + parameters = idx.create() for (const parameter of operation.parameters) { - parameters.push(toCodegenParameter(parameter, name, state)) + const codegenParameter = toCodegenParameter(parameter, name, state) + idx.set(parameters, codegenParameter.name, codegenParameter) } } @@ -276,13 +277,13 @@ function toCodegenOperation(path: string, method: string, operation: OpenAPI.Ope /* Apply special body param properties */ if (parameters) { - const bodyParamIndex = parameters.findIndex(p => p.in === 'body') - if (bodyParamIndex !== -1) { + const bodyParamEntry = idx.findEntry(parameters, p => p.in === 'body') + if (bodyParamEntry) { if (!consumes) { throw new Error(`Consumes not specified for operation with body parameter: ${path}`) } - const existingBodyParam = parameters[bodyParamIndex] + const existingBodyParam = bodyParamEntry[1] const contents = consumes?.map(mediaType => { const result: CodegenContent = { mediaType, @@ -304,7 +305,7 @@ function toCodegenOperation(path: string, method: string, operation: OpenAPI.Ope ...extractCodegenTypeInfo(existingBodyParam), } - parameters.splice(bodyParamIndex, 1) + idx.remove(parameters, bodyParamEntry[0]) } } } @@ -325,14 +326,14 @@ function toCodegenOperation(path: string, method: string, operation: OpenAPI.Ope returnType: defaultResponse ? defaultResponse.type : undefined, returnNativeType: defaultResponse ? defaultResponse.nativeType : undefined, consumes, - produces: responses ? toUniqueMediaTypes(responses.reduce((collected, response) => response.produces ? [...collected, ...response.produces] : collected, [] as CodegenMediaType[])) : undefined, + produces: responses ? toUniqueMediaTypes(idx.values(responses).reduce((collected, response) => response.produces ? [...collected, ...response.produces] : collected, [] as CodegenMediaType[])) : undefined, parameters, - queryParams: parameters?.filter(p => p.isQueryParam), - pathParams: parameters?.filter(p => p.isPathParam), - headerParams: parameters?.filter(p => p.isHeaderParam), - cookieParams: parameters?.filter(p => p.isCookieParam), - formParams: parameters?.filter(p => p.isFormParam), + queryParams: parameters && idx.filterToNothing(parameters, p => p.isQueryParam), + pathParams: parameters && idx.filterToNothing(parameters, p => p.isPathParam), + headerParams: parameters && idx.filterToNothing(parameters, p => p.isHeaderParam), + cookieParams: parameters && idx.filterToNothing(parameters, p => p.isCookieParam), + formParams: parameters && idx.filterToNothing(parameters, p => p.isFormParam), requestBody: bodyParam, @@ -356,12 +357,12 @@ function toCodegenOperation(path: string, method: string, operation: OpenAPI.Ope return op } -function parametersHaveExamples(parameters: CodegenParameter[] | undefined): boolean | undefined { +function parametersHaveExamples(parameters: CodegenParameters | undefined): boolean | undefined { if (!parameters) { return undefined } - return !!parameters.find(param => !!param.examples?.length) + return !!idx.find(parameters, param => !!param.examples?.length) } function requestBodyHasExamples(parameter: CodegenRequestBody | undefined): boolean | undefined { @@ -372,12 +373,12 @@ function requestBodyHasExamples(parameter: CodegenRequestBody | undefined): bool return !!parameter.contents.find(c => !!c.examples?.length) } -function responsesHaveExamples(responses: CodegenResponse[] | undefined): boolean | undefined { +function responsesHaveExamples(responses: CodegenResponses | undefined): boolean | undefined { if (!responses) { return undefined } - return !!responses.find(response => response.contents && response.contents.find(c => !!c.examples?.length)) + return !!idx.findEntry(responses, response => response.contents && response.contents.find(c => !!c.examples?.length)) } function toUniqueMediaTypes(mediaTypes: CodegenMediaType[]): CodegenMediaType[] { @@ -663,13 +664,13 @@ function toProduceMediaTypes(op: OpenAPIV2.OperationObject, state: InternalCodeg } } -function toCodegenResponses(operation: OpenAPI.Operation, scopeName: string, state: InternalCodegenState): CodegenResponse[] | undefined { +function toCodegenResponses(operation: OpenAPI.Operation, scopeName: string, state: InternalCodegenState): CodegenResponses | undefined { const responses = operation.responses if (!responses) { return undefined } - const result: CodegenResponse[] = [] + const result: CodegenResponses = idx.create() let bestCode: number | undefined let bestResponse: CodegenResponse | undefined @@ -678,7 +679,7 @@ function toCodegenResponses(operation: OpenAPI.Operation, scopeName: string, sta const responseCode = responseCodeString === 'default' ? 0 : parseInt(responseCodeString, 10) const response = toCodegenResponse(operation, responseCode, responses[responseCodeString], false, scopeName, state) - result.push(response) + idx.set(result, `${responseCode}`, response) /* See DefaultCodegen.findMethodResponse */ if (responseCode === 0 || Math.floor(responseCode / 100) === 2) { @@ -1179,19 +1180,19 @@ function uniqueModelName(scopedName: string[], state: InternalCodegenState): str return [...scopeNames, name] } -function toCodegenModelProperties(schema: OpenAPIX.SchemaObject, scope: CodegenScope, state: InternalCodegenState): CodegenProperty[] | undefined { +function toCodegenModelProperties(schema: OpenAPIX.SchemaObject, scope: CodegenScope, state: InternalCodegenState): CodegenProperties | undefined { schema = resolveReference(schema, state) if (typeof schema.properties !== 'object') { return undefined } - const properties: CodegenProperty[] = [] + const properties: CodegenProperties = idx.create() for (const propertyName in schema.properties) { const required = typeof schema.required === 'object' ? schema.required.indexOf(propertyName) !== -1 : false const propertySchema = schema.properties[propertyName] const property = toCodegenProperty(propertyName, propertySchema, required, scope, state) - properties.push(property) + idx.set(properties, property.name, property) } return properties @@ -1298,17 +1299,13 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu model.properties = toCodegenModelProperties(schema, model, state) - function absorbProperties(otherProperties: CodegenProperty[]) { + function absorbProperties(otherProperties: CodegenProperties) { if (!model.properties) { - model.properties = [] + model.properties = idx.create() } - const propertiesByName: { [name: string]: CodegenProperty } = {} - for (const property of model.properties) { - propertiesByName[property.name] = property - } - for (const property of otherProperties) { - const existingProperty = propertiesByName[property.name] + for (const property of idx.values(otherProperties)) { + const existingProperty = idx.get(model.properties, property.name) if (existingProperty) { /* Check that the types don't conflict */ if (existingProperty.propertyType !== property.propertyType) { @@ -1317,8 +1314,7 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu throw new Error(`Cannot merge properties "${property.name}" for "${model.nativeType}" due to type conflict: ${existingProperty.nativeType} vs ${property.nativeType}`) } } else { - model.properties.push(property) - propertiesByName[property.name] = property + idx.set(model.properties, property.name, property) } } } @@ -1413,7 +1409,7 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu for (const subSchema of oneOf) { const subModel = toCodegenModel('submodel', model, subSchema, state) - const subModelDiscriminatorProperty = removeModelProperty(subModel, schemaDiscriminator.propertyName) + const subModelDiscriminatorProperty = removeModelProperty(subModel.properties, schemaDiscriminator.propertyName) if (!subModelDiscriminatorProperty) { throw new Error(`Discriminator property "${schemaDiscriminator.propertyName}" for "${nativeType}" missing from "${subModel.nativeType}"`) } @@ -1437,27 +1433,27 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu }) if (!subModel.implements) { - subModel.implements = [] + subModel.implements = idx.create() } - subModel.implements.push(model) + idx.set(subModel.implements, model.name, model) if (!model.implementors) { - model.implementors = [] + model.implementors = idx.create() } - model.implementors.push(subModel) + idx.set(model.implementors, subModel.name, subModel) } } else { /* Without a discriminator we bundle all of the properties together into this model and turn the subModels into interfaces */ - model.implements = [] + model.implements = idx.create() for (const subSchema of oneOf) { const subModel = toCodegenModel('submodel', model, subSchema, state) absorbModel(subModel) subModel.isInterface = true - model.implements.push(subModel) + idx.set(model.implements, subModel.name, subModel) if (!subModel.implementors) { - subModel.implementors = [] + subModel.implementors = idx.create() } - subModel.implementors.push(model) + idx.set(subModel.implementors, model.name, model) } } } else if (schema.enum) { @@ -1487,10 +1483,10 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu nativeType: enumValueNativeType, } - const enumValues: CodegenEnumValue[] | undefined = schema.enum ? schema.enum.map(name => ({ + const enumValues: CodegenEnumValues | undefined = schema.enum ? idx.create(schema.enum.map(name => ([name, { name: state.generator.toEnumMemberName(name, state), literalValue: state.generator.toLiteral(name, enumValueLiteralOptions, state), - })) : undefined + }]))) : undefined if (enumValues) { model.enumValueNativeType = enumValueNativeType @@ -1542,9 +1538,9 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu /* Add child model */ if (model.parent) { if (!model.parent.children) { - model.parent.children = [] + model.parent.children = idx.create() } - model.parent.children.push(model) + idx.set(model.parent.children, model.name, model) const discriminatorModel = findClosestDiscriminatorModel(model.parent) if (discriminatorModel) { @@ -1566,11 +1562,11 @@ function toCodegenModel(suggestedName: string, suggestedScope: CodegenScope | nu if (scope) { if (!scope.models) { - scope.models = [] + scope.models = idx.create() } - scope.models.push(model) + idx.set(scope.models, model.name, model) } else { - state.models.push(model) + idx.set(state.models, model.name, model) } return model } @@ -1604,20 +1600,18 @@ function findClosestDiscriminatorModel(model: CodegenModel): CodegenModel | unde } } -function removeModelProperty(model: CodegenModel, name: string): CodegenProperty | undefined -function removeModelProperty(properties: CodegenProperty[], name: string): CodegenProperty | undefined -function removeModelProperty(modelOrProperties: CodegenModel | CodegenProperty[], name: string): CodegenProperty | undefined { - const properties = Array.isArray(modelOrProperties) ? modelOrProperties : modelOrProperties.properties +function removeModelProperty(properties: CodegenProperties | undefined, name: string): CodegenProperty | undefined { if (!properties) { return undefined } - const index = properties.findIndex(p => p.name === name) - if (index === -1) { + const entry = idx.findEntry(properties, p => p.name === name) + if (!entry) { return undefined } - return properties.splice(index, 1)[0] + idx.remove(properties, entry[0]) + return entry[1] } function toCodegenDiscriminatorMappings(discriminator: OpenAPIV3.DiscriminatorObject): CodegenDiscriminatorMappings { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9c6e0ba8..99da3727 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,4 +1,4 @@ -import { CodegenModel, CodegenState, CodegenInputDocument } from '@openapi-generator-plus/types' +import { CodegenModel, CodegenState, CodegenInputDocument, CodegenModels } from '@openapi-generator-plus/types' import { OpenAPIX } from './types/patches' @@ -14,7 +14,7 @@ export interface InternalCodegenState extends CodegenState, CodegenIn modelsBySchema: Map /** A hash of $ref to fully qualified model name, representing reserved model names */ reservedNames: { [$ref: string]: string | undefined } - /** The array of top-level models */ - models: CodegenModel[] + /** The map of top-level models */ + models: CodegenModels specVersion: CodegenSpecVersion } diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 6948d9ef..21d97d58 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -108,7 +108,7 @@ export interface CodegenRootContext { export interface CodegenDocument { info: CodegenInfo groups: CodegenOperationGroup[] - models: CodegenModel[] + models: CodegenModels servers?: CodegenServer[] securitySchemes?: CodegenSecurityScheme[] securityRequirements?: CodegenSecurityRequirement[] @@ -166,18 +166,18 @@ export interface CodegenOperation { consumes?: CodegenMediaType[] produces?: CodegenMediaType[] - parameters?: CodegenParameter[] - queryParams?: CodegenParameter[] - pathParams?: CodegenParameter[] - headerParams?: CodegenParameter[] - cookieParams?: CodegenParameter[] - formParams?: CodegenParameter[] + parameters?: CodegenParameters + queryParams?: CodegenParameters + pathParams?: CodegenParameters + headerParams?: CodegenParameters + cookieParams?: CodegenParameters + formParams?: CodegenParameters requestBody?: CodegenRequestBody securityRequirements?: CodegenSecurityRequirement[] vendorExtensions?: CodegenVendorExtensions - responses?: CodegenResponse[] + responses?: CodegenResponses defaultResponse?: CodegenResponse deprecated?: boolean summary?: string @@ -194,6 +194,14 @@ export interface CodegenOperation { hasResponseExamples?: boolean } +// type IndexedObjectsType = Map +interface IndexedObjectsType { + [key: string]: T +} + +export type CodegenResponses = IndexedObjectsType +export type CodegenParameters = IndexedObjectsType + export interface CodegenResponse extends Partial { code: number description: string @@ -204,9 +212,11 @@ export interface CodegenResponse extends Partial { isDefault: boolean vendorExtensions?: CodegenVendorExtensions - headers?: CodegenProperty[] + headers?: CodegenHeaders } +export type CodegenHeaders = IndexedObjectsType + export interface CodegenContent extends CodegenTypeInfo { mediaType: CodegenMediaType examples?: CodegenExamples @@ -372,15 +382,17 @@ export interface CodegenScope { scopedName: string[] /** Nested models */ - models?: CodegenModel[] + models?: CodegenModels } +export type CodegenModels = IndexedObjectsType + export interface CodegenModel extends CodegenTypeInfo, CodegenScope { /** The name of this model */ name: string description?: string - properties?: CodegenProperty[] + properties?: CodegenProperties /* Polymorphism */ @@ -391,14 +403,14 @@ export interface CodegenModel extends CodegenTypeInfo, CodegenScope { discriminatorValues?: CodegenDiscriminatorValue[] /** The models that have this model as their parent */ - children?: CodegenModel[] + children?: CodegenModels /** Whether this model is an interface; it has no properties and exists as a marker in the type system rather than a functional object */ isInterface?: boolean /** The interface models that this model complies with */ - implements?: CodegenModel[] - implementors?: CodegenModel[] + implements?: CodegenModels + implementors?: CodegenModels vendorExtensions?: CodegenVendorExtensions @@ -409,7 +421,7 @@ export interface CodegenModel extends CodegenTypeInfo, CodegenScope { /** The native type of the enum value */ enumValueNativeType?: CodegenNativeType /** The values making up the enum */ - enumValues?: CodegenEnumValue[] + enumValues?: CodegenEnumValues /** Parent model */ parent?: CodegenModel @@ -421,6 +433,8 @@ export interface CodegenModel extends CodegenTypeInfo, CodegenScope { deprecated?: boolean } +export type CodegenProperties = IndexedObjectsType + export interface CodegenModelReference { model: CodegenModel name: string @@ -511,6 +525,8 @@ export interface CodegenNativeMapTypeOptions { purpose: CodegenMapTypePurpose } +export type CodegenEnumValues = IndexedObjectsType + export interface CodegenEnumValue { name: string literalValue: string