Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feat/configure-impl…
Browse files Browse the repository at this point in the history
…icit-body-coercion
  • Loading branch information
friedow committed Mar 18, 2024
2 parents ddce959 + efdb468 commit c4821a3
Show file tree
Hide file tree
Showing 21 changed files with 166 additions and 86 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.1.4",
"version": "6.1.5",
"npmClient": "yarn",
"command": {
"version": {
Expand Down
7 changes: 3 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tsoa/cli",
"description": "Build swagger-compliant REST APIs using TypeScript and Node",
"version": "6.1.4",
"version": "6.1.5",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
Expand Down Expand Up @@ -30,7 +30,7 @@
"author": "Luke Autry <lukeautry@gmail.com> (http://www.lukeautry.com)",
"license": "MIT",
"dependencies": {
"@tsoa/runtime": "^6.1.4",
"@tsoa/runtime": "^6.1.5",
"@types/multer": "^1.4.11",
"fs-extra": "^11.2.0",
"glob": "^10.3.10",
Expand All @@ -40,15 +40,14 @@
"ts-deepmerge": "^7.0.0",
"typescript": "^5.3.3",
"validator": "^13.11.0",
"yamljs": "^0.3.0",
"yaml": "^2.4.1",
"yargs": "^17.7.1"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/minimatch": "^5.1.0",
"@types/node": "^18.0.0",
"@types/validator": "^13.11.7",
"@types/yamljs": "^0.2.34",
"@types/yargs": "^17.0.32",
"copyfiles": "^2.4.1",
"rimraf": "^5.0.5"
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
import YAML from 'yamljs';
import YAML from 'yaml';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { Config, RoutesConfig, SpecConfig, Tsoa } from '@tsoa/runtime';
Expand Down Expand Up @@ -52,7 +52,8 @@ const getConfig = async (configPath = 'tsoa.json'): Promise<Config> => {
const ext = extname(configPath);
try {
if (ext === '.yaml' || ext === '.yml') {
config = YAML.load(configPath);
const configRaw = await fsReadFile(`${workingDir}/${configPath}`);
config = YAML.parse(configRaw.toString('utf8'));
} else if (ext === '.js') {
config = await import(`${workingDir}/${configPath}`);
} else {
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/metadataGeneration/methodGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,18 @@ export class MethodGenerator {
}

private getMethodResponses(): Tsoa.Response[] {
const responseExamplesByName: Record<string, any> = {};
const decorators = this.getDecoratorsByIdentifier(this.node, 'Response');
if (!decorators || !decorators.length) {
return [];
}

return decorators.map(decorator => {
const [name, description, example, produces] = getDecoratorValues(decorator, this.current.typeChecker);

responseExamplesByName[name] = responseExamplesByName[name] ? [...responseExamplesByName[name], example] : [example];
return {
description: description || '',
examples: example === undefined ? undefined : [example],
examples: responseExamplesByName[name] || undefined,
name: name || '200',
produces: this.getProducesAdapter(produces),
schema: this.getSchemaFromDecorator(decorator, 0),
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/module/generate-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
import * as YAML from 'yamljs';
import * as YAML from 'yaml';
import { ExtendedSpecConfig } from '../cli';
import { MetadataGenerator } from '../metadataGeneration/metadataGenerator';
import { Tsoa, Swagger, Config } from '@tsoa/runtime';
Expand All @@ -22,7 +22,7 @@ export const generateSpec = async (
* pass in cached metadata returned in a previous step to speed things up
*/
metadata?: Tsoa.Metadata,
defaultNumberType?: Config['defaultNumberType']
defaultNumberType?: Config['defaultNumberType'],
) => {
if (!metadata) {
metadata = new MetadataGenerator(swaggerConfig.entryFile, compilerOptions, ignorePaths, swaggerConfig.controllerPathGlobs, swaggerConfig.rootSecurity, defaultNumberType).Generate();
Expand All @@ -39,7 +39,7 @@ export const generateSpec = async (

let data = JSON.stringify(spec, null, '\t');
if (swaggerConfig.yaml) {
data = YAML.stringify(JSON.parse(data), 10);
data = YAML.stringify(JSON.parse(data));
}

const outputPath = getSwaggerOutputPath(swaggerConfig);
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tsoa/runtime",
"description": "Build swagger-compliant REST APIs using TypeScript and Node",
"version": "6.1.4",
"version": "6.1.5",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
Expand Down Expand Up @@ -30,9 +30,9 @@
"dependencies": {
"@hapi/boom": "^10.0.1",
"@hapi/hapi": "^21.3.3",
"@types/koa": "^2.15.0",
"@types/multer": "^1.4.11",
"express": "^4.18.3",
"koa": "^2.15.0",
"reflect-metadata": "^0.2.1",
"validator": "^13.11.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export class ExpressTemplateService extends TemplateService<ExpressApiHandlerPar
const files = Object.values(args).filter(param => param.dataType === 'file');
if (param.dataType === 'file' && files.length > 0) {
const requestFiles = request.files as { [fileName: string]: Express.Multer.File[] };
if (requestFiles[name] === undefined) {
return undefined;
}

const fileArgs = this.validationService.ValidateParam(param, requestFiles[name], name, fieldErrors, false, undefined);
return fileArgs.length === 1 ? fileArgs[0] : fileArgs;
} else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export class KoaTemplateService extends TemplateService<KoaApiHandlerParameters,
const files = Object.values(args).filter(param => param.dataType === 'file');
const contextRequest = context.request as any;
if (param.dataType === 'file' && files.length > 0) {
if (contextRequest.files[name] === undefined) {
return undefined;
}

const fileArgs = this.validationService.ValidateParam(param, contextRequest.files[name], name, errorFields, false, undefined);
return fileArgs.length === 1 ? fileArgs[0] : fileArgs;
} else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') {
Expand Down
6 changes: 3 additions & 3 deletions packages/tsoa/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tsoa",
"description": "Build swagger-compliant REST APIs using TypeScript and Node",
"version": "6.1.4",
"version": "6.1.5",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
Expand All @@ -28,8 +28,8 @@
"author": "Luke Autry <lukeautry@gmail.com> (http://www.lukeautry.com)",
"license": "MIT",
"dependencies": {
"@tsoa/cli": "^6.1.4",
"@tsoa/runtime": "^6.1.4"
"@tsoa/cli": "^6.1.5",
"@tsoa/runtime": "^6.1.5"
},
"devDependencies": {
"@types/node": "^18.0.0",
Expand Down
2 changes: 1 addition & 1 deletion tests/esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "tsoa-tests-esm",
"private": true,
"description": "Build swagger-compliant REST APIs using TypeScript and Node",
"version": "6.1.0",
"version": "6.1.5",
"type": "module",
"sideEffects": false,
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion tests/esm/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { hrtime } from 'process';

const specESM = async () => {
const result = await generateSpecAndRoutes({
configuration: 'tsoa.json',
configuration: 'tsoa.yaml',
});
return result;
};
Expand Down
34 changes: 0 additions & 34 deletions tests/esm/tsoa.json

This file was deleted.

27 changes: 27 additions & 0 deletions tests/esm/tsoa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
entryFile: "./fixtures/express/server.ts"
noImplicitAdditionalProperties: silently-remove-extras
spec:
outputDirectory: "./dist"
host: localhost:3000
basePath: "/v1"
securityDefinitions:
api_key:
type: apiKey
name: access_token
in: query
tsoa_auth:
type: oauth2
authorizationUrl: http://swagger.io/api/oauth/dialog
flow: implicit
scopes:
write:pets: modify things
read:pets: read things
yaml: true
specVersion: 2
routes:
basePath: "/v1"
routesDir: "./fixtures/express"
middleware: express
authenticationModule: "./fixtures/express/authentication.ts"
esm: true
3 changes: 2 additions & 1 deletion tests/fixtures/controllers/methodController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export class MethodController extends Controller {
return new ModelService().getModel();
}

@Response<ErrorResponseModel>('400', 'Bad Request')
@Response<ErrorResponseModel>('400', 'Bad Request', { status: 400, message: 'reason 1' })
@Response<ErrorResponseModel>('400', 'Bad Request', { status: 400, message: 'reason 2' })
@Response<ErrorResponseModel>('401', 'Unauthorized')
@Response<ErrorResponseModel>('default', 'Unexpected error', { status: 500, message: 'Something went wrong!' })
@Get('MultiResponse')
Expand Down
7 changes: 4 additions & 3 deletions tests/fixtures/controllers/postController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ export class PostTestController {
return [fileA, fileB];
}

@Post('MixedFormDataWithFile')
@Post('MixedFormDataWithFilesContainsOptionalFile')
public async mixedFormDataWithFile(
@FormField('username') username: string,
@UploadedFile('avatar') avatar: File,
): Promise<{ username: string; avatar: File; }> {
return { username, avatar };
@UploadedFile('optionalAvatar') optionalAvatar?: File,
): Promise<{ username: string; avatar: File; optionalAvatar?: File; }> {
return { username, avatar, optionalAvatar };
}

/**
Expand Down
27 changes: 25 additions & 2 deletions tests/integration/express-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1560,16 +1560,17 @@ describe('Express Server', () => {
});
});

it('can post mixed form data content with file', () => {
it('can post mixed form data content with file and not providing optional file', () => {
const formData = {
username: 'test',
avatar: '@../tsconfig.json',
};
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFile`, formData, (_err, res) => {
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
const file = res.body.avatar;
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
const returnedBuffer = Buffer.from(file.buffer);
expect(res.body.username).to.equal(formData.username);
expect(res.body.optionalAvatar).to.undefined;
expect(file).to.not.be.undefined;
expect(file.fieldname).to.be.not.undefined;
expect(file.originalname).to.be.not.undefined;
Expand All @@ -1579,6 +1580,28 @@ describe('Express Server', () => {
});
});

it('can post mixed form data content with file and provides optional file', () => {
const formData = {
username: 'test',
avatar: '@../tsconfig.json',
optionalAvatar: '@../package.json',
};
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
expect(res.body.username).to.equal(formData.username);
for (const fieldName of ['avatar', 'optionalAvatar']) {
const file = res.body[fieldName];
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
const returnedBuffer = Buffer.from(file.buffer);
expect(file).to.not.be.undefined;
expect(file.fieldname).to.be.not.undefined;
expect(file.originalname).to.be.not.undefined;
expect(file.encoding).to.be.not.undefined;
expect(file.mimetype).to.equal('application/json');
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
}
});
});

function verifyFileUploadRequest(
path: string,
formData: any,
Expand Down
27 changes: 25 additions & 2 deletions tests/integration/hapi-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1443,16 +1443,17 @@ describe('Hapi Server', () => {
});
});

it('can post mixed form data content with file', () => {
it('can post mixed form data content with file and not providing optional file', () => {
const formData = {
username: 'test',
avatar: '@../tsconfig.json',
};
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFile`, formData, (_err, res) => {
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
const file = res.body.avatar;
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
const returnedBuffer = Buffer.from(file.buffer);
expect(res.body.username).to.equal(formData.username);
expect(res.body.optionalAvatar).to.undefined;
expect(file).to.not.be.undefined;
expect(file.fieldname).to.be.not.undefined;
expect(file.originalname).to.be.not.undefined;
Expand All @@ -1462,6 +1463,28 @@ describe('Hapi Server', () => {
});
});

it('can post mixed form data content with file and provides optional file', () => {
const formData = {
username: 'test',
avatar: '@../tsconfig.json',
optionalAvatar: '@../package.json',
};
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
expect(res.body.username).to.equal(formData.username);
for (const fieldName of ['avatar', 'optionalAvatar']) {
const file = res.body[fieldName];
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
const returnedBuffer = Buffer.from(file.buffer);
expect(file).to.not.be.undefined;
expect(file.fieldname).to.be.not.undefined;
expect(file.originalname).to.be.not.undefined;
expect(file.encoding).to.be.not.undefined;
expect(file.mimetype).to.equal('application/json');
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
}
});
});

function verifyFileUploadRequest(
path: string,
formData: any,
Expand Down
Loading

0 comments on commit c4821a3

Please sign in to comment.