From e2b6715db580688bc798b3282bc264dc01f8727f Mon Sep 17 00:00:00 2001 From: Arnaud Cortisse Date: Mon, 28 Dec 2020 16:37:29 +0100 Subject: [PATCH] fix: arrays and objects badly managed when not body type --- lib/services/schema-object-factory.ts | 75 ++++++------- test/explorer/swagger-explorer.spec.ts | 112 ++++++++++++++++++++ test/services/schema-object-factory.spec.ts | 80 ++++++++++++++ 3 files changed, 223 insertions(+), 44 deletions(-) diff --git a/lib/services/schema-object-factory.ts b/lib/services/schema-object-factory.ts index 011c0a697..001976562 100644 --- a/lib/services/schema-object-factory.ts +++ b/lib/services/schema-object-factory.ts @@ -62,36 +62,40 @@ export class SchemaObjectFactory { return this.createQueryOrParamSchema(param, schemas, schemaRefsStack); } - const modelName = this.exploreModelSchema( - param.type, - schemas, - schemaRefsStack - ); - const name = param.name || modelName; - const schema = { - ...((param as BaseParameterObject).schema || {}), - $ref: getSchemaPath(modelName) - }; - const isArray = param.isArray; - param = omit(param, 'isArray'); - - if (isArray) { - return { - ...param, - name, - schema: { - type: 'array', - items: schema - } - }; - } + return this.getCustomType(param, schemas, schemaRefsStack); + }); + return flatten(parameterObjects); + } + + getCustomType(param, schemas, schemaRefsStack) { + const modelName = this.exploreModelSchema( + param.type, + schemas, + schemaRefsStack + ); + const name = param.name || modelName; + const schema = { + ...((param as BaseParameterObject).schema || {}), + $ref: getSchemaPath(modelName) + }; + const isArray = param.isArray; + param = omit(param, 'isArray'); + + if (isArray) { return { ...param, name, - schema + schema: { + type: 'array', + items: schema + } }; - }); - return flatten(parameterObjects); + } + return { + ...param, + name, + schema + }; } createQueryOrParamSchema( @@ -111,24 +115,7 @@ export class SchemaObjectFactory { }; } if (isFunction(param.type)) { - const propertiesWithType = this.extractPropertiesFromType( - param.type, - schemas, - schemaRefsStack - ); - if (!propertiesWithType) { - return param; - } - return propertiesWithType.map( - (property: ParameterObject & ParamWithTypeMetadata) => { - const parameterObject = { - ...(omit(property, 'enumName') as ParameterObject), - in: 'query', - required: property.required ?? true - }; - return parameterObject; - } - ) as ParameterObject[]; + return this.getCustomType(param, schemas, schemaRefsStack); } return param; } diff --git a/test/explorer/swagger-explorer.spec.ts b/test/explorer/swagger-explorer.spec.ts index 2fd9b6b62..5d9b5614d 100644 --- a/test/explorer/swagger-explorer.spec.ts +++ b/test/explorer/swagger-explorer.spec.ts @@ -971,4 +971,116 @@ describe('SwaggerExplorer', () => { ]); }); }); + + describe('when arrays are used', () => { + enum LettersEnum { + A = 'A', + B = 'B', + C = 'C' + } + + class NestedDto { + @ApiProperty() + nestedString: string; + } + + class ObjectDto { + @ApiProperty() + field: string; + + @ApiProperty() + nestedObject: NestedDto; + + @ApiProperty({ + isArray: true, + type: NestedDto + }) + nestedArrayOfObjects: NestedDto[]; + + @ApiProperty({ + type: [NestedDto] + }) + nestedArrayOfObjects2: NestedDto[]; + } + + class FooDto { + @ApiProperty({ + isArray: true, + type: ObjectDto + }) + arrayOfObjectsDto: ObjectDto[]; + } + + class FooController { + @Get('/route1') + route1(@Query() fooDto: FooDto) {} + @Get('/route2') + route2(@Query() objectDto: ObjectDto) {} + } + + it('should properly define arrays in query', () => { + const explorer = new SwaggerExplorer(schemaObjectFactory); + const routes = explorer.exploreController( + { + instance: new FooController(), + metatype: FooController + } as InstanceWrapper, + 'path' + ); + + expect(routes[0].root.parameters).toEqual([ + { + name: 'arrayOfObjectsDto', + required: true, + in: 'query', + schema: { + items: { + $ref: '#/components/schemas/ObjectDto' + }, + type: 'array' + } + } + ]); + expect(routes[1].root.parameters).toEqual([ + { + name: 'field', + required: true, + in: 'query', + schema: { + type: 'string' + } + }, + { + name: 'nestedObject', + required: true, + in: 'query', + schema: { + $ref: '#/components/schemas/NestedDto' + } + }, + { + name: 'nestedArrayOfObjects', + required: true, + in: 'query', + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/NestedDto' + } + } + }, + { + name: 'nestedArrayOfObjects2', + required: true, + in: 'query', + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/NestedDto' + } + } + } + ]); + }); + }); }); diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts index d580fce13..79d29af5c 100644 --- a/test/services/schema-object-factory.spec.ts +++ b/test/services/schema-object-factory.spec.ts @@ -1,5 +1,7 @@ import { ApiProperty } from '../../lib/decorators'; +import { SchemaObject } from '../../lib/interfaces/open-api-spec.interface'; import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor'; +import { ParamWithTypeMetadata } from '../../lib/services/parameter-metadata-accessor'; import { SchemaObjectFactory } from '../../lib/services/schema-object-factory'; import { SwaggerTypesMapper } from '../../lib/services/swagger-types-mapper'; import { CreateUserDto } from './fixtures/create-user.dto'; @@ -234,5 +236,83 @@ describe('SchemaObjectFactory', () => { properties: { name: { type: 'string', minLength: 1 } } }); }); + + it('should create arrays of objects', () => { + class ObjectDto { + @ApiProperty() + field: string; + } + + class TestDto { + @ApiProperty() + arrayOfStrings: string[]; + } + + class Test2Dto { + @ApiProperty({ + isArray: true, + type: ObjectDto + }) + arrayOfObjects: ObjectDto[]; + } + + const schemas = []; + schemaObjectFactory.exploreModelSchema(TestDto, schemas); + schemaObjectFactory.exploreModelSchema(Test2Dto, schemas); + + expect(schemas[0][TestDto.name]).toEqual({ + type: 'object', + properties: { + arrayOfStrings: { + type: 'array', + items: { + type: 'string' + } + } + }, + required: ['arrayOfStrings'] + }); + expect(schemas[2][Test2Dto.name]).toEqual({ + type: 'object', + properties: { + arrayOfObjects: { + type: 'array', + items: { + $ref: '#/components/schemas/ObjectDto' + } + } + }, + required: ['arrayOfObjects'] + }); + }); + }); + + describe('createFromModel', () => { + it('should create arrays of objects', () => { + class ObjectDto { + @ApiProperty() + field: string; + } + + class TestDto { + @ApiProperty() + arrayOfStrings: string[]; + } + + class Test2Dto { + @ApiProperty({}) + arrayOfObjects: ObjectDto[]; + } + + const property = ApiProperty({ + isArray: true, + type: ObjectDto + }); + + const parameters: ParamWithTypeMetadata[] = []; + + const schemas: SchemaObject[] = []; + const result = schemaObjectFactory.createFromModel(parameters, schemas); + }); }); });