Skip to content

Commit ef19b21

Browse files
committed
feat: add validator middleware
closes #4
1 parent 0427a8d commit ef19b21

File tree

10 files changed

+252
-30
lines changed

10 files changed

+252
-30
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ npm i @qiwi/mware
1919
* [mware-logger](./packages/mware-logger/README.md)
2020
* [mware-crumbs](./packages/mware-crumbs/README.md)
2121
* [mware-cors](./packages/mware-cors/README.md)
22+
* [mware-validator](./packages/mware-validator/README.md)
2223

2324
### Usage
2425
```javascript

packages/mware-validator/.babelrc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"comments": false,
3+
"ignore": [
4+
"interface.js"
5+
],
6+
"presets": [
7+
"@babel/preset-flow"
8+
],
9+
"env": {
10+
"production": {
11+
"presets": [
12+
"@babel/preset-flow",
13+
["@babel/preset-env", {
14+
"modules": false
15+
}]
16+
],
17+
"plugins": [
18+
"@babel/plugin-transform-modules-commonjs",
19+
"@babel/plugin-proposal-class-properties",
20+
"@babel/plugin-proposal-object-rest-spread"
21+
]
22+
},
23+
"test": {
24+
"presets": [
25+
"@babel/preset-flow",
26+
"@babel/preset-env"
27+
],
28+
"plugins": [
29+
"@babel/plugin-transform-modules-commonjs",
30+
"@babel/plugin-proposal-class-properties",
31+
"@babel/plugin-proposal-object-rest-spread"
32+
]
33+
}
34+
}
35+
}

packages/mware-validator/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# @qiwi/mware-validator
2+
JSON-schema based validator middleware
3+
4+
### Install
5+
```bash
6+
yarn add @qiwi/mware-validator
7+
npm i @qiwi/mware-validator
8+
```
9+
10+
### Usage
11+
12+
```javascript
13+
import validator from '@qiwi/mware-validator'
14+
import express from 'express'
15+
16+
const app = express()
17+
const schema = {
18+
type: 'object',
19+
properties: {
20+
params: {
21+
id: {
22+
type: 'string',
23+
pattern: '[abc]^\d{2}',
24+
required: true
25+
}
26+
},
27+
query: {
28+
data: {
29+
type: 'object',
30+
required: true
31+
}
32+
}
33+
}
34+
}
35+
app.get('/foo/:id', validator({schema}), (req, res) => {
36+
...
37+
res.send({...})
38+
})
39+
40+
app.listen(...)
41+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@qiwi/mware-validator",
3+
"version": "1.3.0",
4+
"private": false,
5+
"description": "JSON-schema based validator middleware",
6+
"main": "dist/es5/index.js",
7+
"files": [
8+
"README.md",
9+
"CHANGELOG.md",
10+
"dist/"
11+
],
12+
"scripts": {
13+
"build_es6": "flow-remove-types src/ --out-dir dist/es6/",
14+
"build_es5": "BABEL_ENV=production babel src --out-dir dist/es5/",
15+
"build": "rm -rf dist && npm run build_es6 && npm run build_es5"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "https://github.com/qiwi/mware.git"
20+
},
21+
"keywords": [
22+
"common middlewares"
23+
],
24+
"author": "Anton Golub <a.golub@qiwi.com>",
25+
"license": "MIT",
26+
"bugs": {
27+
"url": "https://github.com/qiwi/mware/issues"
28+
},
29+
"homepage": "https://github.com/qiwi/mware#readme",
30+
"dependencies": {
31+
"ajv": "^6.5.5",
32+
"http-status-codes": "^1.3.0"
33+
},
34+
"devDependencies": {
35+
"lodash": "^4.17.11",
36+
"reqresnext": "^1.5.1"
37+
}
38+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @flow
2+
3+
import type {
4+
IMiddlewareFactory,
5+
IRegularMiddleware,
6+
IRequest,
7+
IResponse,
8+
INext, IAny
9+
} from '../../mware-core/src/interface'
10+
import Ajv from 'ajv'
11+
import { BAD_REQUEST, getStatusText } from 'http-status-codes'
12+
13+
const ajv = new Ajv()
14+
15+
export type IOpts = {
16+
schema: {
17+
[key: string]: IAny
18+
},
19+
scheme?: {
20+
[key: string]: IAny
21+
}
22+
}
23+
24+
export const DEFAULT_SCHEMA: IOpts = {
25+
schema: {}
26+
}
27+
28+
export default ((opts?: IOpts) => {
29+
const {schema, scheme} = opts || DEFAULT_SCHEMA
30+
31+
return ((req: IRequest, res: IResponse, next: INext) => {
32+
if (!ajv.validate(schema || scheme, req)) {
33+
res
34+
.status(BAD_REQUEST)
35+
.send({
36+
message: getStatusText(BAD_REQUEST),
37+
details: ajv.errorsText()
38+
})
39+
40+
return
41+
}
42+
43+
next()
44+
}: IRegularMiddleware)
45+
}: IMiddlewareFactory)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import factory from '../src'
2+
import reqresnext from 'reqresnext'
3+
import {BAD_REQUEST} from 'http-status-codes'
4+
5+
describe('mware-validator', () => {
6+
beforeEach(() => {
7+
jest.resetAllMocks()
8+
})
9+
10+
it('factory returns a middleware', () => {
11+
expect(factory()).toEqual(expect.any(Function))
12+
})
13+
})
14+
15+
describe('validates request fields by scheme', () => {
16+
const scheme = {
17+
type: 'object',
18+
required: ['headers', 'params', 'query', 'body'],
19+
properties: {
20+
headers: {
21+
type: 'object',
22+
required: ['foo'],
23+
properties: {
24+
foo: {
25+
type: 'string'
26+
}
27+
}
28+
},
29+
params: {
30+
type: 'object',
31+
required: ['baz'],
32+
properties: {
33+
baz: {
34+
type: 'string'
35+
}
36+
}
37+
},
38+
query: {
39+
type: 'object',
40+
required: ['quxx'],
41+
properties: {
42+
quxx: {
43+
type: 'string'
44+
}
45+
}
46+
},
47+
body: {
48+
type: 'string'
49+
}
50+
}
51+
}
52+
53+
const mware = factory({scheme})
54+
55+
it('proceeds to `next` if valid', () => {
56+
const { req, res, next } = reqresnext({
57+
headers: {
58+
foo: 'bar'
59+
},
60+
params: {
61+
baz: 'qux'
62+
},
63+
query: {
64+
quxx: 'buzz'
65+
},
66+
body: 'FooBarBaz'
67+
}, null, jest.fn())
68+
69+
mware(req, res, next)
70+
71+
expect(next).toHaveBeenCalled()
72+
})
73+
74+
it('returns 404 otherwise', () => {
75+
const { req, res, next } = reqresnext(null, null, jest.fn())
76+
77+
mware(req, res, next)
78+
79+
expect(res.body).toBe("{\"message\":\"Bad Request\",\"details\":\"data.headers should have required property 'foo'\"}")
80+
expect(res.statusCode).toBe(BAD_REQUEST)
81+
expect(next).not.toHaveBeenCalled()
82+
})
83+
})

packages/mware/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ npm i @qiwi/mware
1212
* [mware-logger](../mware-logger/README.md)
1313
* [mware-crumbs](../mware-crumbs/README.md)
1414
* [mware-cors](../mware-cors/README.md)
15+
* [mware-validator](../mware-validator/README.md)
1516

1617
### Usage
1718

packages/mware/src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import mdc from '@qiwi/mware-mdc'
22
import logger from '@qiwi/mware-logger'
33
import crumbs from '@qiwi/mware-crumbs'
44
import cors from '@qiwi/mware-cors'
5+
import validator from '@qiwi/mware-validator'
56
import { util } from '@qiwi/mware-core'
67

78
export {
89
mdc,
910
logger,
1011
cors,
1112
crumbs,
13+
validator,
1214
util
1315
}
1416

@@ -17,5 +19,6 @@ export default {
1719
logger,
1820
cors,
1921
crumbs,
22+
validator,
2023
util
2124
}

packages/mware/test/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import {mdc, logger, util, cors, crumbs} from '../src'
1+
import {mdc, logger, util, cors, crumbs, validator} from '../src'
22

33
describe('mware', () => {
44
it('exposes middlewares collection', () => {
55
expect(mdc).toEqual(expect.any(Function))
66
expect(logger).toEqual(expect.any(Function))
77
expect(cors).toEqual(expect.any(Function))
88
expect(crumbs).toEqual(expect.any(Function))
9+
expect(validator).toEqual(expect.any(Function))
910
expect(util).not.toBeUndefined()
1011
})
1112
})

yarn.lock

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2648,7 +2648,7 @@ debug@^4.0.0, debug@^4.0.1:
26482648
dependencies:
26492649
ms "^2.1.1"
26502650

2651-
debuglog@*, debuglog@^1.0.1:
2651+
debuglog@^1.0.1:
26522652
version "1.0.1"
26532653
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
26542654

@@ -4324,7 +4324,7 @@ import-local@^1.0.0:
43244324
pkg-dir "^2.0.0"
43254325
resolve-cwd "^2.0.0"
43264326

4327-
imurmurhash@*, imurmurhash@^0.1.4:
4327+
imurmurhash@^0.1.4:
43284328
version "0.1.4"
43294329
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
43304330

@@ -5526,39 +5526,17 @@ lockfile@^1.0.4:
55265526
dependencies:
55275527
signal-exit "^3.0.2"
55285528

5529-
lodash._baseindexof@*:
5530-
version "3.1.0"
5531-
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
5532-
55335529
lodash._baseuniq@~4.6.0:
55345530
version "4.6.0"
55355531
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
55365532
dependencies:
55375533
lodash._createset "~4.0.0"
55385534
lodash._root "~3.0.0"
55395535

5540-
lodash._bindcallback@*:
5541-
version "3.0.1"
5542-
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
5543-
5544-
lodash._cacheindexof@*:
5545-
version "3.0.2"
5546-
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
5547-
5548-
lodash._createcache@*:
5549-
version "3.1.2"
5550-
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
5551-
dependencies:
5552-
lodash._getnative "^3.0.0"
5553-
55545536
lodash._createset@~4.0.0:
55555537
version "4.0.3"
55565538
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
55575539

5558-
lodash._getnative@*, lodash._getnative@^3.0.0:
5559-
version "3.9.1"
5560-
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
5561-
55625540
lodash._reinterpolate@~3.0.0:
55635541
version "3.0.0"
55645542
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -5595,10 +5573,6 @@ lodash.isstring@^4.0.1:
55955573
version "4.0.1"
55965574
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
55975575

5598-
lodash.restparam@*:
5599-
version "3.6.1"
5600-
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
5601-
56025576
lodash.sortby@^4.7.0:
56035577
version "4.7.0"
56045578
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -7323,7 +7297,7 @@ readable-stream@~2.1.5:
73237297
string_decoder "~0.10.x"
73247298
util-deprecate "~1.0.1"
73257299

7326-
readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0:
7300+
readdir-scoped-modules@^1.0.0:
73277301
version "1.0.2"
73287302
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
73297303
dependencies:

0 commit comments

Comments
 (0)