water-orm
Version:
A monolith version of Standalone waterline ORM
308 lines (199 loc) • 6.16 kB
JavaScript
/**
* Module dependencies
*/
var util = require('@sailshq/lodash');
var sanitize = require('../validator').sanitize;
var _ = require('@sailshq/lodash');
/**
* Public access
*/
module.exports = function (entity) {
return new Anchor(entity);
};
/**
* Constructor of individual instance of Anchor
* Specify the function, object, or list to be anchored
*/
function Anchor (entity) {
if (util.isFunction(entity)) {
this.fn = entity;
throw new Error ('Anchor does not support functions yet!');
}
else {
this.data = entity;
}
return this;
}
/**
* Built-in data type rules
*/
Anchor.prototype.rules = require('./lib/match/rules');
/**
* Enforce that the data matches the specified ruleset
*/
Anchor.prototype.to = function (ruleset, context) {
var errors = [];
// If ruleset doesn't contain any explicit rule keys,
// assume that this is a type
// Look for explicit rules
for (var rule in ruleset) {
if (rule === 'type') {
// Use deep match to descend into the collection and verify each item and/or key
// Stop at default maxDepth (50) to prevent infinite loops in self-associations
errors = errors.concat(Anchor.match.type.call(context, this.data, ruleset.type));
}
// Validate a dbType rule
else if (rule === 'dbType') {
// only if a validation rule exists for it so it doesn't break on an adapter that
// doesn't support the particular dbType
if(Anchor.prototype.rules[ruleset.dbType]) {
errors = errors.concat(Anchor.match.type.call(context, this.data, ruleset.dbType));
}
}
// Validate a non-type rule
else {
// Normalize the value if it looks like a boolean
if(ruleset[rule] === 'true') {
ruleset[rule] = true;
}
if(ruleset[rule] === 'false') {
ruleset[rule] = false;
}
// If the value is false, then we shouldn't even run the validation
if(ruleset[rule] === false) {
break;
}
// If the rule value is a boolean we don't need to pass the value along.
// Otherwise we can pass it along so it's options are available in
// the validation.
var ruleVal = _.isBoolean(ruleset[rule]) ? undefined : ruleset[rule];
errors = errors.concat(Anchor.match.rule.call(context, this.data, rule, ruleVal));
}
}
// If errors exist, return the list of them
if (errors.length) {
return errors;
}
// No errors, so return false
else {
return false;
}
};
Anchor.prototype.hasErrors = Anchor.prototype.to;
/**
* Coerce the data to the specified ruleset if possible
* otherwise throw an error
* Priority: this should probably provide the default
* implementation in Waterline core. Currently it's completely
* up to the adapter to define type coercion.
*
* Which is fine!.. but complicates custom CRUD adapter development.
* Much handier would be an evented architecture, that allows
* for adapter developers to write:
*
{
// Called before find() receives criteria
// Here, criteria refers to just attributes (the `where`)
// limit, skip, and sort are not included
coerceCriteria: function (criteria) {
return criteria;
},
// Called before create() or update() receive values
coerceValues: function () {}
}
*
* Adapter developers would be able to use Anchor.prototype.cast()
* to declaritively define these type coercions.
* Down the line, we could take this further for an even nicer API,
* but for now, this alone would be a nice improvement.
*
*/
Anchor.prototype.cast = function (ruleset) {
todo();
};
/**
* Coerce the data to the specified ruleset no matter what
*/
Anchor.prototype.hurl = function (ruleset) {
// Iterate trough given data attributes
// to check if they exist in the ruleset
for (var attr in this.data) {
if (this.data.hasOwnProperty(attr)) {
// If it doesnt...
if (!ruleset[attr]) {
// Declaring err here as error helpers live in match.js
var err = new Error('Validation error: Attribute \"' + attr + '\" is not in the ruleset.');
// just throw it
throw err;
}
}
}
// Once we make sure that attributes match
// we can just proceed to deepMatch
Anchor.match(this.data, ruleset, this);
};
/**
* Specify default values to automatically populated when undefined
*/
Anchor.prototype.defaults = function (ruleset) {
todo();
};
/**
* Declare a custom data type
* If function definition is specified, `name` is required.
* Otherwise, if dictionary-type `definition` is specified,
* `name` must not be present.
*
* @param {String} name [optional]
* @param {Object|Function} definition
*/
Anchor.prototype.define = function (name, definition) {
// check to see if we have an dictionary
if ( util.isObject(name) ) {
// if so all the attributes should be validation functions
for (var attr in name) {
if(!util.isFunction(name[attr])){
throw new Error('Definition error: \"' + attr + '\" does not have a definition');
}
}
// add the new custom data types
util.extend(Anchor.prototype.rules, name);
return this;
}
if ( util.isFunction(definition) && util.isString(name) ) {
// Add a single data type
Anchor.prototype.rules[name] = definition;
return this;
}
throw new Error('Definition error: \"' + name + '\" is not a valid definition.');
};
/**
* Specify custom ruleset
*/
Anchor.prototype.as = function (ruleset) {
todo();
};
/**
* Specify named arguments and their rulesets as an object
*/
Anchor.prototype.args = function (args) {
todo();
};
/**
* Specify each of the permitted usages for this function
*/
Anchor.prototype.usage = function () {
var usages = util.toArray(arguments);
todo();
};
/**
* Deep-match a complex collection or model against a schema
*/
Anchor.match = require('./lib/match');
/**
* Expose `define` so it can be used globally
*/
module.exports.define = Anchor.prototype.define;
function todo() {
throw new Error('Not implemented yet! If you\'d like to contribute, tweet @mikermcneil.');
}