-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow validating anything in context
This introduces the `getContext` and `setContext` methods to the options object. The `getContext` receives the context and must return the object to be validated. The `setContext` receives the context AND the new values and should return the context. ```js const joiOptions = { convert: true, getContext(context) { return context.params.query; }, setContext(context, newValues) { Object.assign(context.params.query, newValues); return context }, }; ```
- Loading branch information
1 parent
49f8d41
commit 39dc934
Showing
5 changed files
with
257 additions
and
78 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,66 +1,79 @@ | ||
/* eslint comma-dangle: 0, object-shorthand: 0, prefer-arrow-callback: 0*/ /* ES5 code */ | ||
const errors = require('@feathersjs/errors'); | ||
const utils = require('feathers-hooks-common/lib/services'); | ||
const joiErrorsForForms = require('joi-errors-for-forms'); | ||
|
||
// We only directly need the convert option. The others are listed for convenience. | ||
// See defaults at https://hapi.dev/family/joi/api/?v=17.1.0#anyvalidatevalue-options | ||
const joiDefaults = { | ||
abortEarly: true, | ||
allowUnknown: false, | ||
cache: true, | ||
convert: true, | ||
debug: false, | ||
externals: true, | ||
noDefaults: false, | ||
nonEnumerables: false, | ||
presence: 'optional', | ||
skipFunctions: false, | ||
stripUnknown: false | ||
}; | ||
|
||
function setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest) { | ||
if (!['undefined', 'object'].includes(typeof joiOptions)) { | ||
throw new errors.GeneralError('joiOptions must be a valid object.'); | ||
} | ||
|
||
const mergedOptions = Object.assign({}, joiDefaults, joiOptions); | ||
|
||
return async function validateWithJoi(context, next) { | ||
utils.checkContext(context, 'before', ['create', 'update', 'patch'], 'validate-joi'); | ||
const values = utils.getItems(context); | ||
|
||
|
||
try { | ||
const convertedValues = await joiSchema.validateAsync(values, mergedOptions); | ||
|
||
if (mergedOptions.convert === true) { | ||
utils.replaceItems(context, convertedValues); | ||
} | ||
|
||
if (typeof next === 'function') { | ||
return next(null, context); | ||
} | ||
return context; | ||
} catch (error) { | ||
const formErrors = translator(error); | ||
if (formErrors) { | ||
// Hacky, but how else without a custom assert? | ||
const msg = ifTest ? JSON.stringify(formErrors) : 'Invalid data'; | ||
throw new errors.BadRequest(msg, { errors: formErrors }); | ||
} | ||
return formErrors || error; | ||
} | ||
}; | ||
} | ||
|
||
module.exports = { | ||
form: function (joiSchema, joiOptions, translations, ifTest) { | ||
const translator = joiErrorsForForms.form(translations); | ||
return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); | ||
}, | ||
mongoose: function (joiSchema, joiOptions, translations, ifTest) { | ||
const translator = joiErrorsForForms.mongoose(translations); | ||
return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); | ||
} | ||
}; | ||
/* eslint comma-dangle: 0, object-shorthand: 0, prefer-arrow-callback: 0 */ | ||
const errors = require('@feathersjs/errors'); | ||
const utils = require('feathers-hooks-common/lib/services'); | ||
const joiErrorsForForms = require('joi-errors-for-forms'); | ||
|
||
// We only directly need the convert option. The others are listed for convenience. | ||
// See defaults at https://hapi.dev/family/joi/api/?v=17.1.0#anyvalidatevalue-options | ||
const joiDefaults = { | ||
abortEarly: true, | ||
allowUnknown: false, | ||
cache: true, | ||
convert: true, | ||
debug: false, | ||
externals: true, | ||
noDefaults: false, | ||
nonEnumerables: false, | ||
presence: 'optional', | ||
skipFunctions: false, | ||
stripUnknown: false, | ||
getContext: undefined, | ||
setContext: undefined | ||
}; | ||
|
||
function setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest) { | ||
if (!['undefined', 'object'].includes(typeof joiOptions)) { | ||
throw new errors.GeneralError('joiOptions must be a valid object.'); | ||
} | ||
|
||
const { getContext, setContext, ...mergedOptions } = { ...joiDefaults, ...joiOptions }; | ||
|
||
if ((getContext || setContext) && (!getContext || !setContext)) { | ||
throw new errors.GeneralError('getContext and setContext must be used together'); | ||
} | ||
|
||
return async function validateWithJoi(context, next) { | ||
let values; | ||
if (typeof getContext === 'function') { | ||
values = getContext(context); | ||
} else { | ||
values = utils.getItems(context); | ||
} | ||
|
||
try { | ||
const convertedValues = await joiSchema.validateAsync(values, mergedOptions); | ||
|
||
if (mergedOptions.convert === true) { | ||
if (typeof setContext === 'function') { | ||
setContext(context, convertedValues); | ||
} else { | ||
utils.replaceItems(context, convertedValues); | ||
} | ||
} | ||
|
||
if (typeof next === 'function') { | ||
return next(null, context); | ||
} | ||
return context; | ||
} catch (error) { | ||
const formErrors = translator(error); | ||
if (formErrors) { | ||
// Hacky, but how else without a custom assert? | ||
const msg = ifTest ? JSON.stringify(formErrors) : 'Invalid data'; | ||
throw new errors.BadRequest(msg, { errors: formErrors }); | ||
} | ||
return formErrors || error; | ||
} | ||
}; | ||
} | ||
|
||
module.exports = { | ||
form: function (joiSchema, joiOptions, translations, ifTest) { | ||
const translator = joiErrorsForForms.form(translations); | ||
return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); | ||
}, | ||
mongoose: function (joiSchema, joiOptions, translations, ifTest) { | ||
const translator = joiErrorsForForms.mongoose(translations); | ||
return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); | ||
} | ||
}; |
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
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
Oops, something went wrong.