From 84b2afe7b07f30a8d6bd3bc36671e20ea9589349 Mon Sep 17 00:00:00 2001 From: villamide Date: Fri, 13 Oct 2017 10:11:59 +0200 Subject: [PATCH 1/2] filter: allow customization of req locations in matchedData --- README.md | 23 ++++++++++++--- filter/matched-data.js | 28 ++++++++++++------ filter/matched-data.spec.js | 57 +++++++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0ebd5393..0d6d657d 100644 --- a/README.md +++ b/README.md @@ -180,15 +180,30 @@ These methods are all available via `require('express-validator/filter')`. ### `matchedData(req[, options])` - `req`: the express request object. -- `options` *(optional)*: an object of options. Defaults to `{ onlyValidData: true }` +- `options` *(optional)*: an object which accepts the following options: + - `onlyValidData`: if set to `false`, the returned value includes data from fields + that didn't pass their validations. Defaults to `true`. + - `locations`: an array of locations to extract the data from. The acceptable values include + `body`, `cookies`, `headers`, `param` and `query`. Defaults to `undefined`, which means all locations. > *Returns:* an object of data validated by the `check` APIs. Extracts data validated by the `check` APIs from the request and builds an object with them. Nested paths and wildcards are properly handled as well. -By default, only valid data is included; this means if a field didn't pass -its validation, it won't be included in the returned object. -You can include invalid data by passing the option `onlyValidData` as `false`. +```js +// Suppose the request looks like this: +// req.query = { from: '2017-01-12' } +// req.body = { to: '2017-31-12' } + +app.post('/room-availability', check(['from', 'to']).isISO8601(), (req, res, next) => { + const queryData = matchedData(req, { locations: ['query'] }); + const bodyData = matchedData(req, { locations: ['body'] }); + const allData = matchedData(req); + console.log(queryData); // { from: '2017-01-12' } + console.log(bodyData); // { to: '2017-31-12' } + console.log(allData); // { from: '2017-01-12', to: '2017-31-12' } +}); +``` ### `sanitize(fields)` - `field`: a string or an array of strings of field names to validate against. diff --git a/filter/matched-data.js b/filter/matched-data.js index f6d5bcdd..26a3b651 100644 --- a/filter/matched-data.js +++ b/filter/matched-data.js @@ -1,17 +1,29 @@ const _ = require('lodash'); const selectFields = require('../check/select-fields'); -module.exports = (req, { onlyValidData = true } = {}) => { - const validityFilter = !onlyValidData ? () => true : field => { - return !req._validationErrors.find(error => - error.param === field.path && - error.location === field.location - ); - }; +module.exports = (req, options = {}) => { + const validityFilter = createValidityFilter(req, options); + const locationsFilter = createLocationFilter(options); return _(req._validationContexts) .flatMap(context => selectFields(req, context)) .filter(validityFilter) + .filter(locationsFilter) .reduce((state, field) => _.set(state, field.path, field.value), {}) .valueOf(); -}; \ No newline at end of file +}; + +function createValidityFilter(req, { onlyValidData }) { + onlyValidData = onlyValidData === undefined ? true : onlyValidData; + return !onlyValidData ? () => true : field => { + return !req._validationErrors.find(error => + error.param === field.path && + error.location === field.location + ); + }; +} + +function createLocationFilter({ locations }) { + const allLocations = !Array.isArray(locations) || locations.length === 0; + return allLocations ? () => true : field => locations.includes(field.location); +} \ No newline at end of file diff --git a/filter/matched-data.spec.js b/filter/matched-data.spec.js index b4e2ed32..72df3fec 100644 --- a/filter/matched-data.spec.js +++ b/filter/matched-data.spec.js @@ -31,7 +31,7 @@ describe('filter: matchedData', () => { }); }); - describe('when { onlyValidData: false } flag is passed', () => { + describe('when option onlyValidData is set to false', () => { it('returns object with all data validated in the request', () => { const req = { query: { foo: '123', bar: 'abc', baz: 'def' } @@ -65,4 +65,57 @@ describe('filter: matchedData', () => { }); }); }); -}); \ No newline at end of file + + describe('when option locations is passed', () => { + it('gathers only data from the locations specified in the array', () => { + const req = { + query: { foo: '123', bar: 'abc' }, + body: { baz: '234' } + }; + + return check(['foo', 'bar', 'baz']).isInt()(req, {}, () => {}).then(() => { + const data = matchedData(req, { locations: ['body'] }); + + expect(data).to.eql({ + baz: '234' + }); + }); + }); + + it('gathers data from all locations if empty array', () => { + const req = { + query: { foo: '123', bar: 'abc' }, + body: { baz: '234' } + }; + + return check(['foo', 'bar', 'baz']).isInt()(req, {}, () => {}).then(() => { + const data = matchedData(req, { + locations: [] + }); + + expect(data).to.eql({ + foo: '123', + baz: '234' + }); + }); + }); + + it('gathers data from all locations if not an array', () => { + const req = { + query: { foo: '123', bar: 'abc' }, + body: { baz: '234' } + }; + + return check(['foo', 'bar', 'baz']).isInt()(req, {}, () => {}).then(() => { + const data = matchedData(req, { + locations: false + }); + + expect(data).to.eql({ + foo: '123', + baz: '234' + }); + }); + }); + }); +}); From bb5f928f4de145003c9527cf80681fa31474a367 Mon Sep 17 00:00:00 2001 From: Gustavo Henke Date: Wed, 18 Oct 2017 22:02:36 -0200 Subject: [PATCH 2/2] typescript: add support for locations option in matchedData --- filter/index.d.ts | 5 +++-- filter/type-definition.spec.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/filter/index.d.ts b/filter/index.d.ts index 5fa53b19..eec37e35 100644 --- a/filter/index.d.ts +++ b/filter/index.d.ts @@ -1,5 +1,5 @@ import * as express from 'express'; -import { Sanitizer } from '../shared-typings'; +import { Sanitizer, Location } from '../shared-typings'; export function matchedData (req: express.Request, options?: MatchedDataOptions): { [key: string]: any }; export const sanitize: SanitizationChainBuilder; @@ -9,7 +9,8 @@ export const sanitizeParam: SanitizationChainBuilder; export const sanitizeQuery: SanitizationChainBuilder; interface MatchedDataOptions { - onlyValidData: boolean + onlyValidData?: boolean + locations?: Location[] } export interface SanitizationChainBuilder { diff --git a/filter/type-definition.spec.ts b/filter/type-definition.spec.ts index 32a5446c..6311affc 100644 --- a/filter/type-definition.spec.ts +++ b/filter/type-definition.spec.ts @@ -13,6 +13,7 @@ const res: Response = {}; matchedData(req).foo; matchedData(req, { onlyValidData: true }).bar; +matchedData(req, { locations: ['body', 'params'] }).bar; // Sanitization sanitize('foo')(req, res, () => {});