1
|
'use strict';
|
2
|
|
3
|
|
4
|
module.exports = {
|
5
|
copy: copy,
|
6
|
checkDataType: checkDataType,
|
7
|
checkDataTypes: checkDataTypes,
|
8
|
coerceToTypes: coerceToTypes,
|
9
|
toHash: toHash,
|
10
|
getProperty: getProperty,
|
11
|
escapeQuotes: escapeQuotes,
|
12
|
equal: require('fast-deep-equal'),
|
13
|
ucs2length: require('./ucs2length'),
|
14
|
varOccurences: varOccurences,
|
15
|
varReplace: varReplace,
|
16
|
cleanUpCode: cleanUpCode,
|
17
|
finalCleanUpCode: finalCleanUpCode,
|
18
|
schemaHasRules: schemaHasRules,
|
19
|
schemaHasRulesExcept: schemaHasRulesExcept,
|
20
|
schemaUnknownRules: schemaUnknownRules,
|
21
|
toQuotedString: toQuotedString,
|
22
|
getPathExpr: getPathExpr,
|
23
|
getPath: getPath,
|
24
|
getData: getData,
|
25
|
unescapeFragment: unescapeFragment,
|
26
|
unescapeJsonPointer: unescapeJsonPointer,
|
27
|
escapeFragment: escapeFragment,
|
28
|
escapeJsonPointer: escapeJsonPointer
|
29
|
};
|
30
|
|
31
|
|
32
|
function copy(o, to) {
|
33
|
to = to || {};
|
34
|
for (var key in o) to[key] = o[key];
|
35
|
return to;
|
36
|
}
|
37
|
|
38
|
|
39
|
function checkDataType(dataType, data, negate) {
|
40
|
var EQUAL = negate ? ' !== ' : ' === '
|
41
|
, AND = negate ? ' || ' : ' && '
|
42
|
, OK = negate ? '!' : ''
|
43
|
, NOT = negate ? '' : '!';
|
44
|
switch (dataType) {
|
45
|
case 'null': return data + EQUAL + 'null';
|
46
|
case 'array': return OK + 'Array.isArray(' + data + ')';
|
47
|
case 'object': return '(' + OK + data + AND +
|
48
|
'typeof ' + data + EQUAL + '"object"' + AND +
|
49
|
NOT + 'Array.isArray(' + data + '))';
|
50
|
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
|
51
|
NOT + '(' + data + ' % 1)' +
|
52
|
AND + data + EQUAL + data + ')';
|
53
|
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
|
54
|
}
|
55
|
}
|
56
|
|
57
|
|
58
|
function checkDataTypes(dataTypes, data) {
|
59
|
switch (dataTypes.length) {
|
60
|
case 1: return checkDataType(dataTypes[0], data, true);
|
61
|
default:
|
62
|
var code = '';
|
63
|
var types = toHash(dataTypes);
|
64
|
if (types.array && types.object) {
|
65
|
code = types.null ? '(': '(!' + data + ' || ';
|
66
|
code += 'typeof ' + data + ' !== "object")';
|
67
|
delete types.null;
|
68
|
delete types.array;
|
69
|
delete types.object;
|
70
|
}
|
71
|
if (types.number) delete types.integer;
|
72
|
for (var t in types)
|
73
|
code += (code ? ' && ' : '' ) + checkDataType(t, data, true);
|
74
|
|
75
|
return code;
|
76
|
}
|
77
|
}
|
78
|
|
79
|
|
80
|
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
|
81
|
function coerceToTypes(optionCoerceTypes, dataTypes) {
|
82
|
if (Array.isArray(dataTypes)) {
|
83
|
var types = [];
|
84
|
for (var i=0; i<dataTypes.length; i++) {
|
85
|
var t = dataTypes[i];
|
86
|
if (COERCE_TO_TYPES[t]) types[types.length] = t;
|
87
|
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
|
88
|
}
|
89
|
if (types.length) return types;
|
90
|
} else if (COERCE_TO_TYPES[dataTypes]) {
|
91
|
return [dataTypes];
|
92
|
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
|
93
|
return ['array'];
|
94
|
}
|
95
|
}
|
96
|
|
97
|
|
98
|
function toHash(arr) {
|
99
|
var hash = {};
|
100
|
for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
|
101
|
return hash;
|
102
|
}
|
103
|
|
104
|
|
105
|
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
|
106
|
var SINGLE_QUOTE = /'|\\/g;
|
107
|
function getProperty(key) {
|
108
|
return typeof key == 'number'
|
109
|
? '[' + key + ']'
|
110
|
: IDENTIFIER.test(key)
|
111
|
? '.' + key
|
112
|
: "['" + escapeQuotes(key) + "']";
|
113
|
}
|
114
|
|
115
|
|
116
|
function escapeQuotes(str) {
|
117
|
return str.replace(SINGLE_QUOTE, '\\$&')
|
118
|
.replace(/\n/g, '\\n')
|
119
|
.replace(/\r/g, '\\r')
|
120
|
.replace(/\f/g, '\\f')
|
121
|
.replace(/\t/g, '\\t');
|
122
|
}
|
123
|
|
124
|
|
125
|
function varOccurences(str, dataVar) {
|
126
|
dataVar += '[^0-9]';
|
127
|
var matches = str.match(new RegExp(dataVar, 'g'));
|
128
|
return matches ? matches.length : 0;
|
129
|
}
|
130
|
|
131
|
|
132
|
function varReplace(str, dataVar, expr) {
|
133
|
dataVar += '([^0-9])';
|
134
|
expr = expr.replace(/\$/g, '$$$$');
|
135
|
return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
|
136
|
}
|
137
|
|
138
|
|
139
|
var EMPTY_ELSE = /else\s*{\s*}/g
|
140
|
, EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
|
141
|
, EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
|
142
|
function cleanUpCode(out) {
|
143
|
return out.replace(EMPTY_ELSE, '')
|
144
|
.replace(EMPTY_IF_NO_ELSE, '')
|
145
|
.replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
|
146
|
}
|
147
|
|
148
|
|
149
|
var ERRORS_REGEXP = /[^v.]errors/g
|
150
|
, REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
|
151
|
, REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
|
152
|
, RETURN_VALID = 'return errors === 0;'
|
153
|
, RETURN_TRUE = 'validate.errors = null; return true;'
|
154
|
, RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
|
155
|
, RETURN_DATA_ASYNC = 'return data;'
|
156
|
, ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
|
157
|
, REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
|
158
|
|
159
|
function finalCleanUpCode(out, async) {
|
160
|
var matches = out.match(ERRORS_REGEXP);
|
161
|
if (matches && matches.length == 2) {
|
162
|
out = async
|
163
|
? out.replace(REMOVE_ERRORS_ASYNC, '')
|
164
|
.replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
|
165
|
: out.replace(REMOVE_ERRORS, '')
|
166
|
.replace(RETURN_VALID, RETURN_TRUE);
|
167
|
}
|
168
|
|
169
|
matches = out.match(ROOTDATA_REGEXP);
|
170
|
if (!matches || matches.length !== 3) return out;
|
171
|
return out.replace(REMOVE_ROOTDATA, '');
|
172
|
}
|
173
|
|
174
|
|
175
|
function schemaHasRules(schema, rules) {
|
176
|
if (typeof schema == 'boolean') return !schema;
|
177
|
for (var key in schema) if (rules[key]) return true;
|
178
|
}
|
179
|
|
180
|
|
181
|
function schemaHasRulesExcept(schema, rules, exceptKeyword) {
|
182
|
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
|
183
|
for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
|
184
|
}
|
185
|
|
186
|
|
187
|
function schemaUnknownRules(schema, rules) {
|
188
|
if (typeof schema == 'boolean') return;
|
189
|
for (var key in schema) if (!rules[key]) return key;
|
190
|
}
|
191
|
|
192
|
|
193
|
function toQuotedString(str) {
|
194
|
return '\'' + escapeQuotes(str) + '\'';
|
195
|
}
|
196
|
|
197
|
|
198
|
function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
|
199
|
var path = jsonPointers // false by default
|
200
|
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
|
201
|
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
|
202
|
return joinPaths(currentPath, path);
|
203
|
}
|
204
|
|
205
|
|
206
|
function getPath(currentPath, prop, jsonPointers) {
|
207
|
var path = jsonPointers // false by default
|
208
|
? toQuotedString('/' + escapeJsonPointer(prop))
|
209
|
: toQuotedString(getProperty(prop));
|
210
|
return joinPaths(currentPath, path);
|
211
|
}
|
212
|
|
213
|
|
214
|
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
|
215
|
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
|
216
|
function getData($data, lvl, paths) {
|
217
|
var up, jsonPointer, data, matches;
|
218
|
if ($data === '') return 'rootData';
|
219
|
if ($data[0] == '/') {
|
220
|
if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data);
|
221
|
jsonPointer = $data;
|
222
|
data = 'rootData';
|
223
|
} else {
|
224
|
matches = $data.match(RELATIVE_JSON_POINTER);
|
225
|
if (!matches) throw new Error('Invalid JSON-pointer: ' + $data);
|
226
|
up = +matches[1];
|
227
|
jsonPointer = matches[2];
|
228
|
if (jsonPointer == '#') {
|
229
|
if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
|
230
|
return paths[lvl - up];
|
231
|
}
|
232
|
|
233
|
if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
|
234
|
data = 'data' + ((lvl - up) || '');
|
235
|
if (!jsonPointer) return data;
|
236
|
}
|
237
|
|
238
|
var expr = data;
|
239
|
var segments = jsonPointer.split('/');
|
240
|
for (var i=0; i<segments.length; i++) {
|
241
|
var segment = segments[i];
|
242
|
if (segment) {
|
243
|
data += getProperty(unescapeJsonPointer(segment));
|
244
|
expr += ' && ' + data;
|
245
|
}
|
246
|
}
|
247
|
return expr;
|
248
|
}
|
249
|
|
250
|
|
251
|
function joinPaths (a, b) {
|
252
|
if (a == '""') return b;
|
253
|
return (a + ' + ' + b).replace(/' \+ '/g, '');
|
254
|
}
|
255
|
|
256
|
|
257
|
function unescapeFragment(str) {
|
258
|
return unescapeJsonPointer(decodeURIComponent(str));
|
259
|
}
|
260
|
|
261
|
|
262
|
function escapeFragment(str) {
|
263
|
return encodeURIComponent(escapeJsonPointer(str));
|
264
|
}
|
265
|
|
266
|
|
267
|
function escapeJsonPointer(str) {
|
268
|
return str.replace(/~/g, '~0').replace(/\//g, '~1');
|
269
|
}
|
270
|
|
271
|
|
272
|
function unescapeJsonPointer(str) {
|
273
|
return str.replace(/~1/g, '/').replace(/~0/g, '~');
|
274
|
}
|