diff --git a/e2e/api-spec.json b/e2e/api-spec.json index cb89d3fa2..fa9615808 100644 --- a/e2e/api-spec.json +++ b/e2e/api-spec.json @@ -1420,6 +1420,25 @@ "REJECTED" ] }, + "XEnumVarnamesTest": { + "type": "number", + "enum": [ + 1, + 2, + 3 + ], + "description": "The x-enum-varnames and x-enum-descriptions test", + "x-enum-varnames": [ + "APPROVED", + "PENDING", + "REJECTED" + ], + "x-enum-descriptions": [ + "Approved State", + "Pending State", + "Rejected State" + ] + }, "CreateCatDto": { "type": "object", "properties": { @@ -1511,6 +1530,14 @@ "$ref": "#/components/schemas/XEnumTest" } ] + }, + "xEnumVarnamesTest": { + "description": "The x-enum-varnames and x-enum-descriptions test", + "allOf": [ + { + "$ref": "#/components/schemas/XEnumVarnamesTest" + } + ] } }, "x-tags": [ @@ -1529,7 +1556,8 @@ "enum", "enumArr", "enumWithRef", - "xEnumTest" + "xEnumTest", + "xEnumVarnamesTest" ] }, "Cat": { diff --git a/e2e/src/cats/dto/create-cat.dto.ts b/e2e/src/cats/dto/create-cat.dto.ts index c3bbc8faf..f4d5d15e3 100644 --- a/e2e/src/cats/dto/create-cat.dto.ts +++ b/e2e/src/cats/dto/create-cat.dto.ts @@ -88,7 +88,16 @@ export class CreateCatDto { description: 'The x-enumNames test', enum: XEnumTest, enumName: 'XEnumTest', - 'x-enumNames': ['APPROVED', 'PENDING', 'REJECTED'] + 'x-enumNames': ['APPROVED', 'PENDING', 'REJECTED'], }) xEnumTest: XEnumTest; + + @ApiProperty({ + description: 'The x-enum-varnames and x-enum-descriptions test', + enum: XEnumTest, + enumName: 'XEnumVarnamesTest', + 'x-enum-varnames': ['APPROVED', 'PENDING', 'REJECTED'], + 'x-enum-descriptions': ['Approved State', 'Pending State', 'Rejected State'] + }) + xEnumVarnamesTest: XEnumTest; } diff --git a/lib/decorators/api-property.decorator.ts b/lib/decorators/api-property.decorator.ts index 74588afe0..9d8095df3 100644 --- a/lib/decorators/api-property.decorator.ts +++ b/lib/decorators/api-property.decorator.ts @@ -9,8 +9,7 @@ import { getEnumType, getEnumValues } from '../utils/enum.utils'; import { createPropertyDecorator, getTypeIsArrayTuple } from './helpers'; export type ApiPropertyCommonOptions = SchemaObjectMetadata & { - 'x-enumNames'?: string[]; - /** + /** * Lazy function returning the type for which the decorated property * can be used as an id * diff --git a/lib/interfaces/open-api-spec.interface.ts b/lib/interfaces/open-api-spec.interface.ts index 9037a1267..a330ac79f 100644 --- a/lib/interfaces/open-api-spec.interface.ts +++ b/lib/interfaces/open-api-spec.interface.ts @@ -233,6 +233,8 @@ export interface SchemaObject { required?: string[]; enum?: any[]; 'x-enumNames'?: string[]; + 'x-enum-varnames'?: string[]; + 'x-enum-descriptions'?: string[]; } export type SchemasObject = Record; diff --git a/lib/services/schema-object-factory.ts b/lib/services/schema-object-factory.ts index 5efa2305f..9e0237fa3 100644 --- a/lib/services/schema-object-factory.ts +++ b/lib/services/schema-object-factory.ts @@ -335,6 +335,11 @@ export class SchemaObjectFactory { ) { const enumName = param.enumName; const $ref = getSchemaPath(enumName); + const enumSchemaExtensions = [ + 'x-enumNames', + 'x-enum-varnames', + 'x-enum-descriptions' + ]; if (!(enumName in schemas)) { const _enum = param.enum @@ -354,7 +359,10 @@ export class SchemaObjectFactory { : param.schema?.['type']) ?? 'string', enum: _enum, ...param.enumSchema, - ...(param['x-enumNames'] ? { 'x-enumNames': param['x-enumNames'] } : {}) + ...enumSchemaExtensions.reduce( + (acc, c) => ({ ...acc, ...(param[c] ? { [c]: param[c] } : {}) }), + {} + ) }; } else { // Enum type is already defined so grab it and @@ -377,8 +385,8 @@ export class SchemaObjectFactory { 'items', 'enumName', 'enum', - 'x-enumNames', - 'enumSchema' + 'enumSchema', + ...enumSchemaExtensions ]); } @@ -396,6 +404,11 @@ export class SchemaObjectFactory { const enumName = metadata.enumName; const $ref = getSchemaPath(enumName); + const enumSchemaExtensions = [ + 'x-enumNames', + 'x-enum-varnames', + 'x-enum-descriptions' + ]; const enumType: string = (metadata.isArray ? metadata.items['type'] : metadata.type) ?? 'string'; @@ -409,7 +422,10 @@ export class SchemaObjectFactory { ? metadata.items['enum'] : metadata.enum, description: metadata.description ?? undefined, - 'x-enumNames': metadata['x-enumNames'] ?? undefined + ...enumSchemaExtensions.reduce( + (acc, c) => ({ ...acc, ...{ [c]: metadata[c] ?? undefined } }), + {} + ) }; } else { if (metadata.enumSchema) { @@ -419,8 +435,10 @@ export class SchemaObjectFactory { }; } - if (metadata['x-enumNames']) { - schemas[enumName]['x-enumNames'] = metadata['x-enumNames']; + for (const extension of enumSchemaExtensions) { + if (metadata[extension]) { + schemas[enumName][extension] = metadata[extension]; + } } } @@ -435,7 +453,12 @@ export class SchemaObjectFactory { : { allOf: [{ $ref }] }; const paramObject = { ..._schemaObject, ...refHost }; - const pathsToOmit = ['enum', 'enumName', 'enumSchema', 'x-enumNames']; + const pathsToOmit = [ + 'enum', + 'enumName', + 'enumSchema', + ...enumSchemaExtensions + ]; if (!metadata.isArray) { pathsToOmit.push('type'); diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts index 1c4427a15..50eafbd3e 100644 --- a/test/services/schema-object-factory.spec.ts +++ b/test/services/schema-object-factory.spec.ts @@ -589,6 +589,25 @@ describe('SchemaObjectFactory', () => { expect(schemas).toEqual({ MyEnum: { enum: [1, 2, 3], type: 'number' } }); }); + + it('should create an enum schema definition with extended attributes', () => { + + const extendedAttributes = { + 'x-enum-varnames': ['APPROVED', 'PENDING', 'REJECTED'], + 'x-enum-descriptions': ['Approved State', 'Pending State', 'Rejected State'], + } + const metadata = { + type: 'number', + enum: [1, 2, 3], + enumName: 'MyEnum', + isArray: false, + ...extendedAttributes + } as const; + const schemas = {}; + schemaObjectFactory.createEnumSchemaType('field', metadata, schemas); + + expect(schemas).toEqual({ MyEnum: { enum: [1, 2, 3], type: 'number', ...extendedAttributes } }); + }); }); describe('createEnumParam', () => { @@ -629,5 +648,28 @@ describe('SchemaObjectFactory', () => { type: 'string' }); }); + + it('should create an enum schema definition with extended attributes', () => { + + const extendedAttributes = { + 'x-enum-varnames': ['APPROVED', 'PENDING', 'REJECTED'], + 'x-enum-descriptions': ['Approved State', 'Pending State', 'Rejected State'], + } + const params: ParamWithTypeMetadata & BaseParameterObject & { 'x-enum-varnames': string[], 'x-enum-descriptions': string[] } = { + required: true, + isArray: true, + enumName: 'MyEnum', + enum: [1, 2, 3], + ...extendedAttributes, + }; + const schemas = {}; + schemaObjectFactory.createEnumParam(params, schemas); + + expect(schemas['MyEnum']).toEqual({ + enum: [1, 2, 3], + type: 'string', + ...extendedAttributes + }); + }); }); });