Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lower Joi dependency #34

Open
wants to merge 16 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ language: node_js

node_js:
- "12"
- "11"
- "10"
- "8"

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(The MIT License)

Copyright (c) 2017 Evan Shortiss <[email protected]>
Copyright (c) 2017-2019 Evan Shortiss <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
56 changes: 43 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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<HelloRequestSchema>, 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'
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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.

Expand Down
9 changes: 6 additions & 3 deletions example/typescript/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ContainerTypes
} from '../../express-joi-validation'
import { Router } from 'express'
import 'joi-extract-type'

const route = Router()
const validator = createValidator()
Expand All @@ -17,11 +16,15 @@ const schema = Joi.object({
})

interface HelloGetRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Query]: Joi.extractType<typeof schema>
[ContainerTypes.Query]: {
name: string
}
}

interface HelloPostRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Fields]: Joi.extractType<typeof schema>
[ContainerTypes.Fields]: {
name: string
}
}

// curl http://localhost:3030/hello/?name=express
Expand Down
7 changes: 4 additions & 3 deletions express-joi-validation.d.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<any> {
export interface ExpressJoiError extends Joi.ValidationResult<unknown> {
type: ContainerTypes
}

Expand All @@ -43,7 +44,7 @@ export type ValidatedRequestSchema = Record<ContainerTypes, any>
export interface ValidatedRequest<T extends ValidatedRequestSchema>
extends express.Request {
body: T[ContainerTypes.Body]
query: T[ContainerTypes.Query]
query: T[ContainerTypes.Query] & ParsedQs
headers: T[ContainerTypes.Headers]
params: T[ContainerTypes.Params]
}
Expand Down Expand Up @@ -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
}

/**
Expand Down
17 changes: 4 additions & 13 deletions express-joi-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down
73 changes: 37 additions & 36 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')(),
Expand Down Expand Up @@ -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)
})
})
})
23 changes: 9 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -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",
Expand All @@ -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"
Expand Down