Skip to content

Commit

Permalink
Merge pull request #885 from vincentvanderweele/feature/allow-path-pa…
Browse files Browse the repository at this point in the history
…rameters-on-controllers

Feat: allow path parameters on controllers
  • Loading branch information
WoH committed Jan 16, 2021
2 parents 0579f62 + b4f5566 commit d6c758c
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/cli/src/metadataGeneration/controllerGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class ControllerGenerator {
private buildMethods() {
return this.node.members
.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration)
.map((m: ts.MethodDeclaration) => new MethodGenerator(m, this.current, this.commonResponses, this.tags, this.security, this.isHidden))
.map((m: ts.MethodDeclaration) => new MethodGenerator(m, this.current, this.commonResponses, this.path, this.tags, this.security, this.isHidden))
.filter(generator => generator.IsValid())
.map(generator => generator.Generate());
}
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/metadataGeneration/methodGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ts from 'typescript';
import * as path from 'path';
import { isVoidType } from '../utils/isVoidType';
import { getDecorators, getDecoratorValues, getSecurites } from './../utils/decoratorUtils';
import { getJSDocComment, getJSDocDescription, isExistJSDocTag } from './../utils/jsDocUtils';
Expand All @@ -19,6 +20,7 @@ export class MethodGenerator {
private readonly node: ts.MethodDeclaration,
private readonly current: MetadataGenerator,
private readonly commonResponses: Tsoa.Response[],
private readonly parentPath?: string,
private readonly parentTags?: string[],
private readonly parentSecurity?: Tsoa.Security[],
private readonly isParentHidden?: boolean,
Expand Down Expand Up @@ -70,10 +72,11 @@ export class MethodGenerator {
}

private buildParameters() {
const fullPath = path.join(this.parentPath || '', this.path);
const parameters = this.node.parameters
.map(p => {
try {
return new ParameterGenerator(p, this.method, this.path, this.current).Generate();
return new ParameterGenerator(p, this.method, fullPath, this.current).Generate();
} catch (e) {
const methodId = this.node.name as ts.Identifier;
const controllerId = (this.node.parent as ts.ClassDeclaration).name as ts.Identifier;
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/routeGeneration/routeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class RouteGenerator {
basePath: normalisedBasePath,
canImportByAlias,
controllers: this.metadata.controllers.map(controller => {
const normalisedControllerPath = normalisePath(controller.path, '/');
const normalisedControllerPath = pathTransformer(normalisePath(controller.path, '/'));

return {
actions: controller.methods.map(method => {
Expand Down
27 changes: 27 additions & 0 deletions tests/fixtures/controllers/subresourceController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Get, Path, Route } from '@tsoa/runtime';

@Route('SubResourceTest/{mainResourceId}/SubResource')
export class SubResourceTestController {
@Get()
public async getSubResource(@Path('mainResourceId') mainResourceId: string): Promise<string> {
return mainResourceId;
}

@Get('{subResourceId}')
public async getWithParameter(@Path('mainResourceId') mainResourceId: string, @Path('subResourceId') subResourceId: string): Promise<string> {
return `${mainResourceId}:${subResourceId}`;
}
}

@Route('SubResourceColonTest/:mainResourceId/SubResource')
export class SubResourceColonTestController {
@Get()
public async getSubResource(@Path('mainResourceId') mainResourceId: string): Promise<string> {
return mainResourceId;
}

@Get('{subResourceId}')
public async getWithParameter(@Path('mainResourceId') mainResourceId: string, @Path('subResourceId') subResourceId: string): Promise<string> {
return `${mainResourceId}:${subResourceId}`;
}
}
1 change: 1 addition & 0 deletions tests/fixtures/express/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import '../controllers/validateController';
import '../controllers/exampleController';
import '../controllers/tagController';
import '../controllers/noExtendsController';
import '../controllers/subresourceController';

import { RegisterRoutes } from './routes';

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/hapi/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../controllers/securityController';
import '../controllers/testController';
import '../controllers/validateController';
import '../controllers/noExtendsController';
import '../controllers/subresourceController';

import { RegisterRoutes } from './routes';

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/koa/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../controllers/securityController';
import '../controllers/testController';
import '../controllers/validateController';
import '../controllers/noExtendsController';
import '../controllers/subresourceController';

import * as bodyParser from 'koa-bodyparser';
import { RegisterRoutes } from './routes';
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/express-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,25 @@ describe('Express Server', () => {
});
});

describe('Sub resource', () => {
it('parses path parameters from the controller description', () => {
const mainResourceId = 'main-123';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource`, (err, res) => {
expect(res.body).to.equal(mainResourceId);
});
});

it('parses path parameters from the controller description and method description', () => {
const mainResourceId = 'main-123';
const subResourceId = 'sub-456';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource/${subResourceId}`, (err, res) => {
expect(res.body).to.equal(`${mainResourceId}:${subResourceId}`);
});
});
});

function verifyGetRequest(path: string, verifyResponse: (err: any, res: request.Response) => any, expectedStatus?: number) {
return verifyRequest(verifyResponse, request => request.get(path), expectedStatus);
}
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/hapi-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,25 @@ describe('Hapi Server', () => {
});
});

describe('Sub resource', () => {
it('parses path parameters from the controller description', () => {
const mainResourceId = 'main-123';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource`, (err, res) => {
expect(res.text).to.equal(mainResourceId);
});
});

it('parses path parameters from the controller description and method description', () => {
const mainResourceId = 'main-123';
const subResourceId = 'sub-456';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource/${subResourceId}`, (err, res) => {
expect(res.text).to.equal(`${mainResourceId}:${subResourceId}`);
});
});
});

function verifyGetRequest(path: string, verifyResponse: (err: any, res: request.Response) => any, expectedStatus?: number) {
return verifyRequest(verifyResponse, request => request.get(path), expectedStatus);
}
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/koa-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,25 @@ describe('Koa Server', () => {
});
});

describe('Sub resource', () => {
it('parses path parameters from the controller description', () => {
const mainResourceId = 'main-123';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource`, (err, res) => {
expect(res.text).to.equal(mainResourceId);
});
});

it('parses path parameters from the controller description and method description', () => {
const mainResourceId = 'main-123';
const subResourceId = 'sub-456';

return verifyGetRequest(basePath + `/SubResourceTest/${mainResourceId}/SubResource/${subResourceId}`, (err, res) => {
expect(res.text).to.equal(`${mainResourceId}:${subResourceId}`);
});
});
});

it('shutdown server', () => server.close());

function verifyGetRequest(path: string, verifyResponse: (err: any, res: request.Response) => any, expectedStatus?: number) {
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/swagger/pathGeneration/subResourceRoutes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'mocha';
import { expect } from 'chai';
import { MetadataGenerator } from '@tsoa/cli/metadataGeneration/metadataGenerator';
import { Tsoa } from '@tsoa/runtime';

describe('Sub resource route generation', () => {
const metadata = new MetadataGenerator('./fixtures/controllers/subresourceController.ts').Generate();

const variants = [
{
name: 'Using brackets',
baseRoute: 'SubResourceTest/{mainResourceId}/SubResource',
},
{
name: 'Using colon',
baseRoute: 'SubResourceColonTest/:mainResourceId/SubResource',
},
];

variants.forEach(({ name, baseRoute }) => {
describe(name, () => {
const getParameters = (methodPath: string) => {
const controller = metadata.controllers.find(c => c.path === baseRoute);
if (!controller) {
throw new Error(`Missing controller for ${baseRoute}`);
}

const method = controller.methods.find(m => m.path === methodPath);
if (!method) {
throw new Error('Unknown method ');
}

return method.parameters;
};

const validatePathParameter = (parameters: Tsoa.Parameter[], name: string) => {
const parameter = parameters.find(p => p.name === name);
expect(parameter, `Path parameter '${name}' wasn't generated.`).to.exist;
expect(parameter!.in).to.equal('path');
expect(parameter!.type).to.eql({ dataType: 'string' });
};

it('should generate a path parameter for method without path parameter', () => {
const parameters = getParameters('');
validatePathParameter(parameters, 'mainResourceId');
});

it('should generate two path parameters for method with path parameter', () => {
const parameters = getParameters('{subResourceId}');
validatePathParameter(parameters, 'mainResourceId');
validatePathParameter(parameters, 'subResourceId');
});
});
});
});

0 comments on commit d6c758c

Please sign in to comment.