286 lines
7.1 KiB
JavaScript
286 lines
7.1 KiB
JavaScript
'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,
|
|
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, strictNumbers, 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 +
|
|
(strictNumbers ? AND + OK + 'isFinite(' + data + ')' : '') +
|
|
')'
|
|
);
|
|
case 'number':
|
|
return (
|
|
'(typeof ' +
|
|
data +
|
|
EQUAL +
|
|
'"' +
|
|
dataType +
|
|
'"' +
|
|
(strictNumbers ? AND + OK + 'isFinite(' + data + ')' : '') +
|
|
')'
|
|
);
|
|
default:
|
|
return 'typeof ' + data + EQUAL + '"' + dataType + '"';
|
|
}
|
|
}
|
|
|
|
function checkDataTypes(dataTypes, data, strictNumbers) {
|
|
switch (dataTypes.length) {
|
|
case 1:
|
|
return checkDataType(dataTypes[0], data, strictNumbers, 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, strictNumbers, 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');
|
|
}
|
|
|
|
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, '$1');
|
|
}
|
|
|
|
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, '~');
|
|
}
|