Skip to content

Commit

Permalink
Merge pull request #453 from villamide/matched-data-separated
Browse files Browse the repository at this point in the history
Implemented separated matched data for different locations
  • Loading branch information
gustavohenke committed Oct 19, 2017
2 parents 94e8c1f + bb5f928 commit b7dbb5a
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 16 deletions.
23 changes: 19 additions & 4 deletions README.md
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions 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;
Expand All @@ -9,7 +9,8 @@ export const sanitizeParam: SanitizationChainBuilder;
export const sanitizeQuery: SanitizationChainBuilder;

interface MatchedDataOptions {
onlyValidData: boolean
onlyValidData?: boolean
locations?: Location[]
}

export interface SanitizationChainBuilder {
Expand Down
28 changes: 20 additions & 8 deletions 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();
};
};

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);
}
57 changes: 55 additions & 2 deletions filter/matched-data.spec.js
Expand Up @@ -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' }
Expand Down Expand Up @@ -65,4 +65,57 @@ describe('filter: matchedData', () => {
});
});
});
});

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'
});
});
});
});
});
1 change: 1 addition & 0 deletions filter/type-definition.spec.ts
Expand Up @@ -13,6 +13,7 @@ const res: Response = <Response>{};

matchedData(req).foo;
matchedData(req, { onlyValidData: true }).bar;
matchedData(req, { locations: ['body', 'params'] }).bar;

// Sanitization
sanitize('foo')(req, res, () => {});
Expand Down

0 comments on commit b7dbb5a

Please sign in to comment.