432 lines
35 KiB
JavaScript
432 lines
35 KiB
JavaScript
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// This is a generated file. You can view the original //
|
||
|
// source in your browser if your browser supports source maps. //
|
||
|
// //
|
||
|
// If you are using Chrome, open the Developer Tools and click the gear //
|
||
|
// icon in its lower right corner. In the General Settings panel, turn //
|
||
|
// on 'Enable source maps'. //
|
||
|
// //
|
||
|
// If you are using Firefox 23, go to `about:config` and set the //
|
||
|
// `devtools.debugger.source-maps-enabled` preference to true. //
|
||
|
// (The preference should be on by default in Firefox 24; versions //
|
||
|
// older than 23 do not support source maps.) //
|
||
|
// //
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
/* Imports */
|
||
|
var Meteor = Package.meteor.Meteor;
|
||
|
var _ = Package.underscore._;
|
||
|
var EJSON = Package.ejson.EJSON;
|
||
|
|
||
|
/* Package-scope variables */
|
||
|
var check, Match;
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/check/match.js //
|
||
|
// //
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// XXX docs // 1
|
||
|
// 2
|
||
|
// Things we explicitly do NOT support: // 3
|
||
|
// - heterogenous arrays // 4
|
||
|
// 5
|
||
|
var currentArgumentChecker = new Meteor.EnvironmentVariable; // 6
|
||
|
// 7
|
||
|
/** // 8
|
||
|
* @summary Check that a value matches a [pattern](#matchpatterns). // 9
|
||
|
* If the value does not match the pattern, throw a `Match.Error`. // 10
|
||
|
* // 11
|
||
|
* Particularly useful to assert that arguments to a function have the right // 12
|
||
|
* types and structure. // 13
|
||
|
* @locus Anywhere // 14
|
||
|
* @param {Any} value The value to check // 15
|
||
|
* @param {MatchPattern} pattern The pattern to match // 16
|
||
|
* `value` against // 17
|
||
|
*/ // 18
|
||
|
check = function (value, pattern) { // 19
|
||
|
// Record that check got called, if somebody cared. // 20
|
||
|
// // 21
|
||
|
// We use getOrNullIfOutsideFiber so that it's OK to call check() // 22
|
||
|
// from non-Fiber server contexts; the downside is that if you forget to // 23
|
||
|
// bindEnvironment on some random callback in your method/publisher, // 24
|
||
|
// it might not find the argumentChecker and you'll get an error about // 25
|
||
|
// not checking an argument that it looks like you're checking (instead // 26
|
||
|
// of just getting a "Node code must run in a Fiber" error). // 27
|
||
|
var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber(); // 28
|
||
|
if (argChecker) // 29
|
||
|
argChecker.checking(value); // 30
|
||
|
try { // 31
|
||
|
checkSubtree(value, pattern); // 32
|
||
|
} catch (err) { // 33
|
||
|
if ((err instanceof Match.Error) && err.path) // 34
|
||
|
err.message += " in field " + err.path; // 35
|
||
|
throw err; // 36
|
||
|
} // 37
|
||
|
}; // 38
|
||
|
// 39
|
||
|
/** // 40
|
||
|
* @namespace Match // 41
|
||
|
* @summary The namespace for all Match types and methods. // 42
|
||
|
*/ // 43
|
||
|
Match = { // 44
|
||
|
Optional: function (pattern) { // 45
|
||
|
return new Optional(pattern); // 46
|
||
|
}, // 47
|
||
|
OneOf: function (/*arguments*/) { // 48
|
||
|
return new OneOf(_.toArray(arguments)); // 49
|
||
|
}, // 50
|
||
|
Any: ['__any__'], // 51
|
||
|
Where: function (condition) { // 52
|
||
|
return new Where(condition); // 53
|
||
|
}, // 54
|
||
|
ObjectIncluding: function (pattern) { // 55
|
||
|
return new ObjectIncluding(pattern); // 56
|
||
|
}, // 57
|
||
|
ObjectWithValues: function (pattern) { // 58
|
||
|
return new ObjectWithValues(pattern); // 59
|
||
|
}, // 60
|
||
|
// Matches only signed 32-bit integers // 61
|
||
|
Integer: ['__integer__'], // 62
|
||
|
// 63
|
||
|
// XXX matchers should know how to describe themselves for errors // 64
|
||
|
Error: Meteor.makeErrorType("Match.Error", function (msg) { // 65
|
||
|
this.message = "Match error: " + msg; // 66
|
||
|
// The path of the value that failed to match. Initially empty, this gets // 67
|
||
|
// populated by catching and rethrowing the exception as it goes back up the // 68
|
||
|
// stack. // 69
|
||
|
// E.g.: "vals[3].entity.created" // 70
|
||
|
this.path = ""; // 71
|
||
|
// If this gets sent over DDP, don't give full internal details but at least // 72
|
||
|
// provide something better than 500 Internal server error. // 73
|
||
|
this.sanitizedError = new Meteor.Error(400, "Match failed"); // 74
|
||
|
}), // 75
|
||
|
// 76
|
||
|
// Tests to see if value matches pattern. Unlike check, it merely returns true // 77
|
||
|
// or false (unless an error other than Match.Error was thrown). It does not // 78
|
||
|
// interact with _failIfArgumentsAreNotAllChecked. // 79
|
||
|
// XXX maybe also implement a Match.match which returns more information about // 80
|
||
|
// failures but without using exception handling or doing what check() // 81
|
||
|
// does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion // 82
|
||
|
// 83
|
||
|
/** // 84
|
||
|
* @summary Returns true if the value matches the pattern. // 85
|
||
|
* @locus Anywhere // 86
|
||
|
* @param {Any} value The value to check // 87
|
||
|
* @param {MatchPattern} pattern The pattern to match `value` against // 88
|
||
|
*/ // 89
|
||
|
test: function (value, pattern) { // 90
|
||
|
try { // 91
|
||
|
checkSubtree(value, pattern); // 92
|
||
|
return true; // 93
|
||
|
} catch (e) { // 94
|
||
|
if (e instanceof Match.Error) // 95
|
||
|
return false; // 96
|
||
|
// Rethrow other errors. // 97
|
||
|
throw e; // 98
|
||
|
} // 99
|
||
|
}, // 100
|
||
|
// 101
|
||
|
// Runs `f.apply(context, args)`. If check() is not called on every element of // 102
|
||
|
// `args` (either directly or in the first level of an array), throws an error // 103
|
||
|
// (using `description` in the message). // 104
|
||
|
// // 105
|
||
|
_failIfArgumentsAreNotAllChecked: function (f, context, args, description) { // 106
|
||
|
var argChecker = new ArgumentChecker(args, description); // 107
|
||
|
var result = currentArgumentChecker.withValue(argChecker, function () { // 108
|
||
|
return f.apply(context, args); // 109
|
||
|
}); // 110
|
||
|
// If f didn't itself throw, make sure it checked all of its arguments. // 111
|
||
|
argChecker.throwUnlessAllArgumentsHaveBeenChecked(); // 112
|
||
|
return result; // 113
|
||
|
} // 114
|
||
|
}; // 115
|
||
|
// 116
|
||
|
var Optional = function (pattern) { // 117
|
||
|
this.pattern = pattern; // 118
|
||
|
}; // 119
|
||
|
// 120
|
||
|
var OneOf = function (choices) { // 121
|
||
|
if (_.isEmpty(choices)) // 122
|
||
|
throw new Error("Must provide at least one choice to Match.OneOf"); // 123
|
||
|
this.choices = choices; // 124
|
||
|
}; // 125
|
||
|
// 126
|
||
|
var Where = function (condition) { // 127
|
||
|
this.condition = condition; // 128
|
||
|
}; // 129
|
||
|
// 130
|
||
|
var ObjectIncluding = function (pattern) { // 131
|
||
|
this.pattern = pattern; // 132
|
||
|
}; // 133
|
||
|
// 134
|
||
|
var ObjectWithValues = function (pattern) { // 135
|
||
|
this.pattern = pattern; // 136
|
||
|
}; // 137
|
||
|
// 138
|
||
|
var typeofChecks = [ // 139
|
||
|
[String, "string"], // 140
|
||
|
[Number, "number"], // 141
|
||
|
[Boolean, "boolean"], // 142
|
||
|
// While we don't allow undefined in EJSON, this is good for optional // 143
|
||
|
// arguments with OneOf. // 144
|
||
|
[undefined, "undefined"] // 145
|
||
|
]; // 146
|
||
|
// 147
|
||
|
var checkSubtree = function (value, pattern) { // 148
|
||
|
// Match anything! // 149
|
||
|
if (pattern === Match.Any) // 150
|
||
|
return; // 151
|
||
|
// 152
|
||
|
// Basic atomic types. // 153
|
||
|
// Do not match boxed objects (e.g. String, Boolean) // 154
|
||
|
for (var i = 0; i < typeofChecks.length; ++i) { // 155
|
||
|
if (pattern === typeofChecks[i][0]) { // 156
|
||
|
if (typeof value === typeofChecks[i][1]) // 157
|
||
|
return; // 158
|
||
|
throw new Match.Error("Expected " + typeofChecks[i][1] + ", got " + // 159
|
||
|
typeof value); // 160
|
||
|
} // 161
|
||
|
} // 162
|
||
|
if (pattern === null) { // 163
|
||
|
if (value === null) // 164
|
||
|
return; // 165
|
||
|
throw new Match.Error("Expected null, got " + EJSON.stringify(value)); // 166
|
||
|
} // 167
|
||
|
// 168
|
||
|
// Strings and numbers match literally. Goes well with Match.OneOf. // 169
|
||
|
if (typeof pattern === "string" || typeof pattern === "number") { // 170
|
||
|
if (value === pattern) // 171
|
||
|
return; // 172
|
||
|
throw new Match.Error("Expected " + pattern + ", got " + // 173
|
||
|
EJSON.stringify(value)); // 174
|
||
|
} // 175
|
||
|
// 176
|
||
|
// Match.Integer is special type encoded with array // 177
|
||
|
if (pattern === Match.Integer) { // 178
|
||
|
// There is no consistent and reliable way to check if variable is a 64-bit // 179
|
||
|
// integer. One of the popular solutions is to get reminder of division by 1 // 180
|
||
|
// but this method fails on really large floats with big precision. // 181
|
||
|
// E.g.: 1.348192308491824e+23 % 1 === 0 in V8 // 182
|
||
|
// Bitwise operators work consistantly but always cast variable to 32-bit // 183
|
||
|
// signed integer according to JavaScript specs. // 184
|
||
|
if (typeof value === "number" && (value | 0) === value) // 185
|
||
|
return // 186
|
||
|
throw new Match.Error("Expected Integer, got " // 187
|
||
|
+ (value instanceof Object ? EJSON.stringify(value) : value)); // 188
|
||
|
} // 189
|
||
|
// 190
|
||
|
// "Object" is shorthand for Match.ObjectIncluding({}); // 191
|
||
|
if (pattern === Object) // 192
|
||
|
pattern = Match.ObjectIncluding({}); // 193
|
||
|
// 194
|
||
|
// Array (checked AFTER Any, which is implemented as an Array). // 195
|
||
|
if (pattern instanceof Array) { // 196
|
||
|
if (pattern.length !== 1) // 197
|
||
|
throw Error("Bad pattern: arrays must have one type element" + // 198
|
||
|
EJSON.stringify(pattern)); // 199
|
||
|
if (!_.isArray(value) && !_.isArguments(value)) { // 200
|
||
|
throw new Match.Error("Expected array, got " + EJSON.stringify(value)); // 201
|
||
|
} // 202
|
||
|
// 203
|
||
|
_.each(value, function (valueElement, index) { // 204
|
||
|
try { // 205
|
||
|
checkSubtree(valueElement, pattern[0]); // 206
|
||
|
} catch (err) { // 207
|
||
|
if (err instanceof Match.Error) { // 208
|
||
|
err.path = _prependPath(index, err.path); // 209
|
||
|
} // 210
|
||
|
throw err; // 211
|
||
|
} // 212
|
||
|
}); // 213
|
||
|
return; // 214
|
||
|
} // 215
|
||
|
// 216
|
||
|
// Arbitrary validation checks. The condition can return false or throw a // 217
|
||
|
// Match.Error (ie, it can internally use check()) to fail. // 218
|
||
|
if (pattern instanceof Where) { // 219
|
||
|
if (pattern.condition(value)) // 220
|
||
|
return; // 221
|
||
|
// XXX this error is terrible // 222
|
||
|
throw new Match.Error("Failed Match.Where validation"); // 223
|
||
|
} // 224
|
||
|
// 225
|
||
|
// 226
|
||
|
if (pattern instanceof Optional) // 227
|
||
|
pattern = Match.OneOf(undefined, pattern.pattern); // 228
|
||
|
// 229
|
||
|
if (pattern instanceof OneOf) { // 230
|
||
|
for (var i = 0; i < pattern.choices.length; ++i) { // 231
|
||
|
try { // 232
|
||
|
checkSubtree(value, pattern.choices[i]); // 233
|
||
|
// No error? Yay, return. // 234
|
||
|
return; // 235
|
||
|
} catch (err) { // 236
|
||
|
// Other errors should be thrown. Match errors just mean try another // 237
|
||
|
// choice. // 238
|
||
|
if (!(err instanceof Match.Error)) // 239
|
||
|
throw err; // 240
|
||
|
} // 241
|
||
|
} // 242
|
||
|
// XXX this error is terrible // 243
|
||
|
throw new Match.Error("Failed Match.OneOf or Match.Optional validation"); // 244
|
||
|
} // 245
|
||
|
// 246
|
||
|
// A function that isn't something we special-case is assumed to be a // 247
|
||
|
// constructor. // 248
|
||
|
if (pattern instanceof Function) { // 249
|
||
|
if (value instanceof pattern) // 250
|
||
|
return; // 251
|
||
|
throw new Match.Error("Expected " + (pattern.name || // 252
|
||
|
"particular constructor")); // 253
|
||
|
} // 254
|
||
|
// 255
|
||
|
var unknownKeysAllowed = false; // 256
|
||
|
var unknownKeyPattern; // 257
|
||
|
if (pattern instanceof ObjectIncluding) { // 258
|
||
|
unknownKeysAllowed = true; // 259
|
||
|
pattern = pattern.pattern; // 260
|
||
|
} // 261
|
||
|
if (pattern instanceof ObjectWithValues) { // 262
|
||
|
unknownKeysAllowed = true; // 263
|
||
|
unknownKeyPattern = [pattern.pattern]; // 264
|
||
|
pattern = {}; // no required keys // 265
|
||
|
} // 266
|
||
|
// 267
|
||
|
if (typeof pattern !== "object") // 268
|
||
|
throw Error("Bad pattern: unknown pattern type"); // 269
|
||
|
// 270
|
||
|
// An object, with required and optional keys. Note that this does NOT do // 271
|
||
|
// structural matches against objects of special types that happen to match // 272
|
||
|
// the pattern: this really needs to be a plain old {Object}! // 273
|
||
|
if (typeof value !== 'object') // 274
|
||
|
throw new Match.Error("Expected object, got " + typeof value); // 275
|
||
|
if (value === null) // 276
|
||
|
throw new Match.Error("Expected object, got null"); // 277
|
||
|
if (value.constructor !== Object) // 278
|
||
|
throw new Match.Error("Expected plain object"); // 279
|
||
|
// 280
|
||
|
var requiredPatterns = {}; // 281
|
||
|
var optionalPatterns = {}; // 282
|
||
|
_.each(pattern, function (subPattern, key) { // 283
|
||
|
if (subPattern instanceof Optional) // 284
|
||
|
optionalPatterns[key] = subPattern.pattern; // 285
|
||
|
else // 286
|
||
|
requiredPatterns[key] = subPattern; // 287
|
||
|
}); // 288
|
||
|
// 289
|
||
|
_.each(value, function (subValue, key) { // 290
|
||
|
try { // 291
|
||
|
if (_.has(requiredPatterns, key)) { // 292
|
||
|
checkSubtree(subValue, requiredPatterns[key]); // 293
|
||
|
delete requiredPatterns[key]; // 294
|
||
|
} else if (_.has(optionalPatterns, key)) { // 295
|
||
|
checkSubtree(subValue, optionalPatterns[key]); // 296
|
||
|
} else { // 297
|
||
|
if (!unknownKeysAllowed) // 298
|
||
|
throw new Match.Error("Unknown key"); // 299
|
||
|
if (unknownKeyPattern) { // 300
|
||
|
checkSubtree(subValue, unknownKeyPattern[0]); // 301
|
||
|
} // 302
|
||
|
} // 303
|
||
|
} catch (err) { // 304
|
||
|
if (err instanceof Match.Error) // 305
|
||
|
err.path = _prependPath(key, err.path); // 306
|
||
|
throw err; // 307
|
||
|
} // 308
|
||
|
}); // 309
|
||
|
// 310
|
||
|
_.each(requiredPatterns, function (subPattern, key) { // 311
|
||
|
throw new Match.Error("Missing key '" + key + "'"); // 312
|
||
|
}); // 313
|
||
|
}; // 314
|
||
|
// 315
|
||
|
var ArgumentChecker = function (args, description) { // 316
|
||
|
var self = this; // 317
|
||
|
// Make a SHALLOW copy of the arguments. (We'll be doing identity checks // 318
|
||
|
// against its contents.) // 319
|
||
|
self.args = _.clone(args); // 320
|
||
|
// Since the common case will be to check arguments in order, and we splice // 321
|
||
|
// out arguments when we check them, make it so we splice out from the end // 322
|
||
|
// rather than the beginning. // 323
|
||
|
self.args.reverse(); // 324
|
||
|
self.description = description; // 325
|
||
|
}; // 326
|
||
|
// 327
|
||
|
_.extend(ArgumentChecker.prototype, { // 328
|
||
|
checking: function (value) { // 329
|
||
|
var self = this; // 330
|
||
|
if (self._checkingOneValue(value)) // 331
|
||
|
return; // 332
|
||
|
// Allow check(arguments, [String]) or check(arguments.slice(1), [String]) // 333
|
||
|
// or check([foo, bar], [String]) to count... but only if value wasn't // 334
|
||
|
// itself an argument. // 335
|
||
|
if (_.isArray(value) || _.isArguments(value)) { // 336
|
||
|
_.each(value, _.bind(self._checkingOneValue, self)); // 337
|
||
|
} // 338
|
||
|
}, // 339
|
||
|
_checkingOneValue: function (value) { // 340
|
||
|
var self = this; // 341
|
||
|
for (var i = 0; i < self.args.length; ++i) { // 342
|
||
|
// Is this value one of the arguments? (This can have a false positive if // 343
|
||
|
// the argument is an interned primitive, but it's still a good enough // 344
|
||
|
// check.) // 345
|
||
|
// (NaN is not === to itself, so we have to check specially.) // 346
|
||
|
if (value === self.args[i] || (_.isNaN(value) && _.isNaN(self.args[i]))) { // 347
|
||
|
self.args.splice(i, 1); // 348
|
||
|
return true; // 349
|
||
|
} // 350
|
||
|
} // 351
|
||
|
return false; // 352
|
||
|
}, // 353
|
||
|
throwUnlessAllArgumentsHaveBeenChecked: function () { // 354
|
||
|
var self = this; // 355
|
||
|
if (!_.isEmpty(self.args)) // 356
|
||
|
throw new Error("Did not check() all arguments during " + // 357
|
||
|
self.description); // 358
|
||
|
} // 359
|
||
|
}); // 360
|
||
|
// 361
|
||
|
var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case", // 362
|
||
|
"else", "enum", "eval", "false", "null", "this", "true", "void", "with", // 363
|
||
|
"break", "catch", "class", "const", "super", "throw", "while", "yield", // 364
|
||
|
"delete", "export", "import", "public", "return", "static", "switch", // 365
|
||
|
"typeof", "default", "extends", "finally", "package", "private", "continue", // 366
|
||
|
"debugger", "function", "arguments", "interface", "protected", "implements", // 367
|
||
|
"instanceof"]; // 368
|
||
|
// 369
|
||
|
// Assumes the base of path is already escaped properly // 370
|
||
|
// returns key + base // 371
|
||
|
var _prependPath = function (key, base) { // 372
|
||
|
if ((typeof key) === "number" || key.match(/^[0-9]+$/)) // 373
|
||
|
key = "[" + key + "]"; // 374
|
||
|
else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key)) // 375
|
||
|
key = JSON.stringify([key]); // 376
|
||
|
// 377
|
||
|
if (base && base[0] !== "[") // 378
|
||
|
return key + '.' + base; // 379
|
||
|
return key + base; // 380
|
||
|
}; // 381
|
||
|
// 382
|
||
|
// 383
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
/* Exports */
|
||
|
if (typeof Package === 'undefined') Package = {};
|
||
|
Package.check = {
|
||
|
check: check,
|
||
|
Match: Match
|
||
|
};
|
||
|
|
||
|
})();
|