Skip to content

Commit 40ac572

Browse files
committed
fix: allow number/string indexed types
1 parent 0b45240 commit 40ac572

File tree

4 files changed

+76
-7
lines changed

4 files changed

+76
-7
lines changed

packages/cli/src/metadataGeneration/typeResolver.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,16 @@ export class TypeResolver {
243243
}
244244
}
245245

246+
if (ts.isIndexedAccessTypeNode(this.typeNode) && (this.typeNode.indexType.kind === ts.SyntaxKind.NumberKeyword || this.typeNode.indexType.kind === ts.SyntaxKind.StringKeyword)) {
247+
const numberIndexType = this.typeNode.indexType.kind === ts.SyntaxKind.NumberKeyword;
248+
const objectType = this.current.typeChecker.getTypeFromTypeNode(this.typeNode.objectType);
249+
const type = numberIndexType ? objectType.getNumberIndexType() : objectType.getStringIndexType();
250+
if (type === undefined) {
251+
throw new GenerateMetadataError(`Could not determine ${numberIndexType ? 'number' : 'string'} index on ${this.current.typeChecker.typeToString(objectType)}`, this.typeNode);
252+
}
253+
return new TypeResolver(this.current.typeChecker.typeToTypeNode(type)!, this.current, this.typeNode, this.context, this.referencer).resolve();
254+
}
255+
246256
if (
247257
ts.isIndexedAccessTypeNode(this.typeNode) &&
248258
ts.isLiteralTypeNode(this.typeNode.indexType) &&

tests/fixtures/testModel.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export interface TestModel extends Model {
101101
excludeToEnum?: Exclude<EnumUnion, EnumNumberValue>;
102102
excludeToAlias?: Exclude<ThreeOrFour, TypeAliasModel3>;
103103
// prettier-ignore
104-
excludeLiteral?: Exclude<keyof TestClassModel, 'account' | "defaultValue2" | "indexedTypeToInterface" | 'indexedTypeToClass' | 'indexedTypeToAlias' | 'indexedResponseObject'>;
104+
excludeLiteral?: Exclude<keyof TestClassModel, 'account' | "defaultValue2" | "indexedTypeToInterface" | 'indexedTypeToClass' | 'indexedTypeToAlias' | 'indexedResponseObject' | 'arrayUnion' | 'objectUnion'>;
105105
excludeToInterface?: Exclude<OneOrTwo, TypeAliasModel1>;
106106
excludeTypeToPrimitive?: NonNullable<number | null>;
107107

@@ -605,6 +605,7 @@ interface Indexed {
605605
class: IndexedClass;
606606
}
607607
type IndexType = 'foo';
608+
const fixedArray = ['foo', 'bar'] as const;
608609

609610
const ClassIndexTest = {
610611
foo: ['id'],
@@ -631,6 +632,8 @@ export class TestClassModel extends TestClassBaseModel {
631632
public indexedTypeToAlias?: Indexed['alias'];
632633
public indexedResponse?: IndexRecordAlias<string>['foo'];
633634
public indexedResponseObject?: IndexRecordAlias<{ myProp1: string }>['foo'];
635+
public arrayUnion?: typeof fixedArray[number];
636+
public objectUnion?: Record<string, 'foo' | 'bar'>[string];
634637
/**
635638
* This is a description of a public string property
636639
*

tests/unit/swagger/definitionsGeneration/definitions.spec.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ describe('Definition generation', () => {
392392
},
393393
strLiteralArr: (propertyName, propertySchema) => {
394394
expect(propertySchema.type).to.eq('array', `for property ${propertyName}.type`);
395-
expect(propertySchema!.items!.$ref).to.eq('#/definitions/StrLiteral', `for property ${propertyName}.$ref`);
395+
expect(propertySchema.items!.$ref).to.eq('#/definitions/StrLiteral', `for property ${propertyName}.$ref`);
396396
expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
397397

398398
expect(propertySchema['x-nullable']).to.eq(undefined, `for property ${propertyName}[x-nullable]`);
@@ -722,7 +722,8 @@ describe('Definition generation', () => {
722722
excludeToEnum: { $ref: '#/definitions/Exclude_EnumUnion.EnumNumberValue_', description: undefined, format: undefined, example: undefined },
723723
excludeToAlias: { $ref: '#/definitions/Exclude_ThreeOrFour.TypeAliasModel3_', description: undefined, format: undefined, example: undefined },
724724
excludeLiteral: {
725-
$ref: '#/definitions/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject_',
725+
$ref:
726+
'#/definitions/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject-or-arrayUnion-or-objectUnion_',
726727
description: undefined,
727728
format: undefined,
728729
example: undefined,
@@ -850,7 +851,7 @@ describe('Definition generation', () => {
850851
);
851852

852853
const excludeLiteral = getValidatedDefinition(
853-
'Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject_',
854+
'Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject-or-arrayUnion-or-objectUnion_',
854855
currentSpec,
855856
);
856857
expect(excludeLiteral).to.deep.eq(
@@ -970,6 +971,24 @@ describe('Definition generation', () => {
970971
indexedTypeToClass: { $ref: '#/definitions/IndexedClass', description: undefined, format: undefined, example: undefined },
971972
indexedTypeToInterface: { $ref: '#/definitions/IndexedInterface', description: undefined, format: undefined, example: undefined },
972973
keyInterface: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined, 'x-nullable': false, enum: ['id'] },
974+
arrayUnion: {
975+
default: undefined,
976+
description: undefined,
977+
enum: ['foo', 'bar'],
978+
example: undefined,
979+
format: undefined,
980+
type: 'string',
981+
'x-nullable': false,
982+
},
983+
objectUnion: {
984+
default: undefined,
985+
description: undefined,
986+
enum: ['foo', 'bar'],
987+
example: undefined,
988+
format: undefined,
989+
type: 'string',
990+
'x-nullable': false,
991+
},
973992
optionalPublicConstructorVar: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined },
974993
readonlyConstructorArgument: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined },
975994
publicConstructorVar: { type: 'string', default: undefined, description: 'This is a description for publicConstructorVar', format: undefined, example: undefined },

tests/unit/swagger/schemaDetails3.spec.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
741741
},
742742
strLiteralArr: (propertyName, propertySchema) => {
743743
expect(propertySchema.type).to.eq('array', `for property ${propertyName}.type`);
744-
expect(propertySchema!.items!.$ref).to.eq('#/components/schemas/StrLiteral', `for property ${propertyName}.$ref`);
744+
expect(propertySchema.items!.$ref).to.eq('#/components/schemas/StrLiteral', `for property ${propertyName}.$ref`);
745745
expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
746746

747747
expect(propertySchema.nullable).to.eq(undefined, `for property ${propertyName}[x-nullable]`);
@@ -1051,7 +1051,8 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
10511051
excludeToEnum: { $ref: '#/components/schemas/Exclude_EnumUnion.EnumNumberValue_', description: undefined, format: undefined, example: undefined },
10521052
excludeToAlias: { $ref: '#/components/schemas/Exclude_ThreeOrFour.TypeAliasModel3_', description: undefined, format: undefined, example: undefined },
10531053
excludeLiteral: {
1054-
$ref: '#/components/schemas/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject_',
1054+
$ref:
1055+
'#/components/schemas/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject-or-arrayUnion-or-objectUnion_',
10551056
description: undefined,
10561057
format: undefined,
10571058
example: undefined,
@@ -1271,7 +1272,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
12711272
);
12721273

12731274
const excludeLiteral = getComponentSchema(
1274-
'Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject_',
1275+
'Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias-or-indexedResponseObject-or-arrayUnion-or-objectUnion_',
12751276
currentSpec,
12761277
);
12771278
expect(excludeLiteral).to.deep.eq(
@@ -1397,6 +1398,42 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
13971398
indexedTypeToClass: { $ref: '#/components/schemas/IndexedClass', description: undefined, format: undefined, example: undefined },
13981399
indexedTypeToInterface: { $ref: '#/components/schemas/IndexedInterface', description: undefined, format: undefined, example: undefined },
13991400
indexedTypeToAlias: { $ref: '#/components/schemas/IndexedInterfaceAlias', description: undefined, format: undefined, example: undefined },
1401+
arrayUnion: {
1402+
anyOf: [
1403+
{
1404+
enum: ['foo'],
1405+
nullable: false,
1406+
type: 'string',
1407+
},
1408+
{
1409+
enum: ['bar'],
1410+
nullable: false,
1411+
type: 'string',
1412+
},
1413+
],
1414+
default: undefined,
1415+
description: undefined,
1416+
example: undefined,
1417+
format: undefined,
1418+
},
1419+
objectUnion: {
1420+
anyOf: [
1421+
{
1422+
enum: ['foo'],
1423+
nullable: false,
1424+
type: 'string',
1425+
},
1426+
{
1427+
enum: ['bar'],
1428+
nullable: false,
1429+
type: 'string',
1430+
},
1431+
],
1432+
default: undefined,
1433+
description: undefined,
1434+
example: undefined,
1435+
format: undefined,
1436+
},
14001437
keyInterface: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined, enum: ['id'], nullable: false },
14011438
optionalPublicConstructorVar: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined },
14021439
readonlyConstructorArgument: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined },

0 commit comments

Comments
 (0)