diff --git a/.travis.yml b/.travis.yml index b6e9f03..00e6107 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: node_js node_js: - "12" - - "11" - "10" - "8" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a44843..4aa406b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ ## CHANGELOG Date format is DD/MM/YYYY +## 4.0.3 (18/11/2019) +* Fix TypeScript example in the README. + +## 4.0.2 (12/11/2019) +* Apply a fix for compatibility with Joi v16 typings. + +## 4.0.1 (24/09/2019) +* Remove outdated "joi" option in README + +## 4.0.0 (20/09/2019) +* Update to support Joi v16.x +* No longer supports passing a Joi instance to factory +* Finally removed deprecated function on `module.exports` from v2 + ## 3.0.0 (30/08/2019) * Removed `fields`, `originalQuery`, `originalHeaders`, `originalBody`, `originalParams`, and `originalFields` from `ValidatedRequest`. This simplifies diff --git a/LICENSE b/LICENSE index 49a8687..54d1ac9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2017 Evan Shortiss +Copyright (c) 2017-2019 Evan Shortiss Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 70c56fa..148f43e 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,7 @@ this repository. ```js const Joi = require('joi') const app = require('express')() -const validator = require('express-joi-validation').createValidator({ - // You can pass a specific Joi instance using this option. By default the - // module will load the @hapi/joi version you have in your package.json - // joi: require('@hapi/joi') -}) +const validator = require('express-joi-validation').createValidator({}) const querySchema = Joi.object({ name: Joi.string().required() @@ -80,8 +76,45 @@ For TypeScript a helper `ValidatedRequest` and `express.Request` type and allows you to pass a schema using generics to ensure type safety in your handler function. -One downside to this is that there's some duplication. You can minimise this -duplication by using [joi-extract-type](https://github.com/TCMiranda/joi-extract-type/). +```ts +import * as Joi from '@hapi/joi' +import * as express from 'express' +import { + ContainerTypes, + // Use this as a replacement for express.Request + ValidatedRequest, + // Extend from this to define a valid schema type/interface + ValidatedRequestSchema, + // Creates a validator that generates middlewares + createValidator +} from 'express-joi-validation' + +const app = express() +const validator = createValidator() + +const querySchema = Joi.object({ + name: Joi.string().required() +}) + +interface HelloRequestSchema extends ValidatedRequestSchema { + [ContainerTypes.Query]: { + name: string + } +} + +app.get( + '/hello', + validator.query(querySchema), + (req: ValidatedRequest, res) => { + // Woohoo, type safety and intellisense for req.query! + res.end(`Hello ${req.query.name}!`) + } +) +``` + +You can minimise some duplication by using [joi-extract-type](https://github.com/TCMiranda/joi-extract-type/). + +_NOTE: this does not work with Joi v16+ at the moment. See [this issue](https://github.com/TCMiranda/joi-extract-type/issues/23)._ ```ts import * as Joi from '@hapi/joi' @@ -133,11 +166,11 @@ app.get( * [createValidator(config)](#createvalidatorconfig) * [query(options)](#validatorqueryschema-options) * [body(options)](#validatorbodyschema-options) - * [headers(options)](#headersschema-options) + * [headers(options)](#validatorheadersschema-options) * [params(options)](#validatorparamsschema-options) + * [response(options)](#validatorresponseschema-options) * [fields(options)](#validatorfieldsschema-options) - ### createValidator(config) Creates a validator. Supports the following options: @@ -215,10 +248,7 @@ Supported options are the same as `validator.query`. ## Behaviours ### Joi Versioning -You can explicitly pass a versiong of Joi using the `joi` option supported by -the `createValidator` function. - -Otherwise, this module uses `peerDependencies` for the Joi version being used. +This module uses `peerDependencies` for the Joi version being used. This means whatever `@hapi/joi` version is in the `dependencies` of your `package.json` will be used by this module. diff --git a/example/typescript/route.ts b/example/typescript/route.ts index d71bc33..c4d078c 100644 --- a/example/typescript/route.ts +++ b/example/typescript/route.ts @@ -8,7 +8,6 @@ import { ContainerTypes } from '../../express-joi-validation' import { Router } from 'express' -import 'joi-extract-type' const route = Router() const validator = createValidator() @@ -17,11 +16,15 @@ const schema = Joi.object({ }) interface HelloGetRequestSchema extends ValidatedRequestSchema { - [ContainerTypes.Query]: Joi.extractType + [ContainerTypes.Query]: { + name: string + } } interface HelloPostRequestSchema extends ValidatedRequestSchema { - [ContainerTypes.Fields]: Joi.extractType + [ContainerTypes.Fields]: { + name: string + } } // curl http://localhost:3030/hello/?name=express diff --git a/express-joi-validation.d.ts b/express-joi-validation.d.ts index 59a3cb0..cb2cd69 100644 --- a/express-joi-validation.d.ts +++ b/express-joi-validation.d.ts @@ -1,6 +1,7 @@ import * as Joi from '@hapi/joi' import * as express from 'express' import { IncomingHttpHeaders } from 'http' +import { ParsedQs } from 'qs' /** * Creates an instance of this module that can be used to generate middleware @@ -24,7 +25,7 @@ export enum ContainerTypes { * Use this in you express error handler if you've set *passError* to true * when calling *createValidator* */ -export interface ExpressJoiError extends Joi.ValidationResult { +export interface ExpressJoiError extends Joi.ValidationResult { type: ContainerTypes } @@ -43,7 +44,7 @@ export type ValidatedRequestSchema = Record export interface ValidatedRequest extends express.Request { body: T[ContainerTypes.Body] - query: T[ContainerTypes.Query] + query: T[ContainerTypes.Query] & ParsedQs headers: T[ContainerTypes.Headers] params: T[ContainerTypes.Params] } @@ -75,9 +76,9 @@ export interface ValidatedRequestWithRawInputsAndFields< * Configuration options supported by *createValidator(config)* */ export interface ExpressJoiConfig { - joi?: typeof Joi statusCode?: number passError?: boolean + joi?: object } /** diff --git a/express-joi-validation.js b/express-joi-validation.js index d6a1b54..5219afe 100644 --- a/express-joi-validation.js +++ b/express-joi-validation.js @@ -59,17 +59,8 @@ function buildErrorString(err, container) { return ret } -module.exports = function() { - throw new Error( - 'express-joi-validation: exported member is no longer a factory function. use exported createValidator function instead' - ) -} - module.exports.createValidator = function generateJoiMiddlewareInstance(cfg) { cfg = cfg || {} // default to an empty config - - const Joi = cfg.joi || require('@hapi/joi') - // We'll return this instance of the middleware const instance = { response @@ -81,9 +72,9 @@ module.exports.createValidator = function generateJoiMiddlewareInstance(cfg) { instance[type] = function(schema, opts) { opts = opts || {} // like config, default to empty object - - return function exporessJoiValidator(req, res, next) { - const ret = Joi.validate(req[type], schema, opts.joi || container.joi) + const computedOpts = { ...container.joi, ...cfg.joi, ...opts.joi } + return function expressJoiValidator(req, res, next) { + const ret = schema.validate(req[type], computedOpts) if (!ret.error) { req[container.storageProperty] = req[type] @@ -111,7 +102,7 @@ module.exports.createValidator = function generateJoiMiddlewareInstance(cfg) { next() function validateJson(json) { - const ret = Joi.validate(json, schema, opts.joi) + const ret = schema.validate(json, opts.joi) const { error, value } = ret if (!error) { // return res.json ret to retain express compatibility diff --git a/index.test.js b/index.test.js index 12e4c68..c608cdb 100644 --- a/index.test.js +++ b/index.test.js @@ -58,6 +58,24 @@ describe('express joi', function() { } ) + app.post( + '/global-joi-config', + require('body-parser').json(), + middleware, + (req, res) => { + expect(req.body).to.exist + expect(req.originalBody).to.exist + + expect(req.originalBody.known).to.exist + expect(req.originalBody.known).to.exist + + expect(req.originalBody.unknown).to.exist + expect(req.originalBody.unknown).to.exist + + res.end('ok') + } + ) + app.post( '/fields-check', require('express-formidable')(), @@ -269,47 +287,30 @@ describe('express joi', function() { done() }) }) + }) - it('should use supplied config.joi and config.statusCode', function(done) { - const errStr = '"id" is required' - const statusCode = 403 - - const joiStub = { - validate: sinon.stub().returns({ - error: { - details: [ - { - message: errStr - } - ] - } - }) - } - - const reqStub = { - query: {} - } - - const resStub = { - end: str => { - expect(joiStub.validate.called).to.be.true - expect(resStub.status.calledWith(statusCode)).to.be.true - expect(str).to.equal(`Error validating request query. ${errStr}.`) - done() - } - } - resStub.status = sinon.stub().returns(resStub) - + describe('#joiGlobalOptionMerging.', function() { + it('should return a 200 since our body is valid', function(done) { const mod = require('./express-joi-validation.js').createValidator({ - joi: joiStub, - statusCode: statusCode + passError: true, + joi: { + allowUnknown: true + } + }) + const schema = Joi.object({ + known: Joi.boolean().required() }) - const mw = mod.query(Joi.object({})) + const mw = mod.body(schema) - mw(reqStub, resStub, () => { - done(new Error('next should not be called')) - }) + getRequester(mw) + .post('/global-joi-config') + .send({ + known: true, + unknown: true + }) + .expect(200) + .end(done) }) }) }) diff --git a/package.json b/package.json index db3bd31..da245dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-joi-validation", - "version": "3.0.0", + "version": "4.0.4-beta.0", "description": "validate express application inputs and parameters using joi", "main": "express-joi-validation.js", "scripts": { @@ -22,25 +22,19 @@ "joi", "express", "validation", - "validator", "middleware", - "validate", - "sanatize", - "sanatise", - "input", - "parameter", "typescript", - "ts", "tsc" ], "author": "Evan Shortiss", "license": "MIT", "devDependencies": { - "@hapi/joi": "~15.0.3", + "@hapi/joi": "~15", "@types/express": "~4.0.39", "@types/express-formidable": "~1.0.4", - "@types/hapi__joi": "~15.0.2", - "@types/node": "^6.0.117", + "@types/hapi__joi": "~15", + "@types/node": "~10.14.18", + "@types/qs": "~6.9.3", "body-parser": "~1.18.3", "chai": "~3.5.0", "clear-require": "~2.0.0", @@ -50,19 +44,20 @@ "husky": "~1.0.1", "joi-extract-type": "~15.0.0", "lint-staged": "~8.2.1", - "lodash": "~4.17.4", + "lodash": "~4.17.15", "mocha": "~5.2.0", "mocha-lcov-reporter": "~1.3.0", - "nodemon": "~1.11.0", + "nodemon": "~1.19.2", "nyc": "~14.1.1", "prettier": "~1.14.3", "proxyquire": "~1.7.11", + "qs": "~6.9.4", "sinon": "~1.17.7", "supertest": "~3.0.0", "typescript": "~3.5.2" }, "peerDependencies": { - "@hapi/joi": "*" + "@hapi/joi": "15" }, "engines": { "node": ">=8.0.0"