146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Routines used to validate and normalize arguments. 6868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// TODO(benwells): unit test this file. 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)var JSONSchemaValidator = require('json_schema').JSONSchemaValidator; 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)var schemaValidator = new JSONSchemaValidator(); 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Validate arguments. 13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)function validate(args, parameterSchemas) { 14868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (args.length > parameterSchemas.length) 15868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) throw new Error("Too many arguments."); 16868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) for (var i = 0; i < parameterSchemas.length; i++) { 17868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (i in args && args[i] !== null && args[i] !== undefined) { 18868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) schemaValidator.resetErrors(); 19868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) schemaValidator.validate(args[i], parameterSchemas[i]); 20868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (schemaValidator.errors.length == 0) 21868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) continue; 22868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var message = "Invalid value for argument " + (i + 1) + ". "; 23868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) for (var i = 0, err; 24868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) err = schemaValidator.errors[i]; i++) { 25868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (err.path) { 26868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message += "Property '" + err.path + "': "; 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 28868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message += err.message; 29868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message = message.substring(0, message.length - 1); 30868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message += ", "; 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 32868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message = message.substring(0, message.length - 2); 33868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) message += "."; 34868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) throw new Error(message); 35868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } else if (!parameterSchemas[i].optional) { 36868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) throw new Error("Parameter " + (i + 1) + " (" + 37868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) parameterSchemas[i].name + ") is required."); 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 39868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Generate all possible signatures for a given API function. 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function getSignatures(parameterSchemas) { 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (parameterSchemas.length === 0) 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return [[]]; 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var signatures = []; 47eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch var remaining = getSignatures($Array.slice(parameterSchemas, 1)); 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < remaining.length; i++) 49eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(signatures, $Array.concat([parameterSchemas[0]], remaining[i])) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (parameterSchemas[0].optional) 51eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return $Array.concat(signatures, remaining); 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return signatures; 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Return true if arguments match a given signature's schema. 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function argumentsMatchSignature(args, candidateSignature) { 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (args.length != candidateSignature.length) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < candidateSignature.length; i++) { 60868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var argType = JSONSchemaValidator.getType(args[i]); 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!schemaValidator.isValidSchemaType(argType, 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) candidateSignature[i])) 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Finds the function signature for the given arguments. 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function resolveSignature(args, definedSignature) { 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var candidateSignatures = getSignatures(definedSignature); 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < candidateSignatures.length; i++) { 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (argumentsMatchSignature(args, candidateSignatures[i])) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return candidateSignatures[i]; 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns a string representing the defined signature of the API function. 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Example return value for chrome.windows.getCurrent: 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// "windows.getCurrent(optional object populate, function callback)" 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function getParameterSignatureString(name, definedSignature) { 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var getSchemaTypeString = function(schema) { 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var schemaTypes = schemaValidator.getAllTypesForSchema(schema); 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var typeName = schemaTypes.join(" or ") + " " + schema.name; 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (schema.optional) 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return "optional " + typeName; 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return typeName; 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var typeNames = definedSignature.map(getSchemaTypeString); 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return name + "(" + typeNames.join(", ") + ")"; 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns a string representing a call to an API function. 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Example return value for call: chrome.windows.get(1, callback) is: 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// "windows.get(int, function)" 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function getArgumentSignatureString(name, args) { 97868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var typeNames = args.map(JSONSchemaValidator.getType); 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return name + "(" + typeNames.join(", ") + ")"; 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Finds the correct signature for the given arguments, then validates the 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// arguments against that signature. Returns a 'normalized' arguments list 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// where nulls are inserted where optional parameters were omitted. 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// |args| is expected to be an array. 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function normalizeArgumentsAndValidate(args, funDef) { 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (funDef.allowAmbiguousOptionalArguments) { 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) validate(args, funDef.definition.parameters); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return args; 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var definedSignature = funDef.definition.parameters; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var resolvedSignature = resolveSignature(args, definedSignature); 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!resolvedSignature) 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error("Invocation of form " + 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getArgumentSignatureString(funDef.name, args) + 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) " doesn't match definition " + 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getParameterSignatureString(funDef.name, definedSignature)); 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) validate(args, resolvedSignature); 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var normalizedArgs = []; 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var ai = 0; 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var si = 0; si < definedSignature.length; si++) { 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (definedSignature[si] === resolvedSignature[ai]) 122eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(normalizedArgs, args[ai++]); 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(normalizedArgs, null); 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return normalizedArgs; 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Validates that a given schema for an API function is not ambiguous. 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function isFunctionSignatureAmbiguous(functionDef) { 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (functionDef.allowAmbiguousOptionalArguments) 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var signaturesAmbiguous = function(signature1, signature2) { 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (signature1.length != signature2.length) 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < signature1.length; i++) { 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!schemaValidator.checkSchemaOverlap( 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature1[i], signature2[i])) 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var candidateSignatures = getSignatures(functionDef.parameters); 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < candidateSignatures.length; i++) { 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var j = i + 1; j < candidateSignatures.length; j++) { 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j])) 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exports.isFunctionSignatureAmbiguous = isFunctionSignatureAmbiguous; 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exports.normalizeArgumentsAndValidate = normalizeArgumentsAndValidate; 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exports.schemaValidator = schemaValidator; 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exports.validate = validate; 157