Skip to content

Commit

Permalink
feat(arrest): Updated to latest version of jsonpolice to support asyn…
Browse files Browse the repository at this point in the history
…chrounous schema validation of

BREAKING CHANGE: Switched to async schema validation, changed the dynamic schema interface
  • Loading branch information
0xfede committed Apr 28, 2017
1 parent 9efeb7f commit e529bc3
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 67 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@
"@types/request": "^0.0.42",
"@types/semver": "^5.3.31",
"chai": "^3.5.0",
"chai-as-promised": "6.0.0",
"chai-spies": "^0.7.1",
"commitizen": "^2.9.6",
"coveralls": "2.13.0",
"coveralls": "2.13.1",
"cz-conventional-changelog": "^2.0.0",
"husky": "^0.13.3",
"mocha": "^3.2.0",
Expand All @@ -98,7 +99,7 @@
"decamelize": "^1.2.0",
"eredita": "^1.0.1",
"express": "^4.15.2",
"jsonpolice": "^4.1.0",
"jsonpolice": "^5.1.0",
"jsonref": "^3.5.0",
"lodash": "^4.17.4",
"mongodb": "^2.2.25",
Expand Down
34 changes: 18 additions & 16 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import * as https from 'https';
import * as url from 'url';
import * as _ from 'lodash';
import * as semver from 'semver';
import * as jr from 'jsonref';
import * as express from 'express';
import { normalizeUri } from 'jsonref';
import { Schema } from 'jsonpolice';
import { Router, RouterOptions, RequestHandler, Request, Response, NextFunction } from 'express';
import { Eredita } from 'eredita';
import debug, { Logger } from './debug';
Expand Down Expand Up @@ -148,7 +149,7 @@ const __default_schema_tag: Swagger.Tag = {
};
const __default_options: APIOptions = {
swagger: true
}
};

let reqId: number = 0;

Expand Down Expand Up @@ -226,16 +227,23 @@ export class API implements Swagger {
return this;
}

registerSchema(id: string, schema: Swagger.Schema | APIRequestHandler) {
registerSchema(id: string, schema: Swagger.Schema | Schema) {
if (!this[__schemas]) {
this[__schemas] = {};
this.registerTag(_.cloneDeep(__default_schema_tag));
this.registerOperation('/schemas/{id}', 'get', _.cloneDeep(__default_schema_operation));
}
this[__schemas][id] = schema;
if (typeof schema !== 'function') {
this.registry.register(`schemas/${id}`, schema);

let _schema: Swagger.FullSchema;
if (schema instanceof Schema) {
_schema = {};
Schema.attach(_schema, schema as Schema);
} else {
_schema = schema as Swagger.FullSchema;
}

this[__schemas][id] = _schema;
this.registry.register(`schemas/${id}`, _schema);
}
registerOperation(path:string, method:string, operation:Swagger.Operation) {
if (!this.paths) {
Expand Down Expand Up @@ -322,10 +330,10 @@ export class API implements Swagger {
_.each(originalSwagger.securityDefinitions, (i:any, k) => {
if (k) {
if (i.authorizationUrl) {
out.securityDefinitions[k].authorizationUrl = jr.normalizeUri(i.authorizationUrl, id, true);
out.securityDefinitions[k].authorizationUrl = normalizeUri(i.authorizationUrl, id, true);
}
if (i.tokenUrl) {
out.securityDefinitions[k].tokenUrl = jr.normalizeUri(i.tokenUrl, id, true);
out.securityDefinitions[k].tokenUrl = normalizeUri(i.tokenUrl, id, true);
}
}
});
Expand All @@ -338,20 +346,14 @@ export class API implements Swagger {
r.get('/schemas/:id', (req: APIRequest, res: APIResponse, next: NextFunction) => {
let s = this[__schemas][req.params.id];
if (s) {
if (typeof s === 'function') {
(s as APIRequestHandler)(req, res, next);
} else {
res.json(this[__schemas][req.params.id]);
}
Schema.get(s as Schema).schema().then(data => res.json(data));
} else {
next();
}
});
}
for (let i in this[__schemas]) {
if (typeof this[__schemas][i] !== 'function') {
await this.registry.create(this[__schemas][i]);
}
await this.registry.create(this[__schemas][i]);
}
await this.registry.resolve(this)
let promises: Promise<Router>[] = [];
Expand Down
62 changes: 31 additions & 31 deletions src/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,37 +75,36 @@ export abstract class Operation implements Swagger.Operation {
Object.assign(this, info);
return this;
}
protected createValidators(key: string, parameters: Swagger.Parameter[]): Promise<APIRequestHandler> {
return new Promise(resolve => {
let validators: Promise<(req: APIRequest) => void>[] = [];
parameters.forEach((parameter: Swagger.Parameter) => {
let required = parameter.required;
delete parameter.required;
validators.push(this.api.registry.create(parameter).then((schema: jp.Schema) => {
return function (req: APIRequest) {
req.logger.debug(`validator ${key}.${parameter.name}`);
if (typeof req[key][parameter.name] === 'undefined' && required === true) {
throw new jp.ValidationError(key + '.' + parameter.name, (schema as any).scope, 'required');
} else {
let out = schema.validate(req[key][parameter.name], key + '.' + parameter.name);
if (typeof out !== 'undefined') {
req[key][parameter.name] = out;
} else {
delete req[key][parameter.name];
}
}
protected async createValidators(key: string, parameters: Swagger.Parameter[]): Promise<APIRequestHandler> {
let validatorFactories: Promise<(req: APIRequest) => Promise<void>>[] = parameters.map(async (parameter: Swagger.Parameter) => {
let required = parameter.required;
delete parameter.required;
let schema:jp.Schema = await this.api.registry.create(parameter);

return async function(req: APIRequest) {
req.logger.debug(`validator ${key}.${parameter.name}, required ${required}, value ${req[key][parameter.name]}`);
if (typeof req[key][parameter.name] === 'undefined') {
if (required === true) {
throw new jp.ValidationError(key + '.' + parameter.name, (schema as any).scope, 'required');
}
}));
});
resolve(Promise.all(validators).then((validators:((req: APIRequest) => void)[]) => {
return function(req:APIRequest, res:APIResponse, next:NextFunction) {
req.logger.debug('validating');
validators.forEach(v => v(req));
req.logger.debug('validated');
next();
} else {
req[key][parameter.name] = await schema.validate(req[key][parameter.name], key + '.' + parameter.name);
}
}));
}
});

let validators:((req: APIRequest) => Promise<void>)[] = await Promise.all(validatorFactories);

return async function(req:APIRequest, res:APIResponse, next:NextFunction) {
try {
for (let v of validators) {
await v(req);
}
next();
} catch(err) {
next(err);
}
}
}

attach(api:API) {
Expand Down Expand Up @@ -177,12 +176,13 @@ export abstract class Operation implements Swagger.Operation {
return (req:APIRequest, res:APIResponse, next:NextFunction) => {
if (_.isEqual(req.body, {}) && (!parseInt(req.headers['content-length']))) {
if (params.body[0].required === true) {
throw new jp.ValidationError('body', schema.scope, 'required');
next(new jp.ValidationError('body', schema.scope, 'required'));
} else {
next();
}
} else {
schema.validate(req.body, 'body');
schema.validate(req.body, 'body').then(() => next(), err => next(err));
}
next();
}
}));
}
Expand Down
19 changes: 10 additions & 9 deletions src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as request from 'request';
import * as jr from 'jsonref';
import * as jp from 'jsonpolice';
import { parse as parseRefs, normalizeUri, ParseOptions } from 'jsonref';
import { create as createSchema, Schema } from 'jsonpolice';
import { Swagger } from './swagger';
import { RESTError } from './error'

const json_schema_draft_04 = {
Expand Down Expand Up @@ -155,7 +156,7 @@ const json_schema_draft_04 = {
}

export class SchemaRegistry {
private opts:jr.ParseOptions;
private opts:ParseOptions;

constructor(scope?:string) {
this.opts = {
Expand Down Expand Up @@ -185,13 +186,13 @@ export class SchemaRegistry {
});
}
async resolve(dataOrUri:any): Promise<any> {
return jr.parse(dataOrUri, this.opts);
return parseRefs(dataOrUri, this.opts);
}
async create(dataOrUri:any): Promise<jp.Schema> {
return jp.create(dataOrUri, this.opts);
async create(dataOrUri:any): Promise<Schema> {
return createSchema(dataOrUri, this.opts);
}
register(id, data:any) {
data.id = jr.normalizeUri(id);
Object.assign(this.opts.store, { [data.id]: data });
register(id, schema: Swagger.FullSchema) {
schema.id = normalizeUri(id);
Object.assign(this.opts.store, { [schema.id]: schema });
}
}
1 change: 1 addition & 0 deletions src/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export namespace Swagger {
}

export interface BasicSchema {
id?: string;
format?: string;
["default"]?: any;
maximum?: number;
Expand Down
21 changes: 19 additions & 2 deletions test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var chai = require('chai')
, supertest = require('supertest')
, pem = require('pem')
, express = require('express')
, Schema = require('jsonpolice').Schema
, Scopes = require('../dist/scopes').Scopes
, API = require('../dist/api').API
, Resource = require('../dist/resource').Resource
Expand Down Expand Up @@ -195,11 +196,28 @@ describe('API', function() {
const app = express();
const schema1 = { a: true, b: 2 };
const schema2 = { c: 'd', e: [] };

class TestSchema extends Schema {
constructor(scope, opts) {
super(scope, opts);
}
async schema() {
return schema2;
}
async validate(data, path) {
return data;
}
init() {
}
default(data) {
return data;
}
}
let server;

before(function() {
api.registerSchema('abc', schema1);
api.registerSchema('def', (req, res) => res.json(schema2));
api.registerSchema('def', new TestSchema());
return api.router().then(router => {
app.use(router);
server = app.listen(port);
Expand Down Expand Up @@ -567,7 +585,6 @@ describe('API', function() {

});


describe('schema', function() {

const port = 9876;
Expand Down
19 changes: 12 additions & 7 deletions test/schema.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
let chai = require('chai')
, spies = require('chai-spies')
, chaiAsPromised = require('chai-as-promised')
, should = chai.should()
, express = require('express')
, SchemaRegistry = require('../dist/schema').SchemaRegistry


chai.use(spies);
chai.use(chaiAsPromised);

describe('SchemaRegistry', function() {

Expand Down Expand Up @@ -97,9 +98,11 @@ describe('SchemaRegistry', function() {
additionalProperties: false,
required: [ 'a' ]
}).then(function(schema) {
(function() { schema.validate({ a: true }) }).should.not.throw();
(function() { schema.validate({ a: 1 }) }).should.throw();
(function() { schema.validate({ a: true, b: 1 }) }).should.throw();
return Promise.all([
schema.validate({ a: true }).should.be.fulfilled,
schema.validate({ a: 1 }).should.be.rejected,
schema.validate({ a: true, b: 1 }).should.be.rejected
]);
});
});

Expand All @@ -124,9 +127,11 @@ describe('SchemaRegistry', function() {
return registry.create('c').then(function(schema) {
server.close();
spy.should.have.been.called.once();
(function() { schema.validate({ a: true }) }).should.not.throw();
(function() { schema.validate({ a: 1 }) }).should.throw();
(function() { schema.validate({ a: true, b: 1 }) }).should.throw();
return Promise.all([
schema.validate({ a: true }).should.be.fulfilled,
schema.validate({ a: 1 }).should.be.rejected,
schema.validate({ a: true, b: 1 }).should.be.rejected
]);
}, function(err) {
server.close();
throw err;
Expand Down

0 comments on commit e529bc3

Please sign in to comment.