diff --git a/.eslintrc.js b/.eslintrc.js index 23b1f1a..f0914b1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,14 +1,9 @@ -'use strict'; - module.exports = { extends: 'airbnb-base', env: { node: true }, rules: { - 'prefer-destructuring': 'off', 'no-underscore-dangle': 'off', - 'comma-dangle': ['error', 'never'], - 'strict': 'off', } }; \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 9c5227e..79e2ce6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ sudo: false language: node_js node_js: - - "4" - - "6" - "8" - "node" diff --git a/lib/index.d.ts b/lib/index.d.ts index 8332a18..0e75539 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -23,7 +23,7 @@ declare namespace Celebrate { /** * The Joi version Celebrate uses internally. */ - export const Joi: joi + export const Joi: joi; /** * Examines an error object to determine if it originated from the celebrate middleware. diff --git a/lib/index.js b/lib/index.js index 19e083b..a73f291 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,3 @@ -'use strict'; - const Series = require('fastseries')({ results: true }); const Assert = require('assert'); const Joi = require('joi'); @@ -8,22 +6,26 @@ const validations = require('./schema'); const CELEBRATED = Symbol('isCelebrate'); const DEFAULTS = { - escapeHtml: true + escapeHtml: true, }; const validateSource = source => (config, next) => { - const req = config.req; - const options = config.options; - const rules = config.rules; + const { + req, + options, + rules, + } = config; const spec = rules.get(source); if (!spec) { return next(null); } const result = Joi.validate(req[source], spec, options); - const value = result.value; - const err = result.error; + const { + value, + error: err, + } = result; if (value !== undefined) { const descriptor = Object.getOwnPropertyDescriptor(req, source); @@ -32,7 +34,7 @@ const validateSource = source => req[source] = value; } else { Object.defineProperty(req, source, { - get() { return value; } + get() { return value; }, }); } } @@ -62,11 +64,11 @@ const REQ_VALIDATIONS = [ validateHeaders, validateParams, validateQuery, - maybeValidateBody + maybeValidateBody, ]; const isCelebrate = (err) => { - if (err != null && typeof err === 'object') { // eslint-disable-line eqeqeq + if (err != null && typeof err === 'object') { return err[CELEBRATED] || false; } return false; @@ -78,16 +80,14 @@ const celebrate = (schema, options) => { const rules = new Map(); const joiOpts = Object.assign({}, DEFAULTS, options); - const keys = Object.keys(schema); - for (let i = 0; i < keys.length; i += 1) { - const key = keys[i]; + Object.keys(schema).forEach((key) => { rules.set(key, Joi.compile(schema[key])); - } + }); const middleware = (req, res, next) => { Series(null, REQ_VALIDATIONS, { req, options: joiOpts, - rules + rules, }, next); }; @@ -104,21 +104,20 @@ const errors = () => (err, req, res, next) => { message: err.message, validation: { source: err._meta.source, - keys: [] - } + keys: [], + }, }; if (err.details) { for (let i = 0; i < err.details.length; i += 1) { - /* istanbul ignore next */ - const path = Array.isArray(err.details[i].path) ? err.details[i].path.join('.') : err.details[i].path; + const path = err.details[i].path.join('.'); error.validation.keys.push(EscapeHtml(path)); } } return res.status(400).send(error); } - // If this isn't a Joi error, send it to the next error handler + // If this isn't a Celebrate error, send it to the next error handler return next(err); }; @@ -126,5 +125,5 @@ module.exports = { celebrate, Joi, errors, - isCelebrate + isCelebrate, }; diff --git a/lib/schema.js b/lib/schema.js index 024e1ea..d733572 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1,10 +1,8 @@ -'use strict'; - const Joi = require('joi'); module.exports.schema = Joi.object().keys({ headers: Joi.any(), params: Joi.any(), query: Joi.any(), - body: Joi.any() + body: Joi.any(), }).min(1); diff --git a/package.json b/package.json index 27b8f29..b765e1a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dependencies": { "escape-html": "1.0.3", "fastseries": "1.7.2", - "joi": "12.x.x" + "joi": "13.x.x" }, "devDependencies": { "@types/express": "4.x.x", @@ -39,11 +39,11 @@ "eslint": "4.19.x", "eslint-config-airbnb-base": "12.1.x", "eslint-plugin-import": "2.11.x", - "expect": "21.2.x", + "expect": "22.x.x", "express": "5.0.0-alpha.6", - "jest": "21.2.x" + "jest": "22.x.x" }, "engines": { - "node": ">=4.0.0" + "node": ">=8.9.0" } } diff --git a/test/celebrate.test.js b/test/celebrate.test.js index 989dbb6..12c04b9 100644 --- a/test/celebrate.test.js +++ b/test/celebrate.test.js @@ -1,13 +1,11 @@ -'use strict'; - /* eslint-env jest */ const expect = require('expect'); -const Celebrate = require('../lib'); - -const celebrate = Celebrate.celebrate; -const Joi = Celebrate.Joi; -const errors = Celebrate.errors; -const isCelebrate = Celebrate.isCelebrate; +const { + celebrate, + Joi, + errors, + isCelebrate, +} = require('../lib'); describe('Celebrate', () => { it('throws an error if using an invalid schema', () => { @@ -16,9 +14,9 @@ describe('Celebrate', () => { celebrate({ query: { name: Joi.string(), - age: Joi.number() + age: Joi.number(), }, - foo: Joi.string() + foo: Joi.string(), }); }).toThrow('"foo" is not allowed'); @@ -35,14 +33,14 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ headers: { - accept: Joi.string().regex(/xml/) - } + accept: Joi.string().regex(/xml/), + }, }); middleware({ headers: { - accept: 'application/json' - } + accept: 'application/json', + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"accept" with value "application/json" fails to match the required pattern: /xml/'); @@ -53,14 +51,14 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ params: { - id: Joi.string().token() - } + id: Joi.string().token(), + }, }); middleware({ params: { - id: '@@@' - } + id: '@@@', + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"id" must only contain alpha-numeric and underscore characters'); @@ -71,14 +69,14 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ query: Joi.object().keys({ - start: Joi.date() - }) + start: Joi.date(), + }), }); middleware({ query: { - end: 1 - } + end: 1, + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"end" is not allowed'); @@ -91,16 +89,16 @@ describe('Celebrate', () => { body: { first: Joi.string().required(), last: Joi.string(), - role: Joi.number().integer() - } + role: Joi.number().integer(), + }, }); middleware({ body: { first: 'john', - last: 123 + last: 123, }, - method: 'POST' + method: 'POST', }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"last" must be a string'); @@ -111,29 +109,29 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ params: { - id: Joi.string().required() + id: Joi.string().required(), }, query: Joi.object().keys({ - start: Joi.date() + start: Joi.date(), }), body: { first: Joi.string().required(), last: Joi.string(), - role: Joi.number().integer() - } + role: Joi.number().integer(), + }, }); middleware({ params: { - id: '1' + id: '1', }, query: { - end: false + end: false, }, body: { first: 'john', - last: 123 - } + last: 123, + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"end" is not allowed'); @@ -145,18 +143,18 @@ describe('Celebrate', () => { const req = { body: { first: 'john', - last: 'doe' + last: 'doe', }, query: undefined, - method: 'POST' + method: 'POST', }; const middleware = celebrate({ body: { first: Joi.string().required(), last: Joi.string(), - role: Joi.number().integer().default('admin') + role: Joi.number().integer().default('admin'), }, - query: Joi.number() + query: Joi.number(), }); middleware(req, null, (err) => { @@ -164,7 +162,7 @@ describe('Celebrate', () => { expect(req.body).toEqual({ first: 'john', last: 'doe', - role: 'admin' + role: 'admin', }); expect(req.query).toBeUndefined(); }); @@ -176,16 +174,16 @@ describe('Celebrate', () => { body: { first: Joi.string().required(), last: Joi.string(), - role: Joi.number().integer() - } + role: Joi.number().integer(), + }, }); middleware({ body: { first: 'john', - last: 123 + last: 123, }, - method: 'GET' + method: 'GET', }, null, (err) => { expect(err).toBe(null); }); @@ -204,23 +202,23 @@ describe('Celebrate', () => { return this.createError('string.isJohn', { v }, state, options); } return undefined; - } - }] + }, + }], }); const middleware = celebrate({ body: { first: _Joi.string().required().isJohn(), - role: _Joi.number().integer() - } + role: _Joi.number().integer(), + }, }); middleware({ body: { first: 'george', - role: 123 + role: 123, }, - method: 'POST' + method: 'POST', }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"first" must equal "john"'); @@ -231,15 +229,15 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ query: Joi.object().keys({ - page: Joi.number() - }) + page: Joi.number(), + }), }, { stripUnknown: true }); const req = { query: { start: Date.now(), - page: 1 + page: 1, }, - method: 'GET' + method: 'GET', }; middleware(req, null, (err) => { @@ -252,14 +250,14 @@ describe('Celebrate', () => { expect.assertions(2); const middleware = celebrate({ headers: { - accept: Joi.string().regex(/xml/) - } + accept: Joi.string().regex(/xml/), + }, }, { escapeHtml: false }); middleware({ headers: { - accept: 'application/json' - } + accept: 'application/json', + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); expect(err.details[0].message).toBe('"accept" with value "application/json" fails to match the required pattern: /xml/'); @@ -271,8 +269,8 @@ describe('Celebrate', () => { expect.assertions(3); const middleware = celebrate({ query: { - role: Joi.number().integer().min(4) - } + role: Joi.number().integer().min(4), + }, }); const handler = errors(); const next = jest.fn(); @@ -283,16 +281,16 @@ describe('Celebrate', () => { send(err) { expect(err).toMatchSnapshot(); expect(next).not.toHaveBeenCalled(); - } + }, }; - } + }, }; middleware({ query: { - role: '0' + role: '0', }, - method: 'GET' + method: 'GET', }, null, (err) => { handler(err, undefined, res, next); }); @@ -304,14 +302,14 @@ describe('Celebrate', () => { const res = { status() { throw Error('status called'); - } + }, }; const next = (err) => { expect(err).toEqual(errorDirectlyFromJoi); }; const schema = Joi.object({ - role: Joi.number().integer().min(4) + role: Joi.number().integer().min(4), }); Joi.validate({ role: '0' }, schema, { abortEarly: false, convert: false }, (err) => { @@ -324,8 +322,8 @@ describe('Celebrate', () => { expect.assertions(3); const middleware = celebrate({ body: { - first: Joi.string().required() - } + first: Joi.string().required(), + }, }); const handler = errors(); const next = jest.fn(); @@ -336,16 +334,16 @@ describe('Celebrate', () => { send(err) { expect(err).toMatchSnapshot(); expect(next).not.toHaveBeenCalled(); - } + }, }; - } + }, }; middleware({ body: { - role: '0' + role: '0', }, - method: 'POST' + method: 'POST', }, null, (err) => { err.details = null; // eslint-disable-line no-param-reassign handler(err, undefined, res, next); @@ -376,14 +374,14 @@ describe('Celebrate', () => { expect.assertions(1); const middleware = celebrate({ headers: { - accept: Joi.string().regex(/xml/) - } + accept: Joi.string().regex(/xml/), + }, }); middleware({ headers: { - accept: 'application/json' - } + accept: 'application/json', + }, }, null, (err) => { expect(isCelebrate(err)).toBe(true); }); diff --git a/test/integration.test.js b/test/integration.test.js index e6adcea..d2d9876 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1,14 +1,14 @@ -'use strict'; + /* eslint-env jest */ const Express = require('express'); const Artificial = require('artificial'); const BodyParser = require('body-parser'); -const Celebrate = require('../lib'); - -const celebrate = Celebrate.celebrate; -const Joi = Celebrate.Joi; -const errors = Celebrate.errors; +const { + celebrate, + Joi, + errors, +} = require('../lib'); const Server = () => { const server = Express(); @@ -25,10 +25,10 @@ describe('express integration', () => { server.get('/', celebrate({ headers: { - accept: Joi.string().regex(/xml/) - } + accept: Joi.string().regex(/xml/), + }, }, { - allowUnknown: true + allowUnknown: true, })); server.use(errors()); @@ -37,8 +37,8 @@ describe('express integration', () => { method: 'GET', url: '/', headers: { - accept: 'application/json' - } + accept: 'application/json', + }, }, (res) => { expect(res.statusCode).toBe(400); expect(JSON.parse(res.payload)).toMatchSnapshot(); @@ -51,15 +51,15 @@ describe('express integration', () => { const server = Server(); server.get('/user/:id', celebrate({ params: { - id: Joi.string().token() - } + id: Joi.string().token(), + }, })); server.use(errors()); server.inject({ method: 'get', - url: '/user/@@' + url: '/user/@@', }, (res) => { expect(res.statusCode).toBe(400); expect(JSON.parse(res.payload)).toMatchSnapshot(); @@ -73,14 +73,14 @@ describe('express integration', () => { server.get('/', celebrate({ query: Joi.object().keys({ - start: Joi.date() - }) + start: Joi.date(), + }), })); server.use(errors()); server.inject({ - url: '/?end=celebrate' + url: '/?end=celebrate', }, (res) => { expect(res.statusCode).toBe(400); expect(JSON.parse(res.payload)).toMatchSnapshot(); @@ -95,8 +95,8 @@ describe('express integration', () => { body: { first: Joi.string().required(), last: Joi.string(), - role: Joi.number().integer() - } + role: Joi.number().integer(), + }, })); server.use(errors()); @@ -106,8 +106,8 @@ describe('express integration', () => { method: 'post', payload: { first: 'john', - last: 123 - } + last: 123, + }, }, (res) => { expect(res.statusCode).toBe(400); expect(JSON.parse(res.payload)).toMatchSnapshot(); @@ -123,16 +123,16 @@ describe('express integration', () => { server.get('/', celebrate({ headers: { accept: Joi.string().regex(/json/), - 'secret-header': Joi.string().default('@@@@@@') - } + 'secret-header': Joi.string().default('@@@@@@'), + }, }, { - allowUnknown: true + allowUnknown: true, }), (req, res) => { delete req.headers.host; // this can change computer to computer, so just remove it expect(req.headers).toEqual({ accept: 'application/json', 'user-agent': 'shot', - 'secret-header': '@@@@@@' + 'secret-header': '@@@@@@', }); res.send(); }); @@ -141,8 +141,8 @@ describe('express integration', () => { method: 'GET', url: '/', headers: { - accept: 'application/json' - } + accept: 'application/json', + }, }, () => done()); }); @@ -151,8 +151,8 @@ describe('express integration', () => { const server = Server(); server.get('/user/:id', celebrate({ params: { - id: Joi.string().uppercase() - } + id: Joi.string().uppercase(), + }, }), (req, res) => { expect(req.params.id).toBe('ADAM'); res.send(); @@ -160,7 +160,7 @@ describe('express integration', () => { server.inject({ method: 'get', - url: '/user/adam' + url: '/user/adam', }, () => done()); }); @@ -171,19 +171,19 @@ describe('express integration', () => { server.get('/', celebrate({ query: Joi.object().keys({ name: Joi.string().uppercase(), - page: Joi.number().default(1) - }) + page: Joi.number().default(1), + }), }), (req, res) => { expect(req.query).toBe({ name: 'JOHN', - page: 1 + page: 1, }); res.send(); }); server.inject({ - url: '/?name=john' + url: '/?name=john', }, () => done()); }); @@ -194,13 +194,13 @@ describe('express integration', () => { body: { first: Joi.string().required(), last: Joi.string().default('Smith'), - role: Joi.string().uppercase() - } + role: Joi.string().uppercase(), + }, }), (req, res) => { expect(req.body).toEqual({ first: 'john', role: 'ADMIN', - last: 'Smith' + last: 'Smith', }); res.send(); }); @@ -210,8 +210,8 @@ describe('express integration', () => { method: 'post', payload: { first: 'john', - role: 'admin' - } + role: 'admin', + }, }, () => done()); }); });