diff --git a/src/decorators.ts b/src/decorators.ts index a8c8c52..fdbfc01 100644 --- a/src/decorators.ts +++ b/src/decorators.ts @@ -115,3 +115,26 @@ export function IsFloat(target: any, propertyKey: string, parameterIndex?: numbe export function IsDouble(target: any, propertyKey: string, parameterIndex?: number) { return; } + +/** + * Creates a mapping between a file on a multipart request and a method + * argument. + * Unlike @FileParam provided by typescript-rest, this decorator allows to pipe the request. + */ +export function StreamFileParam(name: string) { + return function (...args: any[]) { + return; + }; +} + +/** + * Creates a mapping between multiple files on a multipart request and a method + * argument. + * Unlike @FileParam provided by typescript-rest, this decorator allows to pipe the request. + */ +export function StreamFilesParam(name: string) { + return function (...args: any[]) { + return; + }; +} + diff --git a/src/metadata/parameterGenerator.ts b/src/metadata/parameterGenerator.ts index 471c5e4..151f287 100644 --- a/src/metadata/parameterGenerator.ts +++ b/src/metadata/parameterGenerator.ts @@ -31,6 +31,10 @@ export class ParameterGenerator { return this.getFileParameter(this.parameter); case 'FilesParam': return this.getFilesParameter(this.parameter); + case 'StreamFileParam': + return this.getFileParameter(this.parameter); + case 'StreamFilesParam': + return this.getFilesParameter(this.parameter); case 'Context': case 'ContextRequest': case 'ContextResponse': @@ -89,7 +93,7 @@ export class ParameterGenerator { return { description: this.getParameterDescription(parameter), in: 'formData', - name: getDecoratorTextValue(this.parameter, ident => ident.text === 'FileParam') || parameterName, + name: getDecoratorTextValue(this.parameter, ident => ident.text === 'FileParam' || ident.text === 'StreamFileParam') || parameterName, parameterName: parameterName, required: !parameter.questionToken, type: { typeName: 'file' } @@ -106,7 +110,7 @@ export class ParameterGenerator { return { description: this.getParameterDescription(parameter), in: 'formData', - name: getDecoratorTextValue(this.parameter, ident => ident.text === 'FilesParam') || parameterName, + name: getDecoratorTextValue(this.parameter, ident => ident.text === 'FilesParam' || ident.text === 'StreamFilesParam') || parameterName, parameterName: parameterName, required: !parameter.questionToken, type: { typeName: 'file' } @@ -255,7 +259,7 @@ export class ParameterGenerator { return ['HeaderParam', 'QueryParam', 'Param', 'FileParam', 'PathParam', 'FilesParam', 'FormParam', 'CookieParam', 'Context', 'ContextRequest', 'ContextResponse', 'ContextNext', - 'ContextLanguage', 'ContextAccept'].some(d => d === decoratorName); + 'ContextLanguage', 'ContextAccept', 'StreamFileParam', 'StreamFilesParam'].some(d => d === decoratorName); } private supportPathDataType(parameterType: Type) { diff --git a/test/data/apis.ts b/test/data/apis.ts index c3b108a..049c339 100644 --- a/test/data/apis.ts +++ b/test/data/apis.ts @@ -1,8 +1,9 @@ 'use strict'; import { - Accept, DELETE, FormParam, GET, Path, - PathParam, POST, PUT, QueryParam, + Accept, DELETE, FileParam, FormParam, GET, + Path, PathParam, POST, PUT, + QueryParam, Return, Security } from 'typescript-rest'; @@ -399,6 +400,18 @@ export class ParameterizedEndpoint { public test(@PathParam('objectId') objectId: string): PrimitiveClassModel { return new PrimitiveClassModel(); } + + @Path('/file') + @POST + public file(@FileParam('file') file: Express.Multer.File): PrimitiveClassModel { + return new PrimitiveClassModel(); + } + + @Path('/stream') + @POST + public stream(@swagger.StreamFileParam('stream') file: Express.Multer.File): PrimitiveClassModel { + return new PrimitiveClassModel(); + } } export abstract class Entity { diff --git a/test/unit/definitions.spec.ts b/test/unit/definitions.spec.ts index e64bed5..9ad179f 100644 --- a/test/unit/definitions.spec.ts +++ b/test/unit/definitions.spec.ts @@ -358,6 +358,26 @@ describe('Definition generation', () => { const expression = jsonata('paths."/parameterized/{objectId}/test".get.parameters[0].in'); expect(expression.evaluate(spec)).toEqual('path'); }); + + it('should generate formData param for params declared on method', () => { + const expression = jsonata('paths."/parameterized/{objectId}/file".post.parameters[0].in'); + expect(expression.evaluate(spec)).toEqual('formData'); + }); + + it('should generate path param for params declared on class', () => { + const expression = jsonata('paths."/parameterized/{objectId}/stream".post.parameters[0].in'); + expect(expression.evaluate(spec)).toEqual('formData'); + }); + + it('should generate formData param for params declared on method', () => { + const expression = jsonata('paths."/parameterized/{objectId}/file".post.parameters[0].in'); + expect(expression.evaluate(spec)).toEqual('formData'); + }); + + it('should generate path param for params declared on class', () => { + const expression = jsonata('paths."/parameterized/{objectId}/stream".post.parameters[0].in'); + expect(expression.evaluate(spec)).toEqual('formData'); + }); }); describe('AbstractEntityEndpoint', () => {