UNPKG

mano

Version:

Web application framework

322 lines (301 loc) 10.3 kB
'use strict'; 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 }); } };