You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1 lines
16 KiB
1 lines
16 KiB
{"version":3,"sources":["check/match.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,W;;AAEA,uC;AACA,2B;;AAEA,4D;;AAEA,G;AACA,mE;AACA,kE;AACA,E;AACA,4E;AACA,uB;AACA,kB;AACA,wC;AACA,qD;AACA,kB;AACA,G;AACA,mC;AACA,qD;AACA,I;AACA,mE;AACA,0E;AACA,sE;AACA,wE;AACA,yE;AACA,8D;AACA,oE;AACA,iB;AACA,+B;AACA,O;AACA,iC;AACA,iB;AACA,iD;AACA,6C;AACA,c;AACA,G;AACA,E;;AAEA,G;AACA,mB;AACA,0D;AACA,G;AACA,S;AACA,gC;AACA,iC;AACA,I;AACA,mC;AACA,2C;AACA,I;AACA,mB;AACA,+B;AACA,gC;AACA,I;AACA,uC;AACA,wC;AACA,I;AACA,wC;AACA,yC;AACA,I;AACA,wC;AACA,2B;;AAEA,mE;AACA,6D;AACA,yC;AACA,6E;AACA,gF;AACA,a;AACA,qC;AACA,mB;AACA,gF;AACA,+D;AACA,gE;AACA,K;;AAEA,gF;AACA,8E;AACA,oD;AACA,gF;AACA,4E;AACA,+E;;AAEA,K;AACA,4D;AACA,oB;AACA,0C;AACA,uE;AACA,K;AACA,mC;AACA,S;AACA,mC;AACA,kB;AACA,iB;AACA,mC;AACA,qB;AACA,8B;AACA,c;AACA,K;AACA,I;;AAEA,gF;AACA,gF;AACA,0C;AACA,I;AACA,8E;AACA,4D;AACA,2E;AACA,oC;AACA,O;AACA,2E;AACA,wD;AACA,kB;AACA,G;AACA,E;;AAEA,mC;AACA,yB;AACA,E;;AAEA,gC;AACA,yB;AACA,uE;AACA,yB;AACA,E;;AAEA,kC;AACA,6B;AACA,E;;AAEA,0C;AACA,yB;AACA,E;;AAEA,2C;AACA,yB;AACA,E;;AAEA,oB;AACA,qB;AACA,qB;AACA,uB;AACA,uE;AACA,0B;AACA,0B;AACA,E;;AAEA,8C;AACA,oB;AACA,4B;AACA,W;;AAEA,wB;AACA,sD;AACA,iD;AACA,yC;AACA,8C;AACA,e;AACA,yE;AACA,0C;AACA,K;AACA,G;AACA,yB;AACA,uB;AACA,a;AACA,0E;AACA,G;;AAEA,sE;AACA,mE;AACA,0B;AACA,a;AACA,4D;AACA,kD;AACA,G;;AAEA,qD;AACA,kC;AACA,+E;AACA,gF;AACA,uE;AACA,kD;AACA,6E;AACA,oD;AACA,2D;AACA,Y;AACA,kD;AACA,8E;AACA,G;;AAEA,yD;AACA,yB;AACA,wC;;AAEA,iE;AACA,iC;AACA,6B;AACA,oE;AACA,4C;AACA,qD;AACA,6E;AACA,K;;AAEA,kD;AACA,W;AACA,+C;AACA,qB;AACA,yC;AACA,mD;AACA,S;AACA,kB;AACA,O;AACA,O;AACA,W;AACA,G;;AAEA,2E;AACA,6D;AACA,iC;AACA,iC;AACA,a;AACA,iC;AACA,2D;AACA,G;;;AAGA,kC;AACA,sD;;AAEA,iC;AACA,sD;AACA,W;AACA,gD;AACA,iC;AACA,e;AACA,qB;AACA,4E;AACA,kB;AACA,0C;AACA,oB;AACA,O;AACA,K;AACA,iC;AACA,6E;AACA,G;;AAEA,uE;AACA,iB;AACA,oC;AACA,iC;AACA,a;AACA,wD;AACA,oE;AACA,G;;AAEA,iC;AACA,wB;AACA,2C;AACA,8B;AACA,8B;AACA,G;AACA,4C;AACA,8B;AACA,0C;AACA,sC;AACA,G;;AAEA,kC;AACA,qD;;AAEA,2E;AACA,6E;AACA,+D;AACA,gC;AACA,kE;AACA,qB;AACA,uD;AACA,mC;AACA,mD;;AAEA,4B;AACA,4B;AACA,8C;AACA,uC;AACA,iD;AACA,Q;AACA,yC;AACA,K;;AAEA,0C;AACA,S;AACA,yC;AACA,sD;AACA,qC;AACA,gD;AACA,sD;AACA,c;AACA,gC;AACA,+C;AACA,gC;AACA,uD;AACA,S;AACA,O;AACA,mB;AACA,qC;AACA,+C;AACA,gB;AACA,K;AACA,K;;AAEA,uD;AACA,uD;AACA,K;AACA,E;;AAEA,oD;AACA,kB;AACA,0E;AACA,2B;AACA,4B;AACA,6E;AACA,4E;AACA,+B;AACA,sB;AACA,iC;AACA,E;;AAEA,qC;AACA,8B;AACA,oB;AACA,sC;AACA,a;AACA,8E;AACA,0E;AACA,0B;AACA,mD;AACA,0D;AACA,K;AACA,I;AACA,uC;AACA,oB;AACA,gD;AACA,+E;AACA,4E;AACA,gB;AACA,mE;AACA,gF;AACA,+B;AACA,oB;AACA,O;AACA,K;AACA,iB;AACA,I;AACA,uD;AACA,oB;AACA,8B;AACA,+D;AACA,wC;AACA,G;AACA,G;;AAEA,+E;AACA,0E;AACA,yE;AACA,uE;AACA,8E;AACA,8E;AACA,gB;;AAEA,uD;AACA,qB;AACA,yC;AACA,yD;AACA,0B;AACA,+E;AACA,gC;;AAEA,8B;AACA,4B;AACA,oB;AACA,E","file":"/packages/check.js","sourcesContent":["// XXX docs\n\n// Things we explicitly do NOT support:\n// - heterogenous arrays\n\nvar currentArgumentChecker = new Meteor.EnvironmentVariable;\n\n/**\n * @summary Check that a value matches a [pattern](#matchpatterns).\n * If the value does not match the pattern, throw a `Match.Error`.\n *\n * Particularly useful to assert that arguments to a function have the right\n * types and structure.\n * @locus Anywhere\n * @param {Any} value The value to check\n * @param {MatchPattern} pattern The pattern to match\n * `value` against\n */\ncheck = function (value, pattern) {\n // Record that check got called, if somebody cared.\n //\n // We use getOrNullIfOutsideFiber so that it's OK to call check()\n // from non-Fiber server contexts; the downside is that if you forget to\n // bindEnvironment on some random callback in your method/publisher,\n // it might not find the argumentChecker and you'll get an error about\n // not checking an argument that it looks like you're checking (instead\n // of just getting a \"Node code must run in a Fiber\" error).\n var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber();\n if (argChecker)\n argChecker.checking(value);\n try {\n checkSubtree(value, pattern);\n } catch (err) {\n if ((err instanceof Match.Error) && err.path)\n err.message += \" in field \" + err.path;\n throw err;\n }\n};\n\n/**\n * @namespace Match\n * @summary The namespace for all Match types and methods.\n */\nMatch = {\n Optional: function (pattern) {\n return new Optional(pattern);\n },\n OneOf: function (/*arguments*/) {\n return new OneOf(_.toArray(arguments));\n },\n Any: ['__any__'],\n Where: function (condition) {\n return new Where(condition);\n },\n ObjectIncluding: function (pattern) {\n return new ObjectIncluding(pattern);\n },\n ObjectWithValues: function (pattern) {\n return new ObjectWithValues(pattern);\n },\n // Matches only signed 32-bit integers\n Integer: ['__integer__'],\n\n // XXX matchers should know how to describe themselves for errors\n Error: Meteor.makeErrorType(\"Match.Error\", function (msg) {\n this.message = \"Match error: \" + msg;\n // The path of the value that failed to match. Initially empty, this gets\n // populated by catching and rethrowing the exception as it goes back up the\n // stack.\n // E.g.: \"vals[3].entity.created\"\n this.path = \"\";\n // If this gets sent over DDP, don't give full internal details but at least\n // provide something better than 500 Internal server error.\n this.sanitizedError = new Meteor.Error(400, \"Match failed\");\n }),\n\n // Tests to see if value matches pattern. Unlike check, it merely returns true\n // or false (unless an error other than Match.Error was thrown). It does not\n // interact with _failIfArgumentsAreNotAllChecked.\n // XXX maybe also implement a Match.match which returns more information about\n // failures but without using exception handling or doing what check()\n // does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion\n\n /**\n * @summary Returns true if the value matches the pattern.\n * @locus Anywhere\n * @param {Any} value The value to check\n * @param {MatchPattern} pattern The pattern to match `value` against\n */\n test: function (value, pattern) {\n try {\n checkSubtree(value, pattern);\n return true;\n } catch (e) {\n if (e instanceof Match.Error)\n return false;\n // Rethrow other errors.\n throw e;\n }\n },\n\n // Runs `f.apply(context, args)`. If check() is not called on every element of\n // `args` (either directly or in the first level of an array), throws an error\n // (using `description` in the message).\n //\n _failIfArgumentsAreNotAllChecked: function (f, context, args, description) {\n var argChecker = new ArgumentChecker(args, description);\n var result = currentArgumentChecker.withValue(argChecker, function () {\n return f.apply(context, args);\n });\n // If f didn't itself throw, make sure it checked all of its arguments.\n argChecker.throwUnlessAllArgumentsHaveBeenChecked();\n return result;\n }\n};\n\nvar Optional = function (pattern) {\n this.pattern = pattern;\n};\n\nvar OneOf = function (choices) {\n if (_.isEmpty(choices))\n throw new Error(\"Must provide at least one choice to Match.OneOf\");\n this.choices = choices;\n};\n\nvar Where = function (condition) {\n this.condition = condition;\n};\n\nvar ObjectIncluding = function (pattern) {\n this.pattern = pattern;\n};\n\nvar ObjectWithValues = function (pattern) {\n this.pattern = pattern;\n};\n\nvar typeofChecks = [\n [String, \"string\"],\n [Number, \"number\"],\n [Boolean, \"boolean\"],\n // While we don't allow undefined in EJSON, this is good for optional\n // arguments with OneOf.\n [undefined, \"undefined\"]\n];\n\nvar checkSubtree = function (value, pattern) {\n // Match anything!\n if (pattern === Match.Any)\n return;\n\n // Basic atomic types.\n // Do not match boxed objects (e.g. String, Boolean)\n for (var i = 0; i < typeofChecks.length; ++i) {\n if (pattern === typeofChecks[i][0]) {\n if (typeof value === typeofChecks[i][1])\n return;\n throw new Match.Error(\"Expected \" + typeofChecks[i][1] + \", got \" +\n typeof value);\n }\n }\n if (pattern === null) {\n if (value === null)\n return;\n throw new Match.Error(\"Expected null, got \" + EJSON.stringify(value));\n }\n\n // Strings and numbers match literally. Goes well with Match.OneOf.\n if (typeof pattern === \"string\" || typeof pattern === \"number\") {\n if (value === pattern)\n return;\n throw new Match.Error(\"Expected \" + pattern + \", got \" +\n EJSON.stringify(value));\n }\n\n // Match.Integer is special type encoded with array\n if (pattern === Match.Integer) {\n // There is no consistent and reliable way to check if variable is a 64-bit\n // integer. One of the popular solutions is to get reminder of division by 1\n // but this method fails on really large floats with big precision.\n // E.g.: 1.348192308491824e+23 % 1 === 0 in V8\n // Bitwise operators work consistantly but always cast variable to 32-bit\n // signed integer according to JavaScript specs.\n if (typeof value === \"number\" && (value | 0) === value)\n return\n throw new Match.Error(\"Expected Integer, got \"\n + (value instanceof Object ? EJSON.stringify(value) : value));\n }\n\n // \"Object\" is shorthand for Match.ObjectIncluding({});\n if (pattern === Object)\n pattern = Match.ObjectIncluding({});\n\n // Array (checked AFTER Any, which is implemented as an Array).\n if (pattern instanceof Array) {\n if (pattern.length !== 1)\n throw Error(\"Bad pattern: arrays must have one type element\" +\n EJSON.stringify(pattern));\n if (!_.isArray(value) && !_.isArguments(value)) {\n throw new Match.Error(\"Expected array, got \" + EJSON.stringify(value));\n }\n\n _.each(value, function (valueElement, index) {\n try {\n checkSubtree(valueElement, pattern[0]);\n } catch (err) {\n if (err instanceof Match.Error) {\n err.path = _prependPath(index, err.path);\n }\n throw err;\n }\n });\n return;\n }\n\n // Arbitrary validation checks. The condition can return false or throw a\n // Match.Error (ie, it can internally use check()) to fail.\n if (pattern instanceof Where) {\n if (pattern.condition(value))\n return;\n // XXX this error is terrible\n throw new Match.Error(\"Failed Match.Where validation\");\n }\n\n\n if (pattern instanceof Optional)\n pattern = Match.OneOf(undefined, pattern.pattern);\n\n if (pattern instanceof OneOf) {\n for (var i = 0; i < pattern.choices.length; ++i) {\n try {\n checkSubtree(value, pattern.choices[i]);\n // No error? Yay, return.\n return;\n } catch (err) {\n // Other errors should be thrown. Match errors just mean try another\n // choice.\n if (!(err instanceof Match.Error))\n throw err;\n }\n }\n // XXX this error is terrible\n throw new Match.Error(\"Failed Match.OneOf or Match.Optional validation\");\n }\n\n // A function that isn't something we special-case is assumed to be a\n // constructor.\n if (pattern instanceof Function) {\n if (value instanceof pattern)\n return;\n throw new Match.Error(\"Expected \" + (pattern.name ||\n \"particular constructor\"));\n }\n\n var unknownKeysAllowed = false;\n var unknownKeyPattern;\n if (pattern instanceof ObjectIncluding) {\n unknownKeysAllowed = true;\n pattern = pattern.pattern;\n }\n if (pattern instanceof ObjectWithValues) {\n unknownKeysAllowed = true;\n unknownKeyPattern = [pattern.pattern];\n pattern = {}; // no required keys\n }\n\n if (typeof pattern !== \"object\")\n throw Error(\"Bad pattern: unknown pattern type\");\n\n // An object, with required and optional keys. Note that this does NOT do\n // structural matches against objects of special types that happen to match\n // the pattern: this really needs to be a plain old {Object}!\n if (typeof value !== 'object')\n throw new Match.Error(\"Expected object, got \" + typeof value);\n if (value === null)\n throw new Match.Error(\"Expected object, got null\");\n if (value.constructor !== Object)\n throw new Match.Error(\"Expected plain object\");\n\n var requiredPatterns = {};\n var optionalPatterns = {};\n _.each(pattern, function (subPattern, key) {\n if (subPattern instanceof Optional)\n optionalPatterns[key] = subPattern.pattern;\n else\n requiredPatterns[key] = subPattern;\n });\n\n _.each(value, function (subValue, key) {\n try {\n if (_.has(requiredPatterns, key)) {\n checkSubtree(subValue, requiredPatterns[key]);\n delete requiredPatterns[key];\n } else if (_.has(optionalPatterns, key)) {\n checkSubtree(subValue, optionalPatterns[key]);\n } else {\n if (!unknownKeysAllowed)\n throw new Match.Error(\"Unknown key\");\n if (unknownKeyPattern) {\n checkSubtree(subValue, unknownKeyPattern[0]);\n }\n }\n } catch (err) {\n if (err instanceof Match.Error)\n err.path = _prependPath(key, err.path);\n throw err;\n }\n });\n\n _.each(requiredPatterns, function (subPattern, key) {\n throw new Match.Error(\"Missing key '\" + key + \"'\");\n });\n};\n\nvar ArgumentChecker = function (args, description) {\n var self = this;\n // Make a SHALLOW copy of the arguments. (We'll be doing identity checks\n // against its contents.)\n self.args = _.clone(args);\n // Since the common case will be to check arguments in order, and we splice\n // out arguments when we check them, make it so we splice out from the end\n // rather than the beginning.\n self.args.reverse();\n self.description = description;\n};\n\n_.extend(ArgumentChecker.prototype, {\n checking: function (value) {\n var self = this;\n if (self._checkingOneValue(value))\n return;\n // Allow check(arguments, [String]) or check(arguments.slice(1), [String])\n // or check([foo, bar], [String]) to count... but only if value wasn't\n // itself an argument.\n if (_.isArray(value) || _.isArguments(value)) {\n _.each(value, _.bind(self._checkingOneValue, self));\n }\n },\n _checkingOneValue: function (value) {\n var self = this;\n for (var i = 0; i < self.args.length; ++i) {\n // Is this value one of the arguments? (This can have a false positive if\n // the argument is an interned primitive, but it's still a good enough\n // check.)\n // (NaN is not === to itself, so we have to check specially.)\n if (value === self.args[i] || (_.isNaN(value) && _.isNaN(self.args[i]))) {\n self.args.splice(i, 1);\n return true;\n }\n }\n return false;\n },\n throwUnlessAllArgumentsHaveBeenChecked: function () {\n var self = this;\n if (!_.isEmpty(self.args))\n throw new Error(\"Did not check() all arguments during \" +\n self.description);\n }\n});\n\nvar _jsKeywords = [\"do\", \"if\", \"in\", \"for\", \"let\", \"new\", \"try\", \"var\", \"case\",\n \"else\", \"enum\", \"eval\", \"false\", \"null\", \"this\", \"true\", \"void\", \"with\",\n \"break\", \"catch\", \"class\", \"const\", \"super\", \"throw\", \"while\", \"yield\",\n \"delete\", \"export\", \"import\", \"public\", \"return\", \"static\", \"switch\",\n \"typeof\", \"default\", \"extends\", \"finally\", \"package\", \"private\", \"continue\",\n \"debugger\", \"function\", \"arguments\", \"interface\", \"protected\", \"implements\",\n \"instanceof\"];\n\n// Assumes the base of path is already escaped properly\n// returns key + base\nvar _prependPath = function (key, base) {\n if ((typeof key) === \"number\" || key.match(/^[0-9]+$/))\n key = \"[\" + key + \"]\";\n else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key))\n key = JSON.stringify([key]);\n\n if (base && base[0] !== \"[\")\n return key + '.' + base;\n return key + base;\n};\n\n"]} |