Skip to content

Commit

Permalink
feat(arrest): Added default and options to the API constructor, more …
Browse files Browse the repository at this point in the history
…debug options
  • Loading branch information
0xfede committed Mar 21, 2017
1 parent c9e665c commit 9d9c2f6
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 51 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "arrest",
"version": "2.3.0-beta.39",
"version": "2.3.0-beta.40",
"description": "REST framework for Node.js, Express and MongoDB",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -96,7 +96,7 @@
"path": "node_modules/cz-conventional-changelog"
},
"ghooks": {
"_pre-commit": "npm run build && npm run cover && npm run check-coverage"
"pre-commit": "npm run build && npm run cover && npm run check-coverage"
}
},
"engines": {
Expand Down
86 changes: 55 additions & 31 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ import { SchemaRegistry } from './schema';
import { Swagger } from './swagger';
import { Scopes } from './scopes';
import { Resource } from './resource';
import {escape} from "querystring";

const logger = debug('arrest');
const __logger = Symbol();
const __options = Symbol();
const __schemas = Symbol();
const __registry = Symbol();
const __resources = Symbol();
const __router = Symbol();
const __default_swagger = {
swagger: '2.0',
info: { version: '1.0.0' },
schemes: [ "https", "http" ],
consumes: [ "application/json" ],
produces: [ "application/json" ],
Expand Down Expand Up @@ -144,6 +145,9 @@ const __default_schema_tag: Swagger.Tag = {
"description": "JSON-Schema definitions used by this API",
"x-name-plural": "Schemas"
};
const __default_options: APIOptions = {
swagger: true
}

let reqId: number = 0;

Expand All @@ -158,6 +162,11 @@ export interface APIRequestHandler extends RequestHandler {
(req: APIRequest, res: APIResponse, next: NextFunction): any;
}

export interface APIOptions {
swagger?: boolean;
registry?: SchemaRegistry;
}

export class API implements Swagger {
swagger;
info;
Expand All @@ -175,15 +184,17 @@ export class API implements Swagger {
tags?: Swagger.Tag[];
externalDocs?: Swagger.ExternalDocs;

constructor(info:Swagger, registry:SchemaRegistry = new SchemaRegistry()) {
delete info.paths;
delete info.tags;
Object.assign(this, (new Eredita(info, new Eredita(_.cloneDeep(__default_swagger)))).mergePath());
constructor(info?:Swagger, options?:APIOptions) {
this[__logger] = debug(this.getDebugLabel());
Object.assign(this, (new Eredita(info || {}, new Eredita(_.cloneDeep(__default_swagger)))).mergePath());
delete this.paths;
delete this.tags;
if (!semver.valid(this.info.version)) {
throw new Error('invalid_version');
}
this[__options] = Object.assign({}, (new Eredita(options || {}, new Eredita(_.cloneDeep(__default_options)))).mergePath());
this[__schemas] = null;
this[__registry] = registry;
this[__registry] = this.options.registry || new SchemaRegistry();
this[__resources] = [];
}

Expand All @@ -194,6 +205,16 @@ export class API implements Swagger {
return this[__resources];
}

get logger(): Logger {
return this[__logger];
}
get options(): APIOptions {
return this[__options];
}

protected getDebugLabel(): string {
return 'arrest';
}
protected getDebugContext(): string {
return `#${++reqId}`;
}
Expand Down Expand Up @@ -273,40 +294,43 @@ export class API implements Swagger {

router(options?: RouterOptions): Promise<Router> {
if (!this[__router]) {
this.logger.info('creating router');
let r = Router(options);
r.use((_req: Request, res: Response, next: NextFunction) => {
let req: APIRequest = _req as APIRequest;
if (!req.logger) {
req.logger = debug('arrest', this.getDebugContext());
req.logger = debug(this.getDebugLabel(), this.getDebugContext());
}
next();
});
r.use(this.securityValidator.bind(this));
let originalSwagger:Swagger = _.cloneDeep(this) as Swagger;
r.get('/swagger.json', (req: APIRequest, res: APIResponse, next: NextFunction) => {
let out:any = _.cloneDeep(originalSwagger);
if (!req.headers['host']) {
next(API.newError(400, 'Bad Request', 'Missing Host header in the request'));
} else {
out.host = req.headers['host'];
out.basePath = req.baseUrl;
let proto = this.schemes && this.schemes.length ? this.schemes[0] : 'http';
out.id = proto + '://' + out.host + out.basePath + '/swagger.json#';
if (originalSwagger.securityDefinitions) {
_.each(originalSwagger.securityDefinitions, (i:any, k) => {
if (k) {
if (i.authorizationUrl) {
out.securityDefinitions[k].authorizationUrl = jr.normalizeUri(i.authorizationUrl, out.id, true);
}
if (i.tokenUrl) {
out.securityDefinitions[k].tokenUrl = jr.normalizeUri(i.tokenUrl, out.id, true);
if (this.options.swagger) {
let originalSwagger:Swagger = _.cloneDeep(this) as Swagger;
r.get('/swagger.json', (req: APIRequest, res: APIResponse, next: NextFunction) => {
let out:any = _.cloneDeep(originalSwagger);
if (!req.headers['host']) {
next(API.newError(400, 'Bad Request', 'Missing Host header in the request'));
} else {
out.host = req.headers['host'];
out.basePath = req.baseUrl;
let proto = this.schemes && this.schemes.length ? this.schemes[0] : 'http';
out.id = proto + '://' + out.host + out.basePath + '/swagger.json#';
if (originalSwagger.securityDefinitions) {
_.each(originalSwagger.securityDefinitions, (i:any, k) => {
if (k) {
if (i.authorizationUrl) {
out.securityDefinitions[k].authorizationUrl = jr.normalizeUri(i.authorizationUrl, out.id, true);
}
if (i.tokenUrl) {
out.securityDefinitions[k].tokenUrl = jr.normalizeUri(i.tokenUrl, out.id, true);
}
}
}
});
});
}
res.json(out);
}
res.json(out);
}
});
});
}
if (this[__schemas]) {
r.get('/schemas/:id', (req: APIRequest, res: APIResponse, next: NextFunction) => {
let s = this[__schemas][req.params.id];
Expand Down
2 changes: 2 additions & 0 deletions src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as debug from 'debug';

export interface Logger {
log: debug.IDebugger;
info: debug.IDebugger;
warn: debug.IDebugger;
error: debug.IDebugger;
debug: debug.IDebugger;
Expand All @@ -20,6 +21,7 @@ export default function(label: string, context?: string): Logger {
} : debug;
return {
log: d(label + ':log'),
info: d(label + ':info'),
warn: d(label + ':warn'),
error: d(label + ':error'),
debug: d(label + ':debug')
Expand Down
65 changes: 52 additions & 13 deletions test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ describe('API', function() {

describe('constructor', function() {

it('should fail to create an API instance if no version or an invalid version is specified', function() {
(function() { new API() }).should.throw(Error, /Cannot convert/);
(function() { new API({}) }).should.throw(Error, /Cannot read/);
(function() { new API({ info: { }}) }).should.throw(Error, 'invalid_version');
it('should fail to create an API instance if an invalid version is specified', function() {
(function() { new API({ info: { version: true }}) }).should.throw(Error, 'invalid_version');
(function() { new API({ info: { version: 'a' }}) }).should.throw(Error, 'invalid_version');
(function() { new API({ info: { version: 1 }}) }).should.throw(Error, 'invalid_version');
(function() { new API({ info: { version: '1' }}) }).should.throw(Error, 'invalid_version');
(function() { new API({ info: { version: '1.0' }}) }).should.throw(Error, 'invalid_version');
(function() { new API({ info: { version: '1.0.0' }}) }).should.not.throw();
(function() { new API() }).should.not.throw();
});

it('should create an API instance', function() {
let api = new API({ info: { version: '1.0.0' }});
let api = new API();
api.swagger.should.equal('2.0')
api.info.version.should.equal('1.0.0')
});
Expand All @@ -38,14 +35,14 @@ describe('API', function() {
describe('router', function() {

it('should return an Expressjs router', function() {
let api = new API({ info: { version: '1.0.0' }});
let api = new API();
return api.router().then(router => {
router.should.be.a('function');
});
});

it('should return the same object when called twice', function() {
let api = new API({ info: { version: '1.0.0' }});
let api = new API();
return api.router().then(router1 => {
return api.router().then(router2 => {
router1.should.equal(router2);
Expand All @@ -57,13 +54,55 @@ describe('API', function() {

describe('plain api (no resources)', function() {

describe('swagger disabled', function() {

const port = 9876;
const host = 'localhost:' + port;
const basePath = 'http://' + host;
const request = supertest(basePath);
const api = new API(null, { swagger: false });
const app = express();
let server;

before(function() {
api.securityDefinitions = {
"access_code": {
"type": "oauth2",
"flow": "accessCode",
"tokenUrl": "token"
},
"implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "/a/b/authorize"
},
"": {}
};
return api.router().then(router => {
app.use(router);
server = app.listen(port);
});
});

after(function() {
server.close();
});

it('should return a the default swagger file', function() {
return request
.get('/swagger.json')
.expect(404);
});

});

describe('/swagger.json', function() {

const port = 9876;
const host = 'localhost:' + port;
const basePath = 'http://' + host;
const request = supertest(basePath);
const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const app = express();
let server;

Expand Down Expand Up @@ -184,7 +223,7 @@ describe('API', function() {
const host = 'localhost:' + port;
const basePath = 'http://' + host;
const request = supertest(basePath);
const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const app = express();
const schema1 = { a: true, b: 2 };
const schema2 = { c: 'd', e: [] };
Expand Down Expand Up @@ -443,7 +482,7 @@ describe('API', function() {
const app = express();
let server, spy;

const api = new API({ info: { version: '1.0.0' }});
const api = new API();
class Op1 extends Operation {
constructor(resource, path, method) {
super('op1', resource, path, method);
Expand Down Expand Up @@ -569,7 +608,7 @@ describe('API', function() {

it('should be able to resolve an internal schema', function() {
const spy = chai.spy((req, res) => { res.json({})});
const api = new API({ info: { version: '1.0.0' }});
const api = new API();
class Op1 extends Operation {
constructor(resource, path, method) {
super('op1', resource, path, method);
Expand Down Expand Up @@ -672,7 +711,7 @@ describe('API', function() {

it('should be able to resolve an external schema', function() {
const spy = chai.spy((req, res) => { res.json({})});
const api = new API({ info: { version: '1.0.0' }});
const api = new API();
class Op1 extends Operation {
constructor(resource, path, method) {
super('op1', resource, path, method);
Expand Down
2 changes: 1 addition & 1 deletion test/mongo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('mongo', function() {
const request = supertest(basePath);
const db = (new mongo.MongoClient()).connect('mongodb://localhost:27017');
const app = express();
const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const collectionName = 'arrest_test';

class FakeOp1 extends QueryMongoOperation {
Expand Down
8 changes: 4 additions & 4 deletions test/operation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('Operation', function() {
const app = express();
let server;

const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const spy = chai.spy(function(req, res) {
res.send({ headers: req.headers });
});
Expand Down Expand Up @@ -265,7 +265,7 @@ describe('Operation', function() {
const app = express();
let server;

const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const spy = chai.spy(function(req, res) {
res.send({ params: req.params });
});
Expand Down Expand Up @@ -366,7 +366,7 @@ describe('Operation', function() {
const app = express();
let server;

const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const spy = chai.spy(function(req, res) {
res.send({ query: req.query });
});
Expand Down Expand Up @@ -488,7 +488,7 @@ describe('Operation', function() {
const app = express();
let server;

const api = new API({ info: { version: '1.0.0' }});
const api = new API();
const spy = chai.spy(function(req, res) {
res.send({ body: req.body });
});
Expand Down

0 comments on commit 9d9c2f6

Please sign in to comment.