-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from paralect/ak_enhancement/rethink-validator
[*] Move validator to middleware
- Loading branch information
Showing
19 changed files
with
455 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
.idea | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
const Joi = require('joi'); | ||
const _ = require('lodash'); | ||
|
||
const joiOptions = { | ||
abortEarly: false, | ||
allowUnknown: true, | ||
stripUnknown: { | ||
objects: true, | ||
}, | ||
}; | ||
|
||
/** | ||
* Parse and return list of errors | ||
* @param {object} joiError | ||
* @return {object[]} | ||
*/ | ||
const parseJoiErrors = (joiError) => { | ||
let resultErrors = []; | ||
if (joiError && _.isArray(joiError.details)) { | ||
resultErrors = joiError.details.map((error) => { | ||
const pathLastPart = error.path.slice(error.path.length - error.context.key.length); | ||
|
||
if (pathLastPart === error.context.key) { | ||
return { [error.path]: error.message }; | ||
} | ||
|
||
return { [error.context.key]: error.message }; | ||
}); | ||
} | ||
|
||
return resultErrors; | ||
}; | ||
|
||
const validate = _.curry((schema, payload) => { | ||
const { error, value } = Joi.validate(payload, schema, joiOptions); | ||
|
||
return { | ||
errors: parseJoiErrors(error), | ||
value, | ||
}; | ||
}); | ||
|
||
module.exports = { | ||
...Joi, | ||
__validate: Joi.validate, | ||
validate, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const Joi = require('./joi.adapter'); | ||
const chai = require('chai'); | ||
|
||
chai.should(); | ||
|
||
describe('joi validator', () => { | ||
it('should apply args partially', () => { | ||
const schema = { | ||
email: Joi.string(), | ||
}; | ||
const validationResult = Joi.validate(schema, { | ||
email: '[email protected]', | ||
}); | ||
|
||
const validationResultPartial = Joi.validate(schema)({ | ||
email: '[email protected]', | ||
}); | ||
|
||
validationResult.should.be.deep.equal(validationResultPartial); | ||
|
||
validationResult.should.be.deep.equal({ | ||
errors: [], | ||
value: { | ||
email: '[email protected]', | ||
}, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const _ = require('lodash'); | ||
|
||
const Symbols = { | ||
PERSISTENT: Symbol('persistent'), | ||
}; | ||
|
||
module.exports.Symbols = Symbols; | ||
|
||
const getValidators = (validators = []) => { | ||
if (_.isFunction(validators)) { | ||
return [validators]; | ||
} | ||
|
||
if (!Array.isArray(validators) || !validators.every(_.isFunction)) { | ||
throw Error('Validators must be a function or array of functions'); | ||
} | ||
|
||
return validators; | ||
}; | ||
|
||
module.exports.validate = (payload, validators = []) => { | ||
const persistentData = payload[Symbols.PERSISTENT]; | ||
return getValidators(validators).reduce(async (result, validator) => { | ||
const data = await result; | ||
|
||
if (data.errors.length) { | ||
return data; | ||
} | ||
|
||
const validationResult = await validator(data.value, persistentData); | ||
|
||
return { | ||
errors: validationResult.errors || [], | ||
value: validationResult.value, | ||
}; | ||
}, { | ||
errors: [], | ||
value: payload, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
const { validate, Symbols } = require('./validator'); | ||
const chai = require('chai'); | ||
|
||
chai.should(); | ||
|
||
describe('validator', () => { | ||
it("should return '{ errors: [], payload }' with empty validators array", async () => { | ||
const validationResult = await validate({}, []); | ||
validationResult.should.be.deep.equal({ | ||
errors: [], | ||
value: {}, | ||
}); | ||
}); | ||
|
||
it('should return the value from the last validator', async () => { | ||
const validators = [ | ||
() => ({ value: 1 }), | ||
() => ({ value: 2 }), | ||
]; | ||
|
||
const validationResult = await validate({}, validators); | ||
validationResult.should.be.deep.equal({ | ||
errors: [], | ||
value: 2, | ||
}); | ||
}); | ||
|
||
it('should skip validators after errors appeared', async () => { | ||
const validators = [ | ||
() => ({ value: 1 }), | ||
() => ({ value: 2, errors: ['Errors was appear'] }), | ||
() => ({ value: 3 }), | ||
]; | ||
|
||
const validationResult = await validate({}, validators); | ||
validationResult.should.be.deep.equal({ | ||
errors: ['Errors was appear'], | ||
value: 2, | ||
}); | ||
}); | ||
|
||
it('should apply persistent data to each validator', async () => { | ||
const persistentData = { | ||
persistant: 'Wow! persistent!', | ||
}; | ||
|
||
const payload = { | ||
[Symbols.PERSISTENT]: persistentData, | ||
}; | ||
|
||
const validators = [ | ||
(data, persistent) => { | ||
persistent.should.be.equal(persistentData); | ||
return { | ||
value: 1, | ||
}; | ||
}, | ||
(data, persist) => { | ||
persist.should.be.equal(persistentData); | ||
return { | ||
value: 2, | ||
}; | ||
}, | ||
]; | ||
|
||
const validationResult = await validate(payload, validators); | ||
validationResult.should.be.deep.equal({ | ||
errors: [], | ||
value: 2, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const { validate, Symbols } = require('helpers/validator'); | ||
|
||
const defaultOptions = { | ||
throwOnInvalid: true, | ||
}; | ||
|
||
const validateMiddleware = (validators = [], options = defaultOptions) => async (ctx, next) => { | ||
const { | ||
throwOnInvalid, | ||
} = { | ||
...defaultOptions, | ||
...options, | ||
}; | ||
|
||
const payload = { | ||
...ctx.request.body, | ||
...ctx.query, | ||
...ctx.params, | ||
[Symbols.PERSISTENT]: ctx, | ||
}; | ||
|
||
const result = await validate(payload, validators); | ||
|
||
if (throwOnInvalid && result.errors.length) { | ||
ctx.body = { | ||
errors: result.errors, | ||
}; | ||
|
||
ctx.throw(400); | ||
} | ||
|
||
ctx.validatedRequest = result; | ||
await next(); | ||
}; | ||
|
||
module.exports = validateMiddleware; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
const path = require('path'); | ||
const _ = require('lodash'); | ||
require('app-module-path').addPath(path.resolve(__dirname, '../')); | ||
|
||
const validate = require('./validate'); | ||
const Joi = require('helpers/joi.adapter'); | ||
const chai = require('chai'); | ||
|
||
chai.should(); | ||
|
||
describe('validator', () => { | ||
const ctx = { | ||
request: { | ||
body: { | ||
test: 'test', | ||
}, | ||
}, | ||
query: {}, | ||
params: {}, | ||
throw: (status) => { | ||
throw new Error(status); | ||
}, | ||
}; | ||
|
||
const noop = () => { }; | ||
|
||
it('should add validatedRequest to ctx', async () => { | ||
const schema = { | ||
test: Joi.string(), | ||
}; | ||
const ctxMock = _.cloneDeep(ctx); | ||
|
||
await validate(Joi.validate(schema))(ctxMock, noop); | ||
ctxMock.validatedRequest.should.deep.equal({ errors: [], value: { test: 'test' } }); | ||
}); | ||
|
||
it('should throw error for wrong validation', async () => { | ||
const schema = { | ||
test: Joi.string().email(), | ||
}; | ||
const ctxMock = _.cloneDeep(ctx); | ||
|
||
try { | ||
await validate(Joi.validate(schema))(ctxMock, noop); | ||
} catch (err) { | ||
err.message.should.be.equal('400'); | ||
} | ||
}); | ||
|
||
|
||
it('should throw error if validators is not a an function', async () => { | ||
const ctxMock = _.cloneDeep(ctx); | ||
|
||
try { | ||
await validate('wrong validate func')(ctxMock, noop); | ||
} catch (err) { | ||
err.message.should.be.equal('Validators must be a function or array of functions'); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
const Router = require('koa-router'); | ||
const validate = require('middlewares/validate'); | ||
const validators = require('./validators'); | ||
|
||
const router = new Router(); | ||
const controller = require('./account.controller'); | ||
|
||
router.post('/signup', controller.signup); | ||
router.get('/verifyEmail/:token', controller.verifyEmail); | ||
router.post('/signin', controller.signin); | ||
router.post('/forgotPassword', controller.forgotPassword); | ||
router.put('/resetPassword', controller.resetPassword); | ||
router.post('/signup', validate(validators.signup), controller.signup); | ||
router.get('/verifyEmail/:token', validate(validators.verifyEmail), controller.verifyEmail); | ||
router.post('/signin', validate(validators.signin), controller.signin); | ||
router.post('/forgotPassword', validate(validators.forgotPassword), controller.forgotPassword); | ||
router.put('/resetPassword', validate(validators.resetPassword), controller.resetPassword); | ||
router.post('/resend', controller.resendVerification); | ||
|
||
module.exports = router.routes(); |
Oops, something went wrong.