mano
Version:
Web application framework
322 lines (301 loc) • 10.3 kB
JavaScript
;
var toArray = require('es5-ext/array/to-array')
, remove = require('es5-ext/array/#/remove')
, pluck = require('es5-ext/function/pluck')
, objectForEach = require('es5-ext/object/for-each')
, customError = require('es5-ext/error/custom')
, contains = require('es5-ext/string/#/contains')
, oForEach = require('es5-ext/object/for-each')
, DbjsError = require('dbjs/_setup/error')
, isNested = require('dbjs/is-dbjs-nested-object')
, db = require('../../').db
, unserializeArrays = require('./unserialize-arrays')
, unserializeProperties = require('./unserialize-properties')
, getMessage = require('./get-error-message')
, _ = require('../../').i18n.bind("Controller")
, isArray = Array.isArray, forEach = Array.prototype.forEach, push = Array.prototype.push
, create = Object.create, getPrototypeOf = Object.getPrototypeOf
, getObject = db.objects.unserialize, isObjectType = db.isObjectType
, getMapValue = pluck(1);
var isFileType = function (type) {
return db.File && ((type === db.File) || db.File.isPrototypeOf(type));
};
module.exports = function (data/*, options*/) {
var errors = [], options = Object(arguments[1]), forceUpdate, multiples = {};
// Unserialize data
unserializeArrays(data);
unserializeProperties(data);
unserializeArrays(data);
// Expose mandatory fields
if (options.assure != null) {
forEach.call(options.assure, function (name) {
if (!data.hasOwnProperty(name)) data[name] = null;
});
}
// Prepare forceUpdate map
if (options.forceUpdate != null) {
forceUpdate = {};
if (isArray(options.forceUpdate)) {
options.forceUpdate.forEach(function (name) { forceUpdate[name] = true; });
} else {
forceUpdate[options.forceUpdate] = true;
}
}
// Validate each property
oForEach(data, function (value, name) {
var desc, error, current, obj, values, isMap = false, type, descProto, item, multipleConf, map;
var handleError = function (error) {
if (options.ignoreErrors && (!options.assure || !options.assure[name])) {
delete data[name];
return;
}
error.fieldName = name;
error.object = desc.object;
error.key = desc.key;
error.invalidValue = value;
errors.push(error);
};
// Ignore empty names
if (!name) {
delete data[name];
return;
}
// Deal with eventual no DBJS property
if (!contains.call(name, '/')) {
if (!options.partial) {
errors.push(customError("Unrecognized DBJS value", 'NOT_DBJS_VALUE', { fieldName: name }));
}
return;
}
// DBJS property
desc = getObject(name);
if (desc._kind_ === 'item') {
item = desc;
desc = desc.object._getOwnDescriptor_(desc._pSKey_);
if (value || !isFileType(desc.type)) {
throw new TypeError('Unsupported multiple item configuration');
}
// Delete from set of files
if (!multiples[desc.__valueId__]) {
multiples[desc.__valueId__] = { object: desc.object.get(desc.key), desc: desc };
}
multipleConf = multiples[desc.__valueId__];
if (!multipleConf.deleted) multipleConf.deleted = [];
multipleConf.deleted.push(item.key);
delete data[name];
return;
}
if (desc.nested) {
descProto = desc.object.get(desc.key).__descriptorPrototype__;
if (desc.database.isObjectType(descProto.type)) {
isMap = true;
type = descProto.type;
} else {
type = desc.type;
if (isFileType(type)) {
if (desc.database.isObjectType(desc.object.__descriptorPrototype__.type)) {
map = desc.object;
if (value) throw new TypeError('Unsupported nested map configuration');
// Delete from map of files
if (!multiples[map.__id__]) {
multiples[map.__id__] = {
object: map,
desc: map.owner ? map.owner.getOwnDescriptor(map.key) : null
};
}
multipleConf = multiples[map.__id__];
if (!multipleConf.deleted) multipleConf.deleted = [];
multipleConf.deleted.push(desc.object.get(desc.key));
delete data[name];
return;
}
}
}
} else {
type = desc.type;
}
if ((isMap || desc.multiple) && isArray(value)) {
// Unserialize multiple value
value = data[name] = value.map(type.fromInputValue, type)
.filter(function (value) { return value != null; });
if (isFileType(type)) {
// Add to set or map of files
try {
value.forEach(function (file) { type._validateCreate_(file); });
} catch (e) {
if (e.code === 'UNSUPPORTED_FILE_TYPE') {
e = new Error(_("Uploaded file must be in either JPG, PNG or PDF format"));
}
handleError(e);
return;
}
if (!multiples[name]) multiples[name] = { object: desc.object.get(desc.key), desc: desc };
multiples[name].added = value;
return;
}
} else {
// Unserialize singular value
if (isMap || desc.multiple) {
value = data[name] = type.fromInputValue(value);
value = data[name] = (value == null) ? [] : [value];
if (isFileType(type)) {
// Add to set or map of files
try {
value.forEach(function (file) { type._validateCreate_(file); });
} catch (e) {
if (e.code === 'UNSUPPORTED_FILE_TYPE') {
e = new Error(_("Uploaded file must be in either JPG, PNG or PDF format"));
}
handleError(e);
return;
}
if (!multiples[name]) multiples[name] = { object: desc.object.get(desc.key), desc: desc };
multiples[name].added = value;
return;
}
} else if (isFileType(type)) {
if (value == null) {
value = desc.object.get(desc.key);
if (desc.nested && !value.name) value = null;
if (!desc.nested && (value == null) && options.deleteNulls) {
value = data[name] = undefined;
}
} else {
value = data[name] = type.fromInputValue(value);
}
} else {
value = data[name] = type.fromInputValue(value);
if ((value === null) && options.deleteNulls && (getPrototypeOf(desc)._value_ == null)) {
value = data[name] = undefined;
}
}
}
if (options.changedOnly && (!forceUpdate ||
!forceUpdate.hasOwnProperty(desc._sKey_))) {
current = desc._resolveValue_(desc.object);
// Filter unchanged properties (when changedOnly: true)
if (isMap || desc.multiple) {
if (typeof desc._value_ !== 'function') {
values = toArray(current);
if (isMap) values = values.map(getMapValue);
if ((value.length === values.length) &&
values.every(function (val, index) { return !type.compare(val, value[index]); })) {
delete data[name];
return;
}
}
} else if ((!desc.nested && desc.hasOwnProperty('_value_') && (desc._value_ != null)) ||
((value != null) && options.meta)) {
if (((value === null) && (current === null)) ||
((value != null) && (current != null) &&
!type.compare(value, current))) {
if (options.meta && desc.hasOwnProperty('_value_')) {
current = getPrototypeOf(desc)
._resolveValue_(getPrototypeOf(desc.object));
if (((value === null) && (current === null)) ||
((value != null) && (current != null) &&
!type.compare(value, current))) {
value = data[name] = undefined;
} else {
delete data[name];
return;
}
} else {
delete data[name];
return;
}
} else if ((value === undefined) && (desc._value_ === undefined)) {
delete data[name];
return;
}
} else if (value == null) {
if (!desc.nested) {
delete data[name];
return;
}
}
}
if (!(desc.multiple || isMap) && options.meta && (value !== undefined)) {
current = getPrototypeOf(desc)
._resolveValue_(getPrototypeOf(desc.object));
if (((value === null) && (current === null)) ||
((value != null) && (current != null) &&
!type.compare(value, current))) {
value = data[name] = undefined;
}
}
obj = desc.object;
// Validate values
if (isObjectType(type) && (isArray(value) ||
((value != null) && !db.Object.is(value)))) {
if (isArray(value)) {
value.forEach(function (val, index) {
if (db.Object.is(val)) return;
try {
value[index] = type._validateCreate_(val)[0];
} catch (e) {
if (error && !options.ignoreErrors) {
e.fieldName = name;
errors.push(e);
} else {
error = e;
}
}
});
} else {
try {
data[name] = type._validateCreate_(value)[0];
} catch (e) { error = e; }
}
} else {
if (desc.object.constructor.prototype === desc.object) obj = create(desc.object);
if ((value != null) ||
(!options.changedOnly || (forceUpdate && forceUpdate.hasOwnProperty(desc._sKey_)))) {
try {
if (options.deleteNulls && (value === undefined)) {
obj._validateDelete_(desc._sKey_);
} else {
if (isNested(value) && (name === value.__id__)) {
delete data[name];
} else if (desc.nested) {
obj = obj._get_(desc._sKey_);
if (value == null) {
if (desc.required) {
throw new DbjsError("Property is required", 'VALUE_REQUIRED');
}
} else {
data[name] = obj._validateCreate_(value)[0];
}
} else {
data[name] = obj._validateSet_(desc._sKey_, value);
}
}
} catch (e) { error = e; }
}
}
if (error) handleError(error);
});
objectForEach(multiples, function (conf, id) {
var value = [], deleted = conf.deleted || [], added = conf.added || [], desc = conf.desc;
conf.object.forEach(function (file) { value.push(file); });
if (options.changedOnly && !deleted.length && !added.length) {
delete data[id];
return;
}
deleted.forEach(function (file) { remove.call(value, file); });
added.forEach(function (file) { push.call(value, file); });
if (!value.length && desc && desc.required) {
if (options.ignoreErrors) {
delete data[id];
return;
}
errors.push(customError("Property is rqeuired", 'VALUE_REQUIRED',
{ fieldName: id, object: desc && desc.object, key: desc && desc.key }));
}
data[id] = value;
});
if (errors.length) {
throw customError("Invalid values:\n\t" +
errors.map(getMessage).join('\t\n'), 'INVALID_INPUT', { errors: errors, statusCode: 400 });
}
};