var f = require('util').format,
Mark = require("markup-js"),
M = require('mstring'),
utils = require('./utils'),
generatePathAndObject = utils.generatePathAndObject;
/**
* @fileOverview The EnumNode class represents a Enumeration validation node.
*/
/**
* The EnumNode class represents a value that can be anything
*
* @class
* @return {EnumNode} a EnumNode instance.
*/
var EnumNode = function(parent, field, options) {
options = options || {};
// Unique id for this node's generated method
this.id = utils.generateId();
// Link to parent node
this.parent = parent;
// The field related to this node
this.field = field;
// Perform type check or not on generation
this.typeCheck = typeof options.typeCheck == 'boolean' ? options.typeCheck : false;
// Validation language
this.validation = options.validation ? options.validation : null;
// Any options
this.options = options;
// All children attached to this node
this.children = [];
// Just some metadata
this.type = 'enum';
}
/**
* Type check value
*
* @method
* @param {boolean} typeCheck type check value
* @return {BooleanNode}
*/
EnumNode.prototype.setTypeCheck = function(typeCheck) {
this.typeCheck = typeCheck;
}
/**
* @ignore
*/
EnumNode.prototype.setDefault = function(value) {
this.defaultValue = value;
}
/**
* Add the array of validator nodes the value must match, can be a string|number|boolean|array|null
*
* @method
* @param {array} enums array of validator nodes the value must match, can be a string|number|boolean|validation node|array
* @return {EnumNode}
*/
EnumNode.prototype.addEnums = function(enums) {
this.enums = enums;
return this;
}
/**
* Return the current object path
*
* @method
* @return {array} an array containing the path to this node
*/
EnumNode.prototype.path = function() {
if(this.parent == null) return [];
return this.parent.path().concat([this.field]);
}
/**
* Generate the code for this node
*
* @method
* @param {object} context the generation context for this node
*/
EnumNode.prototype.generate = function(context) {
// Set self
var self = this;
// Get the path
var path = this.path().join('.');
// Push ourselves to the rules array
context.rules[this.id] = this;
// Validation template
var validationTemplate = M(function(){/***
function enum_validation_{{index}}(path, object, context) {
var valid = false;
if(!(object === undefined)) {
// Enum validations
{{validations}}
// Check if we have the validation
if(!valid && context.failOnFirst) {
throw new ValidationError('field does not match enumeration {{enumeration}}', path, rules[{{ruleIndex}}], object);
} else if(!valid) {
errors.push(new ValidationError('field does not match enumeration {{enumeration}}', path, rules[{{ruleIndex}}], object));
}
}
}
***/});
// Unroll the enum
var validations = self.enums.map(function(x, i) {
// Start conditional
var conditional = '} else ';
if(i == 0) conditional = '';
if(i == self.enums.length) conditional = '}';
// End conditional
var endConditional = '';
if(i == self.enums.length -1 ) endConditional = '}';
// Generate the code
if(typeof x === 'number' || typeof x === 'string' || typeof x === 'boolean' || x === null) {
return Mark.up(M(function(){/***
{{conditional}}if(object === {{value}}) {
valid = true;
{{endConditional}}
***/}), {
value: typeof x === 'string' ? f("'%s'", x) : x,
index: i,
conditional: conditional,
endConditional: endConditional,
enumeration: JSON.stringify(self.enums)
});
} else if(x instanceof Object) {
return Mark.up(M(function(){/***
{{conditional}}if(deepCompareStrict({{value}}, object)) {
valid = true;
{{endConditional}}
***/}), {
value: JSON.stringify(x),
index: i,
conditional: conditional,
endConditional: endConditional,
enumeration: JSON.stringify(self.enums)
});
} else if(Array.isArray(x)) {
return Mark.up(M(function(){/***
{{conditional}}if(deepCompareStrict({{value}}, object)) {
valid = true;
{{endConditional}}
***/}), {
value: JSON.stringify(x),
index: i,
conditional: conditional,
endConditional: endConditional,
enumeration: JSON.stringify(self.enums)
});
} else {
throw new Error(f('value %s is not supported by enum node', x));
}
});
// Rendering context
var renderingOptions = {
index: this.id,
ruleIndex: this.id,
enumeration: JSON.stringify(this.enums),
validations: validations.join('\n'),
}
// Generate path and objectPath
var paths = generatePathAndObject(self, context);
// Generate object validation function
context.functions.push(Mark.up(validationTemplate, renderingOptions));
// Generate function call
context.functionCalls.push(Mark.up(M(function(){/***
enum_validation_{{index}}({{path}}, {{object}}, context);
***/}), {
index: this.id,
path: paths.path,
object: paths.objectPath
}));
}
module.exports = EnumNode;