diff --git a/e2e/api-spec.json b/e2e/api-spec.json index 677c0b477..9ec245529 100644 --- a/e2e/api-spec.json +++ b/e2e/api-spec.json @@ -1409,6 +1409,7 @@ "type": "number" } }, + "description": "ExtraModel description", "required": [ "one", "two" diff --git a/e2e/src/cats/dto/extra-model.dto.ts b/e2e/src/cats/dto/extra-model.dto.ts index c57491ee4..66be96ce1 100644 --- a/e2e/src/cats/dto/extra-model.dto.ts +++ b/e2e/src/cats/dto/extra-model.dto.ts @@ -1,7 +1,8 @@ import { ApiProperty, ApiSchema } from '../../../../lib'; @ApiSchema({ - name: 'ExtraModel' + name: 'ExtraModel', + description: 'ExtraModel description' }) export class ExtraModelDto { @ApiProperty() diff --git a/lib/decorators/api-schema.decorator.ts b/lib/decorators/api-schema.decorator.ts index 98ea6a2b8..99bb843a8 100644 --- a/lib/decorators/api-schema.decorator.ts +++ b/lib/decorators/api-schema.decorator.ts @@ -6,9 +6,14 @@ export interface ApiSchemaOptions extends Pick { /** * Name of the schema. */ - name: string; + name?: string; + + /** + * Description of the schema. + */ + description?: string; } -export function ApiSchema(options: ApiSchemaOptions): ClassDecorator { +export function ApiSchema(options?: ApiSchemaOptions): ClassDecorator { return createClassDecorator(DECORATORS.API_SCHEMA, [options]); } diff --git a/lib/services/schema-object-factory.ts b/lib/services/schema-object-factory.ts index df1381549..9b352a384 100644 --- a/lib/services/schema-object-factory.ts +++ b/lib/services/schema-object-factory.ts @@ -246,6 +246,8 @@ export class SchemaObjectFactory { const extensionProperties = Reflect.getMetadata(DECORATORS.API_EXTENSION, type) || {}; + const { schemaName, schemaProperties } = this.getSchemaMetadata(type); + const typeDefinition: SchemaObject = { type: 'object', properties: mapValues(keyBy(propertiesWithType, 'name'), (property) => { @@ -263,7 +265,8 @@ export class SchemaObjectFactory { return omit(property, [...keysToOmit, 'required']); }) as Record, - ...extensionProperties + ...extensionProperties, + ...schemaProperties }; const typeDefinitionRequiredFields = propertiesWithType @@ -277,23 +280,15 @@ export class SchemaObjectFactory { if (typeDefinitionRequiredFields.length > 0) { typeDefinition['required'] = typeDefinitionRequiredFields; } - const schemaName = this.getSchemaName(type); schemas[schemaName] = typeDefinition; return schemaName; } - getSchemaName(type: Function | Type) { - const customSchema: ApiSchemaOptions[] = Reflect.getOwnMetadata( - DECORATORS.API_SCHEMA, - type - ); - - if (!customSchema || customSchema.length === 0) { - return type.name; - } - - const schemaName = customSchema[customSchema.length - 1].name; - return schemaName ?? type.name; + getSchemaMetadata(type: Function | Type) { + const schemas: ApiSchemaOptions[] = + Reflect.getOwnMetadata(DECORATORS.API_SCHEMA, type) ?? []; + const { name, ...schemaProperties } = schemas[schemas.length - 1] ?? {}; + return { schemaName: name ?? type.name, schemaProperties }; } mergePropertyWithMetadata( diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts index d09714b9a..7db75e122 100644 --- a/test/services/schema-object-factory.spec.ts +++ b/test/services/schema-object-factory.spec.ts @@ -384,50 +384,109 @@ describe('SchemaObjectFactory', () => { }); }); - it('should use schema name instead of class name', () => { - @ApiSchema({ - name: 'CreateUser' - }) - class CreateUserDto {} + describe('@ApiSchema', () => { + it('should use the class name when no options object was passed', () => { + @ApiSchema() + class CreateUserDto {} - const schemas: Record = {}; + const schemas: Record = {}; - schemaObjectFactory.exploreModelSchema(CreateUserDto, schemas); + schemaObjectFactory.exploreModelSchema(CreateUserDto, schemas); - expect(Object.keys(schemas)).toContain('CreateUser'); - }); + expect(Object.keys(schemas)).toContain('CreateUserDto'); + }); - it('should not use schema name of base class', () => { - @ApiSchema({ - name: 'CreateUser' - }) - class CreateUserDto {} + it('should use the class name when the options object is empty', () => { + @ApiSchema({}) + class CreateUserDto {} - class UpdateUserDto extends CreateUserDto {} + const schemas: Record = {}; - const schemas: Record = {}; + schemaObjectFactory.exploreModelSchema(CreateUserDto, schemas); - schemaObjectFactory.exploreModelSchema(UpdateUserDto, schemas); + expect(Object.keys(schemas)).toContain('CreateUserDto'); + }); - expect(Object.keys(schemas)).toContain('UpdateUserDto'); - }); + it('should use the schema name instead of class name', () => { + @ApiSchema({ + name: 'CreateUser' + }) + class CreateUserDto {} - it('should override the schema name of base class', () => { - @ApiSchema({ - name: 'CreateUser' - }) - class CreateUserDto {} + const schemas: Record = {}; - @ApiSchema({ - name: 'UpdateUser' - }) - class UpdateUserDto extends CreateUserDto {} + schemaObjectFactory.exploreModelSchema(CreateUserDto, schemas); - const schemas: Record = {}; + expect(Object.keys(schemas)).toContain('CreateUser'); + }); - schemaObjectFactory.exploreModelSchema(UpdateUserDto, schemas); + it('should not use the schema name of the base class', () => { + @ApiSchema({ + name: 'CreateUser' + }) + class CreateUserDto {} + + class UpdateUserDto extends CreateUserDto {} + + const schemas: Record = {}; + + schemaObjectFactory.exploreModelSchema(UpdateUserDto, schemas); + + expect(Object.keys(schemas)).toContain('UpdateUserDto'); + }); - expect(Object.keys(schemas)).toContain('UpdateUser'); + it('should override the schema name of the base class', () => { + @ApiSchema({ + name: 'CreateUser' + }) + class CreateUserDto {} + + @ApiSchema({ + name: 'UpdateUser' + }) + class UpdateUserDto extends CreateUserDto {} + + const schemas: Record = {}; + + schemaObjectFactory.exploreModelSchema(UpdateUserDto, schemas); + + expect(Object.keys(schemas)).toContain('UpdateUser'); + }); + + it('should use the the description if provided', () => { + @ApiSchema({ + description: 'Represents a user.' + }) + class CreateUserDto {} + + const schemas: Record = {}; + + schemaObjectFactory.exploreModelSchema(CreateUserDto, schemas); + + expect(schemas[CreateUserDto.name].description).toEqual( + 'Represents a user.' + ); + }); + + it('should not use the the description of the base class', () => { + @ApiSchema({ + description: 'Represents a user.' + }) + class CreateUserDto {} + + @ApiSchema({ + description: 'Represents a user update.' + }) + class UpdateUserDto extends CreateUserDto {} + + const schemas: Record = {}; + + schemaObjectFactory.exploreModelSchema(UpdateUserDto, schemas); + + expect(schemas[UpdateUserDto.name].description).toEqual( + 'Represents a user update.' + ); + }); }); it('should include extension properties', () => {