Skip to content

Commit

Permalink
Merge pull request #187 from HPInc/feat/allow-multiple-method-decorator
Browse files Browse the repository at this point in the history
feat: allow multiple http method decorators
  • Loading branch information
Adrià authored Dec 15, 2022
2 parents f6df082 + 80e48bd commit 7015a6a
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 67 deletions.
84 changes: 44 additions & 40 deletions packages/http-server/src/HttpServerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export abstract class HttpServerModule<
return this;
}

public async createRoutes() {
public async createRoutes(): Promise<Route<SMG['Request']>[]> {
const controllersReflection = this.app
.getControllersWithReflection()
.filter(
Expand All @@ -94,62 +94,66 @@ export abstract class HttpServerModule<
reflection.methods.some(m => m.decorators.some(d => d.module === 'http-server'))
);

return mapSeries(controllersReflection, ({ controllerInstance, reflection: controllerReflection }) => {
await mapSeries(controllersReflection, ({ controllerInstance, reflection: controllerReflection }) => {
const controllerDecoratorMetadata: ControllerDecoratorMetadata = controllerReflection.decorators.find(
d => d.module === 'http-server' && d.type === 'controller'
);
const basePath =
controllerDecoratorMetadata?.options?.basePath ?? controllerDecoratorMetadata?.options?.basepath ?? '/';

return mapSeries(controllerReflection.methods, async methodReflection => {
const methodDecoratorMetadata: MethodDecoratorMetadata = methodReflection.decorators.find(
const methodDecoratorMetadatas: Array<MethodDecoratorMetadata> = methodReflection.decorators.filter(
d => d[DecoratorId] === 'http-server.method'
);
const methodName = methodReflection.name;

if (!methodDecoratorMetadata) return null;
if (!methodDecoratorMetadatas.length) return null;

const {
verb,
options: { path }
} = methodDecoratorMetadata;
return mapSeries(methodDecoratorMetadatas, async methodDecoratorMetadata => {
const {
verb,
options: { path }
} = methodDecoratorMetadata;

let fullPath = pathUtils.join(basePath, path);
if (fullPath.length > 1 && fullPath[fullPath.length - 1] === '/') {
fullPath = fullPath.slice(0, -1);
}

const parametersConfig = await this.createParametersConfigurations({
controllerReflection,
methodReflection
});

const responseStatusCodes = Object.keys(methodDecoratorMetadata.options?.responses ?? {})
.reduce((acc, statusCodeString) => {
const statusCode = Number(statusCodeString);
if (!Number.isNaN(statusCode)) {
acc.push(statusCode);
}
return acc;
}, [])
.sort();

const route: Route<SMG['Request']> = {
path: fullPath,
verb,
parametersConfig,
methodDecoratorMetadata,
methodReflection,
controllerDecoratorMetadata,
controllerReflection,
responseStatusCodes
};
let fullPath = pathUtils.join(basePath, path);
if (fullPath.length > 1 && fullPath[fullPath.length - 1] === '/') {
fullPath = fullPath.slice(0, -1);
}

this.routes.push(route);
const parametersConfig = await this.createParametersConfigurations({
controllerReflection,
methodReflection
});

return this[verb](fullPath, await this.createRequestHandler(controllerInstance, methodName, route));
const responseStatusCodes = Object.keys(methodDecoratorMetadata.options?.responses ?? {})
.reduce((acc, statusCodeString) => {
const statusCode = Number(statusCodeString);
if (!Number.isNaN(statusCode)) {
acc.push(statusCode);
}
return acc;
}, [])
.sort();

const route: Route<SMG['Request']> = {
path: fullPath,
verb,
parametersConfig,
methodDecoratorMetadata,
methodReflection,
controllerDecoratorMetadata,
controllerReflection,
responseStatusCodes
};

this.routes.push(route);

return this[verb](fullPath, await this.createRequestHandler(controllerInstance, methodName, route));
});
});
});

return this.routes;
}

public async createRequestHandler(
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server/src/decorators/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const createRouteMethodDecorator = (verb: Verb) => (options: MethodDecoratorOpti
verb,
options
},
{ allowMultiple: false }
{ allowMultiple: true }
);
};

Expand Down
49 changes: 23 additions & 26 deletions packages/http-server/test/unit/HttpServerModule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,29 +141,27 @@ describe('HttpServerModule', () => {
find() {}
@route.post({ path: '/create/', responses: { 201: { description: '' } } })
create() {}
@route.patch({ path: '/:id', responses: { 201: { description: '' } } })
@route.put({ path: '/:id', responses: { 201: { description: '' } } })
update() {}
}
class MyDummyHttpServer extends DummyHttpServer {
get(...args) {
return ['get', ...args];
}
get() {}

post(...args) {
return ['post', ...args];
}
post() {}

patch() {}

put() {}
}
const dummyHttpServer = new MyDummyHttpServer();
const app = new App();
await app.registerController(CustomerController).registerModule(dummyHttpServer);
await app.init();

const [[getRoute, postRoute]] = await dummyHttpServer.createRoutes();
expect(getRoute[0]).to.be.equal('get');
expect(getRoute[1]).to.be.equal('/api/customers/all');
expect(postRoute[0]).to.be.equal('post');
expect(postRoute[1]).to.be.equal('/api/customers/create');
const routes = await dummyHttpServer.createRoutes();

const routesDefinition = dummyHttpServer.getRoutes();
expect(routesDefinition).to.containSubset([
expect(routes).to.containSubset([
{
path: '/api/customers/all',
verb: 'get',
Expand Down Expand Up @@ -193,19 +191,18 @@ describe('HttpServerModule', () => {
path: '/api/customers/create',
verb: 'post',
parametersConfig: [],
methodDecoratorMetadata: {
module: 'http-server',
type: 'route',
verb: 'post',
options: {
path: '/create/',
responses: {
'201': {
description: ''
}
}
}
},
responseStatusCodes: [201]
},
{
path: '/api/customers/:id',
verb: 'put',
parametersConfig: [],
responseStatusCodes: [201]
},
{
path: '/api/customers/:id',
verb: 'patch',
parametersConfig: [],
responseStatusCodes: [201]
}
]);
Expand Down
42 changes: 42 additions & 0 deletions packages/http-server/test/unit/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,48 @@ describe('decorators', () => {
});
});

it('should allow multiple decorators on a single method', () => {
class CustomerController {
@route.get({ path: '/', summary: 'Get customers', description: 'Get a list of customers' })
@route.post({ path: '/', summary: 'Create customers', description: 'Create customers' })
getpost() {}
}

const reflection = reflect(CustomerController);

expect(reflection).to.containSubset({
methods: [
{
kind: 'Method',
name: 'getpost',
parameters: [],
decorators: [
{
module: 'http-server',
type: 'route',
verb: 'get',
options: {
path: '/',
summary: 'Get customers',
description: 'Get a list of customers'
}
},
{
module: 'http-server',
type: 'route',
verb: 'post',
options: {
path: '/',
summary: 'Create customers',
description: 'Create customers'
}
}
]
}
]
});
});

it('should apply the decorator from the class itself, not the one from the super class', () => {
class BaseController {
@route.get({ path: '/', summary: 'Base method', description: 'Base method' })
Expand Down

0 comments on commit 7015a6a

Please sign in to comment.