forked from feathersjs-ecosystem/validate-joi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
executable file
·113 lines (98 loc) · 3.73 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* 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');
const Joi = require('joi');
const pick = require('lodash/pick');
// 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) {
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);
}
}
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,
type: context.type,
path: context.path,
method: context.method,
});
}
throw error;
}
};
}
const validators = {
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);
}
};
/**
* The validatedProvidedAttrs hook is great for validating patch requests, where a partial
* schema needs to be validated. It only validates the attributes that have matching keys
* in `context.data`.
* @param {Object} validationsObj - an object containing the raw keys from a service's
* schema object. It cannot be already wrapped in `Joi.object(validationsObject)`.
* @param {JoiOptionsObject} joiOptions
*/
function setupValidateProvidedData(validationsObj, joiOptions) {
if (!validationsObj || typeof validationsObj !== 'object') {
throw new Error('The `validationsObj` argument is required.');
}
return function validatedProvidedData(context) {
if (context.type === 'after') {
throw new Error('validateProvidedData can only be a before hook');
}
const patchAttrs = pick(validationsObj, Object.keys(context.data));
const patchSchema = Joi.object(patchAttrs);
const validateHook = validators.form(patchSchema, joiOptions);
return validateHook(context);
};
}
Object.assign(validators, { validateProvidedData: setupValidateProvidedData });
module.exports = validators;