Skip to content

Commit

Permalink
Merge pull request #55 from codyjdalton/lit-50
Browse files Browse the repository at this point in the history
lit-50: offer next in res class passed to component handler
  • Loading branch information
codyjdalton committed May 18, 2018
2 parents 544e63c + 76ce6c4 commit 0d8b321
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 288 deletions.
328 changes: 150 additions & 178 deletions README.md

Large diffs are not rendered by default.

211 changes: 208 additions & 3 deletions lib/compiler/classes/compiler.class.spec.ts
Expand Up @@ -10,9 +10,10 @@ import { mapTo, delay } from 'rxjs/operators';

import { GetMapping, PostMapping, PutMapping, PatchMapping, DeleteMapping } from '../../http/mappings';
import { Injector } from './injector.class';
import { LitModule, LitComponent } from '../..';
import { LitModule, LitComponent, LitService } from '../..';
import { ServiceCompiler, LitCompiler } from './compiler.class';
import { HttpResponse } from '../../http';
import { HttpRequest, HttpResponse, HttpNext } from '../../http';
import { TestBed, LitComponentTest } from '../../testing';

describe('Class: Compiler', () => {

Expand Down Expand Up @@ -350,4 +351,208 @@ describe('Class: Compiler', () => {
done();
});
});
});

it('should inject res if the handler has a single param', (done) => {

@LitComponent()
class TestComponent {

@GetMapping()
getItems(res: HttpResponse): void {
res.success({ message: 'succeeded' });
}
}

let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/')
.expect(200)
.expect((res) => {
expect(res.body.message).to.equal('succeeded');
})
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});

it('should inject req and res if the handler has two params', (done) => {

@LitComponent()
class TestComponent {

@GetMapping({
path: ':id'
})
getItems(req: HttpRequest, res: HttpResponse): void {
res.success({ message: req.params.id });
}
}

let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/another-test')
.expect(200)
.expect((res) => {
expect(res.body.message).to.equal('another-test');
})
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});

it('should inject req, res, and next if the handler has three params', (done) => {

@LitComponent()
class TestComponent {

@GetMapping({
path: ':id'
})
getItems(req: HttpRequest, res: HttpResponse, next: HttpNext): void {

if(req.params.id === 'test') {
res.success({ message: req.params.id });
return;
}

next();
}

@GetMapping({
path: ':id'
})
getItemsErr(res: HttpResponse): void {
res.errored(404, { message: 'error' });
}
}

let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/yet-another-test')
.expect(404)
.expect((res) => {
expect(res.body.message).to.equal('error');
})
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});

it('should only handle the next route if next is called', (done) => {

@LitComponent()
class TestComponent {

@GetMapping({
path: ':id'
})
getItems(req: HttpRequest, res: HttpResponse, next: HttpNext): void {

if(req.params.id === 'test') {
res.success({ message: req.params.id });
return;
}

next();
}

@GetMapping({
path: ':id'
})
getItemsErr(res: HttpResponse): void {
res.errored(404, { message: 'error' });
}
}


let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/test')
.expect(200)
.expect((res) => {
expect(res.body.message).to.equal('test');
})
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});

it('should return 404 if no params are defined', (done) => {

@LitComponent()
class TestComponent {

@GetMapping({
path: ':id'
})
getItems(): void {

// I'm not really sure what they are doing here
// but hey, someone will do it
// we want to fail if this method is called
// and instead send them to the default component
// which will throw a 501 err
expect(true).to.be.false;
}
}

let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/test')
.expect(501)
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});

it('should inject dependencies', (done) => {

@LitService()
class TestService {
message: string = 'test-val'
}

@LitComponent()
class TestComponent {

constructor(private testService: TestService) {
}

@GetMapping()
getItems(res: HttpResponse) {
res.success({
message: this.testService.message
});
}
}

let component: LitComponentTest = TestBed.start(TestComponent);

component
.get('/')
.expect(200)
.expect(res => {
expect(res.body.message).to.equal('test-val');
})
.end(function(err, res) {
TestBed.stop();
if (err) return done(err);
done();
});
});
});
47 changes: 28 additions & 19 deletions lib/compiler/classes/compiler.class.ts
Expand Up @@ -6,12 +6,13 @@ import BodyParser = require('body-parser');

import { Application, RequestHandler } from 'express';


import { CoreCompiler, ILitComponent, ILitModule } from '../utils/compiler.utils';
import { DefaultComponent } from '../components/default.component';
import { HttpServer } from '../../http/utils/http.utils';
import { RequestMethod } from '../../http/enums/request-method.enum';
import { HttpResponse } from '../../http/classes/response.class';
import { HttpNext } from '../../http/models/next.model';
import { Injector } from './injector.class';
import { RequestMethod } from '../../http/enums/request-method.enum';

export class ServiceCompiler extends CoreCompiler {

Expand Down Expand Up @@ -115,40 +116,45 @@ export class ServiceCompiler extends CoreCompiler {

/**
* @function getHandler
* @param {ILitComponent} aComponent
* @param {ILitComponent} component
* @param {string} name
*/
private getHandler(aComponent: ILitComponent, name: string): RequestHandler {
private getHandler(component: ILitComponent, name: string): RequestHandler {
return (req: express.Request,
res: express.Response,
next: express.NextFunction): RequestHandler => {
next: HttpNext): RequestHandler => {

// include metadata to send with response
const meta: Object = Injector.getAll(
aComponent,
component.prototype,
name
);

return aComponent[name](req, new HttpResponse(res, meta));
const wrappedRes: HttpResponse = new HttpResponse(res, meta);
const paramLen: number = Injector.getParams(component, name).length;
const aComponent: ILitComponent = Injector.resolve(component);

return paramLen === 1 ? aComponent[name](wrappedRes) :
paramLen === 2 ? aComponent[name](req, wrappedRes) :
paramLen === 3 ? aComponent[name](req, wrappedRes, next) :
DefaultComponent.prototype.notImplemented(wrappedRes);
};
}

/**
* @function addRoute
* @param {string} method
* @param {string} path
* @param {string} aComponent
* @param {string} component
* @param {string} name
* Usage:
*
* LitCompiler.addRoute('post', 'some/route' aComponent, 'someMethod');
* LitCompiler.addRoute('post', 'some/route' SomeComponent, 'someMethod');
*
* Sets:
* POST /some/route
* adds handler aComponent.someMethod
* adds handler SomeComponent.someMethod
*/
private addRoute(method: RequestMethod, path: string, aComponent: ILitComponent, name: string): void {
this.app[method]('/' + path, this.getHandler(aComponent, name));
private addRoute(method: RequestMethod, path: string, component: ILitComponent, name: string): void {
this.app[method]('/' + path, this.getHandler(component, name));
}

/**
Expand All @@ -167,19 +173,22 @@ export class ServiceCompiler extends CoreCompiler {
*/
private addRouteFromMethod(component: ILitComponent, method: string, path: string) {

// get a new instance of the component
const aComponent: ILitComponent = Injector.resolve(component);
const reqMethod: RequestMethod = Injector.get(aComponent, 'method', null, method);
const reqMethod: RequestMethod = Injector.get(
component.prototype,
'method',
null,
method
);

// check if method is elligible for route and add
if(reqMethod) {

path = this.getPath([
path,
Injector.get(aComponent, 'path', null, method)
Injector.get(component.prototype, 'path', null, method)
]);

this.addRoute(reqMethod, path, aComponent, method);
this.addRoute(reqMethod, path, component, method);
}
}

Expand Down
27 changes: 27 additions & 0 deletions lib/compiler/classes/injector.class.spec.ts
Expand Up @@ -106,4 +106,31 @@ describe('Class: Injector', () => {
expect(metadata.path).to.equal(expectedMetadata.path);
expect(metadata.produces).to.equal(expectedMetadata.produces);
});

it('should return a list of param types', () => {

@LitComponent()
class TestComponent {

@GetMapping()
resOnly(res) {

}

@GetMapping()
reqRes(req, res) {

}

@GetMapping()
reqResNext(req, res, next) {

}
}

expect(Injector.getParams(TestComponent, 'noMethod').length).to.equal(0);
expect(Injector.getParams(TestComponent, 'resOnly').length).to.equal(1);
expect(Injector.getParams(TestComponent, 'reqRes').length).to.equal(2);
expect(Injector.getParams(TestComponent, 'reqResNext').length).to.equal(3);
});
});
4 changes: 4 additions & 0 deletions lib/compiler/classes/injector.class.ts
Expand Up @@ -56,4 +56,8 @@ export const Injector = new class {
}, {}
);
}

getParams(target: Type<any>, propertyKey: string) {
return Reflect.getMetadata('design:paramtypes', target.prototype, propertyKey) || [];
}
};
14 changes: 14 additions & 0 deletions lib/compiler/components/default.component.ts
@@ -0,0 +1,14 @@
/**
* default.component
*/
import { HttpResponse } from "../../http";
import { LitComponent } from "../..";

@LitComponent()
export class DefaultComponent {

notImplemented(res: HttpResponse): void {

res.errored(501);
}
}

0 comments on commit 0d8b321

Please sign in to comment.