Skip to content

Commit

Permalink
Initial commit for 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewKeig committed Mar 2, 2020
1 parent 0c1bb7d commit 6a5dfbd
Show file tree
Hide file tree
Showing 53 changed files with 707 additions and 3,214 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
@@ -0,0 +1,10 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 120
2 changes: 2 additions & 0 deletions .eslintignore
@@ -0,0 +1,2 @@
node_modules
.nyc_output
20 changes: 20 additions & 0 deletions .eslintrc
@@ -0,0 +1,20 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true,
"jest": true
},
"extends": ["airbnb-base"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"arrow-parens": 0,
"eslint-disable-next": 0
}
}
41 changes: 0 additions & 41 deletions .eslintrc.yml

This file was deleted.

24 changes: 24 additions & 0 deletions .github/workflows/nodejs.yml
@@ -0,0 +1,24 @@
name: Node.js CI

on: [push]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [8.x, 10.x, 12.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build --if-present
- run: npm run coverage
env:
CI: true
1 change: 1 addition & 0 deletions .nvmrc
@@ -0,0 +1 @@
v12.13.1
22 changes: 22 additions & 0 deletions lib/ev-options.js
@@ -0,0 +1,22 @@
const http = require('http');
const Joi = require('@hapi/joi');

const evOptions = {
context: null,
status: 400,
statusText: http.STATUS_CODES[400],
};

// validate status..
const evSchema = Joi.object({
context: Joi.boolean(),
status: Joi.number(),
});

const mergeEvOptions = (options) => {
const status = options.status || evOptions.status;
return { ...evOptions, options, statusText: http.STATUS_CODES[status] };
};

exports.mergeEvOptions = mergeEvOptions;
exports.evSchema = evSchema;
3 changes: 3 additions & 0 deletions lib/index.d.ts
Expand Up @@ -5,9 +5,11 @@
/// <reference types="node" />
import { RequestHandler } from "express";
import * as Joi from "joi";

interface ValidatorField {
[key: string]: any;
}

interface Validator {
body?: ValidatorField;
params?: ValidatorField;
Expand All @@ -24,6 +26,7 @@ interface Validator {
}

declare function validate(validator: Validator): RequestHandler;

declare namespace validate {
export class ValidationError {
errors: Messages[];
Expand Down
118 changes: 29 additions & 89 deletions lib/index.js
@@ -1,101 +1,41 @@
'use strict';
var Joi = require('@hapi/joi');
var assignIn = require('lodash/assignIn');
var find = require('lodash/find');
var defaults = require('lodash/defaults');
var ValidationError = require('./validation-error');
const Joi = require('@hapi/joi');

var defaultOptions = {
contextRequest: false,
allowUnknownHeaders: true,
allowUnknownBody: true,
allowUnknownQuery: true,
allowUnknownParams: true,
allowUnknownCookies: true,
status: 400,
statusText: 'Bad Request'
};
var globalOptions = {};

// maps the corresponding request object to an `express-validation` option
var unknownMap = {
headers: 'allowUnknownHeaders',
body: 'allowUnknownBody',
query: 'allowUnknownQuery',
params: 'allowUnknownParams',
cookies: 'allowUnknownCookies'
};

exports = module.exports = function expressValidation (schema) {
if (!schema) throw new Error('Please provide a validation schema');

return function (req, res, next) {
var errors = [];

// Set default options
var options = defaults({}, schema.options || {}, globalOptions, defaultOptions);

// NOTE: mutates `errors`
['headers', 'body', 'query', 'params', 'cookies'].forEach(function (key) {
var allowUnknown = options[unknownMap[key]];
var entireContext = options.contextRequest ? req : null;
if (schema[key]) validate(errors, req[key], schema[key], key, allowUnknown, entireContext);
});
const ExpressValidationError = require('./validation-error');
const { mergeJoiOptions, validateJoiSchema, joiSchema } = require('./joi');
const { mergeEvOptions, evSchema } = require('./ev-options');
const { parameters } = require('./parameters');
const { handleMutation } = require('./mutation');

if (errors && errors.length === 0) return next();
exports.validate = (schema = {}, options = {}, joi = {}) => {
Joi.assert(schema, joiSchema);
Joi.assert(options, evSchema);

return next(new ValidationError(errors, options));
};
};
return async (request, response, next) => {
const evOptions = mergeEvOptions(options);
const joiOptions = mergeJoiOptions(joi, options.context, request);

exports.ValidationError = ValidationError;
const validate = (parameter) => schema[parameter].validateAsync(request[parameter], joiOptions)
.then(result => handleMutation(request[parameter], result.value))
.catch(errors => ({ [parameter]: errors.details }));

exports.options = function (opts) {
if (!opts) {
globalOptions = {};
return;
}
const promises = parameters
.filter(parameter => request[parameter] && schema[parameter])
.map(parameter => validate(parameter));

globalOptions = defaults({}, globalOptions, opts);
};
const results = await Promise.all(promises);

/**
* validate checks the current `Request` for validations
* NOTE: mutates `request` in case the object is valid.
*/
function validate (errObj, request, schema, location, allowUnknown, context) {
if (!request || !schema) return;

var joiOptions = {
context: context || request,
allowUnknown: allowUnknown,
abortEarly: false
};
const errors = results
.filter(item => item)
.reduce((a, b) => a.concat(b), []);

Joi.validate(request, schema, joiOptions, function (errors, value) {
if (!errors || errors.details.length === 0) {
assignIn(request, value); // joi responses are parsed into JSON
return;
if (errors && errors.length > 0) {
return next(new ExpressValidationError(errors[0], evOptions));
}
errors.details.forEach(function (error) {
var errorExists = find(errObj, function (item) {
if (item && item.field === error.path && item.location === location) {
item.messages.push(error.message);
item.types.push(error.type);
return item;
}
return;
});

if (!errorExists) {
errObj.push({
field: error.path,
location: location,
messages: [error.message],
types: [error.type]
});
}

});
});
return next();
};
};

exports.ExpressValidationError = ExpressValidationError;
exports.validateJoiSchema = validateJoiSchema;
23 changes: 23 additions & 0 deletions lib/joi.js
@@ -0,0 +1,23 @@
const Joi = require('@hapi/joi');
const { parameters } = require('./parameters');

const schema = parameters
.reduce((result, item) => ({ ...result, [item]: Joi.any() }), {});

const joiSchema = Joi.object(schema).required().min(1).max(6);

const validateJoiSchema = (schemas) => {
schemas.forEach(sc => Joi.assert(sc, joiSchema));
};

const mergeJoiOptions = (joi, ctx, request) => {
const context = ctx ? request : null;
return { ...joi, warnings: true, context };
};

exports.validateJoiSchema = validateJoiSchema;
exports.joiSchema = joiSchema;
exports.mergeJoiOptions = mergeJoiOptions;

// Joi.object({ context: Joi.boolean() });
// exports.sourceSchema = Joi.string().valid(...parameters);
9 changes: 9 additions & 0 deletions lib/mutation.js
@@ -0,0 +1,9 @@
exports.handleMutation = (request, value) => {
Object.keys(value).forEach(parameter => {
if (request[parameter] === undefined) {
Object.defineProperty(request, parameter, { value: value[parameter], enumerable: true });
}
});

return null;
};
8 changes: 8 additions & 0 deletions lib/parameters.js
@@ -0,0 +1,8 @@
exports.parameters = [
'headers',
'params',
'query',
'cookies',
'signedCookies',
'body',
];
30 changes: 30 additions & 0 deletions lib/reducers.js
@@ -0,0 +1,30 @@

// console.log(errors);

// errors.details.forEach(function (error) {
// const errorExists = errObj.find(function (item) {
// if (item && item.field === error.path && item.location === location) {
// console.log('----', item);
// item.messages.push(error.message);
// item.types.push(error.type);
// return item;
// }
// return;
// });

// if (!errorExists) {
// errObj.push({
// field: error.path,
// location: location,
// messages: [error.message],
// types: [error.type]
// });
// }
// // console.log('errObj', errObj);
// });
// });


// if (this.flatten) {
// return this.errors.map(item => item.messages).reduce((a, b) => a.concat(b), []);
// }
27 changes: 12 additions & 15 deletions lib/validation-error.js
@@ -1,28 +1,25 @@
'use strict';
var map = require('lodash/map');
var flatten = require('lodash/flatten');

function ValidationError (errors, options) {
this.name = 'ValidationError';
this.message = 'validation error';
function ExpressValidationError(errors, options) {
// console.log(errors)
this.name = 'ExpressValidationError';
this.message = 'Express validation error';
this.errors = errors;
this.flatten = options.flatten;
this.status = options.status;
this.statusText = options.statusText;
};
ValidationError.prototype = Object.create(Error.prototype);
Error.captureStackTrace(this, ExpressValidationError);
}

ExpressValidationError.prototype = Object.create(Error.prototype);

ValidationError.prototype.toString = function () {
ExpressValidationError.prototype.toString = function toString() {
return JSON.stringify(this.toJSON());
};

ValidationError.prototype.toJSON = function () {
if (this.flatten) return flatten(map(this.errors, 'messages'));
ExpressValidationError.prototype.toJSON = function toJSON() {
return {
status: this.status,
statusText: this.statusText,
errors: this.errors
errors: this.errors,
};
};

module.exports = ValidationError;
module.exports = ExpressValidationError;
File renamed without changes.
File renamed without changes.

0 comments on commit 6a5dfbd

Please sign in to comment.