| 'use strict'; |
| |
| |
| module.exports = { |
| copy: copy, |
| checkDataType: checkDataType, |
| checkDataTypes: checkDataTypes, |
| coerceToTypes: coerceToTypes, |
| toHash: toHash, |
| getProperty: getProperty, |
| escapeQuotes: escapeQuotes, |
| equal: require('fast-deep-equal'), |
| ucs2length: require('./ucs2length'), |
| varOccurences: varOccurences, |
| varReplace: varReplace, |
| cleanUpCode: cleanUpCode, |
| finalCleanUpCode: finalCleanUpCode, |
| schemaHasRules: schemaHasRules, |
| schemaHasRulesExcept: schemaHasRulesExcept, |
| schemaUnknownRules: schemaUnknownRules, |
| toQuotedString: toQuotedString, |
| getPathExpr: getPathExpr, |
| getPath: getPath, |
| getData: getData, |
| unescapeFragment: unescapeFragment, |
| unescapeJsonPointer: unescapeJsonPointer, |
| escapeFragment: escapeFragment, |
| escapeJsonPointer: escapeJsonPointer |
| }; |
| |
| |
| function copy(o, to) { |
| to = to || {}; |
| for (var key in o) to[key] = o[key]; |
| return to; |
| } |
| |
| |
| function checkDataType(dataType, data, negate) { |
| var EQUAL = negate ? ' !== ' : ' === ' |
| , AND = negate ? ' || ' : ' && ' |
| , OK = negate ? '!' : '' |
| , NOT = negate ? '' : '!'; |
| switch (dataType) { |
| case 'null': return data + EQUAL + 'null'; |
| case 'array': return OK + 'Array.isArray(' + data + ')'; |
| case 'object': return '(' + OK + data + AND + |
| 'typeof ' + data + EQUAL + '"object"' + AND + |
| NOT + 'Array.isArray(' + data + '))'; |
| case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + |
| NOT + '(' + data + ' % 1)' + |
| AND + data + EQUAL + data + ')'; |
| default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; |
| } |
| } |
| |
| |
| function checkDataTypes(dataTypes, data) { |
| switch (dataTypes.length) { |
| case 1: return checkDataType(dataTypes[0], data, true); |
| default: |
| var code = ''; |
| var types = toHash(dataTypes); |
| if (types.array && types.object) { |
| code = types.null ? '(': '(!' + data + ' || '; |
| code += 'typeof ' + data + ' !== "object")'; |
| delete types.null; |
| delete types.array; |
| delete types.object; |
| } |
| if (types.number) delete types.integer; |
| for (var t in types) |
| code += (code ? ' && ' : '' ) + checkDataType(t, data, true); |
| |
| return code; |
| } |
| } |
| |
| |
| var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]); |
| function coerceToTypes(optionCoerceTypes, dataTypes) { |
| if (Array.isArray(dataTypes)) { |
| var types = []; |
| for (var i=0; i<dataTypes.length; i++) { |
| var t = dataTypes[i]; |
| if (COERCE_TO_TYPES[t]) types[types.length] = t; |
| else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t; |
| } |
| if (types.length) return types; |
| } else if (COERCE_TO_TYPES[dataTypes]) { |
| return [dataTypes]; |
| } else if (optionCoerceTypes === 'array' && dataTypes === 'array') { |
| return ['array']; |
| } |
| } |
| |
| |
| function toHash(arr) { |
| var hash = {}; |
| for (var i=0; i<arr.length; i++) hash[arr[i]] = true; |
| return hash; |
| } |
| |
| |
| var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; |
| var SINGLE_QUOTE = /'|\\/g; |
| function getProperty(key) { |
| return typeof key == 'number' |
| ? '[' + key + ']' |
| : IDENTIFIER.test(key) |
| ? '.' + key |
| : "['" + escapeQuotes(key) + "']"; |
| } |
| |
| |
| function escapeQuotes(str) { |
| return str.replace(SINGLE_QUOTE, '\\$&') |
| .replace(/\n/g, '\\n') |
| .replace(/\r/g, '\\r') |
| .replace(/\f/g, '\\f') |
| .replace(/\t/g, '\\t'); |
| } |
| |
| |
| function varOccurences(str, dataVar) { |
| dataVar += '[^0-9]'; |
| var matches = str.match(new RegExp(dataVar, 'g')); |
| return matches ? matches.length : 0; |
| } |
| |
| |
| function varReplace(str, dataVar, expr) { |
| dataVar += '([^0-9])'; |
| expr = expr.replace(/\$/g, '$$$$'); |
| return str.replace(new RegExp(dataVar, 'g'), expr + '$1'); |
| } |
| |
| |
| var EMPTY_ELSE = /else\s*{\s*}/g |
| , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g |
| , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g; |
| function cleanUpCode(out) { |
| return out.replace(EMPTY_ELSE, '') |
| .replace(EMPTY_IF_NO_ELSE, '') |
| .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))'); |
| } |
| |
| |
| var ERRORS_REGEXP = /[^v.]errors/g |
| , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g |
| , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g |
| , RETURN_VALID = 'return errors === 0;' |
| , RETURN_TRUE = 'validate.errors = null; return true;' |
| , RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/ |
| , RETURN_DATA_ASYNC = 'return data;' |
| , ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g |
| , REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/; |
| |
| function finalCleanUpCode(out, async) { |
| var matches = out.match(ERRORS_REGEXP); |
| if (matches && matches.length == 2) { |
| out = async |
| ? out.replace(REMOVE_ERRORS_ASYNC, '') |
| .replace(RETURN_ASYNC, RETURN_DATA_ASYNC) |
| : out.replace(REMOVE_ERRORS, '') |
| .replace(RETURN_VALID, RETURN_TRUE); |
| } |
| |
| matches = out.match(ROOTDATA_REGEXP); |
| if (!matches || matches.length !== 3) return out; |
| return out.replace(REMOVE_ROOTDATA, ''); |
| } |
| |
| |
| function schemaHasRules(schema, rules) { |
| if (typeof schema == 'boolean') return !schema; |
| for (var key in schema) if (rules[key]) return true; |
| } |
| |
| |
| function schemaHasRulesExcept(schema, rules, exceptKeyword) { |
| if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not'; |
| for (var key in schema) if (key != exceptKeyword && rules[key]) return true; |
| } |
| |
| |
| function schemaUnknownRules(schema, rules) { |
| if (typeof schema == 'boolean') return; |
| for (var key in schema) if (!rules[key]) return key; |
| } |
| |
| |
| function toQuotedString(str) { |
| return '\'' + escapeQuotes(str) + '\''; |
| } |
| |
| |
| function getPathExpr(currentPath, expr, jsonPointers, isNumber) { |
| var path = jsonPointers // false by default |
| ? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')') |
| : (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\''); |
| return joinPaths(currentPath, path); |
| } |
| |
| |
| function getPath(currentPath, prop, jsonPointers) { |
| var path = jsonPointers // false by default |
| ? toQuotedString('/' + escapeJsonPointer(prop)) |
| : toQuotedString(getProperty(prop)); |
| return joinPaths(currentPath, path); |
| } |
| |
| |
| var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; |
| var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; |
| function getData($data, lvl, paths) { |
| var up, jsonPointer, data, matches; |
| if ($data === '') return 'rootData'; |
| if ($data[0] == '/') { |
| if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data); |
| jsonPointer = $data; |
| data = 'rootData'; |
| } else { |
| matches = $data.match(RELATIVE_JSON_POINTER); |
| if (!matches) throw new Error('Invalid JSON-pointer: ' + $data); |
| up = +matches[1]; |
| jsonPointer = matches[2]; |
| if (jsonPointer == '#') { |
| if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); |
| return paths[lvl - up]; |
| } |
| |
| if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); |
| data = 'data' + ((lvl - up) || ''); |
| if (!jsonPointer) return data; |
| } |
| |
| var expr = data; |
| var segments = jsonPointer.split('/'); |
| for (var i=0; i<segments.length; i++) { |
| var segment = segments[i]; |
| if (segment) { |
| data += getProperty(unescapeJsonPointer(segment)); |
| expr += ' && ' + data; |
| } |
| } |
| return expr; |
| } |
| |
| |
| function joinPaths (a, b) { |
| if (a == '""') return b; |
| return (a + ' + ' + b).replace(/' \+ '/g, ''); |
| } |
| |
| |
| function unescapeFragment(str) { |
| return unescapeJsonPointer(decodeURIComponent(str)); |
| } |
| |
| |
| function escapeFragment(str) { |
| return encodeURIComponent(escapeJsonPointer(str)); |
| } |
| |
| |
| function escapeJsonPointer(str) { |
| return str.replace(/~/g, '~0').replace(/\//g, '~1'); |
| } |
| |
| |
| function unescapeJsonPointer(str) { |
| return str.replace(/~1/g, '/').replace(/~0/g, '~'); |
| } |