UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

201 lines (167 loc) 10.4 kB
/** * Module dependencies */ var util = require('util'); var assert = require('assert'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var rttc = require('rttc'); var getModel = require('../../ontology/get-model'); var getAttribute = require('../../ontology/get-attribute'); /** * normalizeComparisonValue() * * Validate and normalize the provided value vs. a particular attribute, * taking `type` into account, as well as whether the referenced attribute is * a singular association or a primary key. And if no such attribute exists, * then this at least ensure the value is JSON-compatible. * ------------------------------------------------------------------------------------------ * This utility is for the purposes of `normalizeConstraint()` (e.g. within `where` clause) * so does not care about required/defaultsTo/etc. It is used for eq constraints, `in` and * `nin` modifiers, as well as comparison modifiers like `!=`, `<`, `>`, `<=`, and `>=`. * * > • It always tolerates `null` (& does not care about required/defaultsTo/etc.) * > • Collection attrs are never allowed. * > (Attempting to use one will cause this to throw a consistency violation error * > so i.e. it should be checked beforehand.) * ------------------------------------------------------------------------------------------ * @param {Ref} value * The eq constraint or modifier to normalize. * * @param {String} attrName * The name of the attribute to check against. * * @param {String} modelIdentity * The identity of the model the attribute belongs to (e.g. "pet" or "user") * * @param {Ref} orm * The Waterline ORM instance. * ------------------------------------------------------------------------------------------ * @returns {Ref} * The provided value, now normalized and guaranteed to match the specified attribute. * This might be the same original reference, or it might not. * ------------------------------------------------------------------------------------------ * @throws {Error} if invalid and cannot be coerced * @property {String} code (=== "E_VALUE_NOT_USABLE") * ------------------------------------------------------------------------------------------ * @throws {Error} If anything unexpected happens, e.g. bad usage, or a failed assertion. * ------------------------------------------------------------------------------------------ */ module.exports = function normalizeComparisonValue (value, attrName, modelIdentity, orm){ if (!_.isString(attrName)) { throw new Error('Consistency violation: This internal utility must always be called with a valid second argument (the attribute name). But instead, got: '+util.inspect(attrName, {depth:5})+''); } if (!_.isString(modelIdentity)) { throw new Error('Consistency violation: This internal utility must always be called with a valid third argument (the model identity). But instead, got: '+util.inspect(modelIdentity, {depth:5})+''); } if (!_.isObject(orm)) { throw new Error('Consistency violation: This internal utility must always be called with a valid fourth argument (the orm instance). But instead, got: '+util.inspect(orm, {depth:5})+''); } if (_.isUndefined(value)) { throw flaverr('E_VALUE_NOT_USABLE', new Error( 'Cannot compare vs. `undefined`!\n'+ '--\n'+ 'Usually, this just means there is some kind of logic error in the code that builds this `where` clause. '+ 'On the other hand, if you purposely built this query with `undefined`, bear in mind that you\'ll '+ 'need to be more explicit: When comparing "emptiness" in a `where` clause, specify null, empty string (\'\'), '+ '0, or false.\n'+ '--' )); }//-• // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Maybe make the RTTC validation in this file strict (instead of also performing light coercion). // On one hand, it's better to show an error than have experience of fetching stuff from the database // be inconsistent with what you can search for. But on the other hand, it's nice to have Waterline // automatically coerce the string "4" into the number 4 (and vice versa) within an eq constraint. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Look up the primary Waterline model and attribute. var WLModel = getModel(modelIdentity, orm); // Try to look up the attribute definition. // (`attrDef` will either be a valid attribute or `undefined`) var attrDef = WLModel.attributes[attrName]; // If this attribute exists, ensure that it is not a plural association. if (attrDef) { assert(!attrDef.collection, 'Should not call this internal utility on a plural association (i.e. `collection` attribute).'); } // ╔╗╔╦ ╦╦ ╦ // ║║║║ ║║ ║ // ╝╚╝╚═╝╩═╝╩═╝ if (_.isNull(value)) { // `null` is always allowed as a constraint. }//‡ // ┌─┐┌─┐┬─┐ ╦ ╦╔╗╔╦═╗╔═╗╔═╗╔═╗╔═╗╔╗╔╦╔═╗╔═╗╔╦╗ ╔═╗╔╦╗╔╦╗╦═╗╦╔╗ ╦ ╦╔╦╗╔═╗ // ├┤ │ │├┬┘ ║ ║║║║╠╦╝║╣ ║ ║ ║║ ╦║║║║╔═╝║╣ ║║ ╠═╣ ║ ║ ╠╦╝║╠╩╗║ ║ ║ ║╣ // └ └─┘┴└─ ╚═╝╝╚╝╩╚═╚═╝╚═╝╚═╝╚═╝╝╚╝╩╚═╝╚═╝═╩╝ ╩ ╩ ╩ ╩ ╩╚═╩╚═╝╚═╝ ╩ ╚═╝ // If unrecognized, normalize the value as if there was a matching attribute w/ `type: 'json'`. // > This is because we don't want to leave potentially-circular/crazy constraints // > in the criteria unless they correspond w/ `type: 'ref'` attributes. else if (!attrDef) { try { value = rttc.validate('json', value); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_VALUE_NOT_USABLE', new Error( 'There is no such attribute declared by this model... which is fine, '+ 'because the model supports unrecognized attributes (`schema: false`). '+ 'However, all comparison values in constraints for unrecognized attributes '+ 'must be JSON-compatible, and this one is not. '+e.message )); default: throw e; } }//>-• }//‡ // ┌─┐┌─┐┬─┐ ╔═╗╦╔╗╔╔═╗╦ ╦╦ ╔═╗╦═╗ ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔ // ├┤ │ │├┬┘ ╚═╗║║║║║ ╦║ ║║ ╠═╣╠╦╝ ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║ // └ └─┘┴└─ ╚═╝╩╝╚╝╚═╝╚═╝╩═╝╩ ╩╩╚═ ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝ else if (attrDef.model) { // Ensure that this is a valid primary key value for the associated model. var associatedPkType = getAttribute(getModel(attrDef.model, orm).primaryKey, attrDef.model, orm).type; try { // Note: While searching for an fk of 3.3 would be weird, we don't // use the `normalizePKValue()` utility here. Instead we simply // use rttc.validate(). // // > (This is just to allow for edge cases where the schema changed // > and some records in the db were not migrated properly.) value = rttc.validate(associatedPkType, value); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_VALUE_NOT_USABLE', new Error( 'The corresponding attribute (`'+attrName+'`) is a singular ("model") association, '+ 'but the provided value does not match the declared type of the primary key attribute '+ 'for the associated model (`'+attrDef.model+'`). '+ e.message )); default: throw e; } }//</catch> }//‡ // ┌─┐┌─┐┬─┐ ╔═╗╦═╗╦╔╦╗╔═╗╦═╗╦ ╦ ╦╔═╔═╗╦ ╦ ╔═╗╔╦╗╔╦╗╦═╗╦╔╗ ╦ ╦╔╦╗╔═╗ // ├┤ │ │├┬┘ ╠═╝╠╦╝║║║║╠═╣╠╦╝╚╦╝ ╠╩╗║╣ ╚╦╝ ╠═╣ ║ ║ ╠╦╝║╠╩╗║ ║ ║ ║╣ // └ └─┘┴└─ ╩ ╩╚═╩╩ ╩╩ ╩╩╚═ ╩ ╩ ╩╚═╝ ╩ ╩ ╩ ╩ ╩ ╩╚═╩╚═╝╚═╝ ╩ ╚═╝ // ┌─┐┬─┐ ╔╦╗╦╔═╗╔═╗╔═╗╦ ╦ ╔═╗╔╗╔╔═╗╔═╗╦ ╦╔═╗ ╔═╗╔╦╗╔╦╗╦═╗╦╔╗ ╦ ╦╔╦╗╔═╗ // │ │├┬┘ ║║║║╚═╗║ ║╣ ║ ║ ╠═╣║║║║╣ ║ ║║ ║╚═╗ ╠═╣ ║ ║ ╠╦╝║╠╩╗║ ║ ║ ║╣ // └─┘┴└─ ╩ ╩╩╚═╝╚═╝╚═╝╩═╝╩═╝╩ ╩╝╚╝╚═╝╚═╝╚═╝╚═╝ ╩ ╩ ╩ ╩ ╩╚═╩╚═╝╚═╝ ╩ ╚═╝ // // Note that even though primary key values have additional rules on top of basic // RTTC type validation, we still treat them the same for our purposes here. // > (That's because we want you to be able to search for things in the database // > that you might not necessarily be possible to create/update in Waterline.) else { if (!_.isString(attrDef.type) || attrDef.type === '') { throw new Error('Consistency violation: There is no way this attribute (`'+attrName+'`) should have been allowed to be registered with neither a `type`, `model`, nor `collection`! Here is the attr def: '+util.inspect(attrDef, {depth:5})+''); } try { value = rttc.validate(attrDef.type, value); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_VALUE_NOT_USABLE', new Error( 'Does not match the declared data type of the corresponding attribute. '+e.message )); default: throw e; } }//</catch> }//>- // Return the normalized value. return value; };