mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-21 01:41:46 +03:00
1688b17c49
- General code cleanup - Removed unused notContains rule - Swapped custom empty rule for builtin isEmpty rule - Dropped usage of .extend on validator, as this was removed 2 years ago! - This will allow us to upgrade the validator dependency to a much newer version - Changed our internal validator module to only expose the functions we use. - This gives us a clearer Public API - It makes it easier to see if we are affected by changes in validator - It's still easy to add another validator, we just have to update what we require - We can potentially use this to make smaller builds esp for client-side usage - Once ripped out into a module we can use ES imports :D - Rejigged and _slightly_ improved the tests
128 lines
5.1 KiB
JavaScript
128 lines
5.1 KiB
JavaScript
const _ = require('lodash');
|
|
const Promise = require('bluebird');
|
|
|
|
const tpl = require('@tryghost/tpl');
|
|
const errors = require('@tryghost/errors');
|
|
const {validator, validate} = require('../validation');
|
|
|
|
const schema = require('./schema');
|
|
|
|
const messages = {
|
|
valueCannotBeBlank: 'Value in [{tableName}.{columnKey}] cannot be blank.',
|
|
valueMustBeBoolean: 'Value in [{tableName}.{columnKey}] must be one of true, false, 0 or 1.',
|
|
valueExceedsMaxLength: 'Value in [{tableName}.{columnKey}] exceeds maximum length of {maxlength} characters.',
|
|
valueIsNotInteger: 'Value in [{tableName}.{columnKey}] is not an integer.'
|
|
};
|
|
/**
|
|
* Validate model against schema.
|
|
*
|
|
* ## on model update
|
|
* - only validate changed fields
|
|
* - otherwise we could throw errors which the user is out of control
|
|
* - e.g.
|
|
* - we add a new field without proper validation, release goes out
|
|
* - we add proper validation for a single field
|
|
* - if you call `user.save()` the default fallback in bookshelf is `options.method=update`.
|
|
* - we set `options.method` explicit for adding resources (because otherwise bookshelf uses `update`)
|
|
*
|
|
* ## on model add
|
|
* - validate everything to catch required fields
|
|
*/
|
|
function validateSchema(tableName, model, options) {
|
|
options = options || {};
|
|
|
|
const columns = _.keys(schema[tableName]);
|
|
let validationErrors = [];
|
|
|
|
_.each(columns, function each(columnKey) {
|
|
let message = ''; // KEEP: Validator.js only validates strings.
|
|
const strVal = _.toString(model.get(columnKey));
|
|
|
|
if (options.method !== 'insert' && !_.has(model.changed, columnKey)) {
|
|
return;
|
|
}
|
|
|
|
// check nullable
|
|
if (Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'nullable') &&
|
|
schema[tableName][columnKey].nullable !== true &&
|
|
!Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'defaultTo')
|
|
) {
|
|
if (validator.isEmpty(strVal)) {
|
|
message = tpl(messages.valueCannotBeBlank, {
|
|
tableName: tableName,
|
|
columnKey: columnKey
|
|
});
|
|
validationErrors.push(new errors.ValidationError({
|
|
message: message,
|
|
context: tableName + '.' + columnKey
|
|
}));
|
|
}
|
|
}
|
|
|
|
// validate boolean columns
|
|
if (Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'type')
|
|
&& schema[tableName][columnKey].type === 'bool') {
|
|
if (!(validator.isBoolean(strVal) || validator.isEmpty(strVal))) {
|
|
message = tpl(messages.valueMustBeBoolean, {
|
|
tableName: tableName,
|
|
columnKey: columnKey
|
|
});
|
|
validationErrors.push(new errors.ValidationError({
|
|
message: message,
|
|
context: tableName + '.' + columnKey
|
|
}));
|
|
}
|
|
|
|
// CASE: ensure we transform 0|1 to false|true
|
|
if (!validator.isEmpty(strVal)) {
|
|
model.set(columnKey, !!model.get(columnKey));
|
|
}
|
|
}
|
|
|
|
// TODO: check if mandatory values should be enforced
|
|
if (model.get(columnKey) !== null && model.get(columnKey) !== undefined) {
|
|
// check length
|
|
if (Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'maxlength')) {
|
|
if (!validator.isLength(strVal, 0, schema[tableName][columnKey].maxlength)) {
|
|
message = tpl(messages.valueExceedsMaxLength,
|
|
{
|
|
tableName: tableName,
|
|
columnKey: columnKey,
|
|
maxlength: schema[tableName][columnKey].maxlength
|
|
});
|
|
validationErrors.push(new errors.ValidationError({
|
|
message: message,
|
|
context: tableName + '.' + columnKey
|
|
}));
|
|
}
|
|
}
|
|
|
|
// check validations objects
|
|
if (Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'validations')) {
|
|
validationErrors = validationErrors.concat(validate(strVal, columnKey, schema[tableName][columnKey].validations, tableName));
|
|
}
|
|
|
|
// check type
|
|
if (Object.prototype.hasOwnProperty.call(schema[tableName][columnKey], 'type')) {
|
|
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(strVal)) {
|
|
message = tpl(messages.valueIsNotInteger, {
|
|
tableName: tableName,
|
|
columnKey: columnKey
|
|
});
|
|
validationErrors.push(new errors.ValidationError({
|
|
message: message,
|
|
context: tableName + '.' + columnKey
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (validationErrors.length !== 0) {
|
|
return Promise.reject(validationErrors);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
module.exports = validateSchema;
|