1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Routines used to validate and normalize arguments.
// TODO(benwells): unit test this file.
var JSONSchemaValidator = require('json_schema').JSONSchemaValidator;
var schemaValidator = new JSONSchemaValidator();
// Validate arguments.
function validate(args, parameterSchemas) {
if (args.length > parameterSchemas.length)
throw new Error("Too many arguments.");
for (var i = 0; i < parameterSchemas.length; i++) {
if (i in args && args[i] !== null && args[i] !== undefined) {
schemaValidator.resetErrors();
schemaValidator.validate(args[i], parameterSchemas[i]);
if (schemaValidator.errors.length == 0)
continue;
var message = "Invalid value for argument " + (i + 1) + ". ";
for (var i = 0, err;
err = schemaValidator.errors[i]; i++) {
if (err.path) {
message += "Property '" + err.path + "': ";
}
message += err.message;
message = message.substring(0, message.length - 1);
message += ", ";
}
message = message.substring(0, message.length - 2);
message += ".";
throw new Error(message);
} else if (!parameterSchemas[i].optional) {
throw new Error("Parameter " + (i + 1) + " (" +
parameterSchemas[i].name + ") is required.");
}
}
}
// Generate all possible signatures for a given API function.
function getSignatures(parameterSchemas) {
if (parameterSchemas.length === 0)
return [[]];
var signatures = [];
var remaining = getSignatures($Array.slice(parameterSchemas, 1));
for (var i = 0; i < remaining.length; i++)
$Array.push(signatures, $Array.concat([parameterSchemas[0]], remaining[i]))
if (parameterSchemas[0].optional)
return $Array.concat(signatures, remaining);
return signatures;
};
// Return true if arguments match a given signature's schema.
function argumentsMatchSignature(args, candidateSignature) {
if (args.length != candidateSignature.length)
return false;
for (var i = 0; i < candidateSignature.length; i++) {
var argType = JSONSchemaValidator.getType(args[i]);
if (!schemaValidator.isValidSchemaType(argType,
candidateSignature[i]))
return false;
}
return true;
};
// Finds the function signature for the given arguments.
function resolveSignature(args, definedSignature) {
var candidateSignatures = getSignatures(definedSignature);
for (var i = 0; i < candidateSignatures.length; i++) {
if (argumentsMatchSignature(args, candidateSignatures[i]))
return candidateSignatures[i];
}
return null;
};
// Returns a string representing the defined signature of the API function.
// Example return value for chrome.windows.getCurrent:
// "windows.getCurrent(optional object populate, function callback)"
function getParameterSignatureString(name, definedSignature) {
var getSchemaTypeString = function(schema) {
var schemaTypes = schemaValidator.getAllTypesForSchema(schema);
var typeName = schemaTypes.join(" or ") + " " + schema.name;
if (schema.optional)
return "optional " + typeName;
return typeName;
};
var typeNames = definedSignature.map(getSchemaTypeString);
return name + "(" + typeNames.join(", ") + ")";
};
// Returns a string representing a call to an API function.
// Example return value for call: chrome.windows.get(1, callback) is:
// "windows.get(int, function)"
function getArgumentSignatureString(name, args) {
var typeNames = args.map(JSONSchemaValidator.getType);
return name + "(" + typeNames.join(", ") + ")";
};
// Finds the correct signature for the given arguments, then validates the
// arguments against that signature. Returns a 'normalized' arguments list
// where nulls are inserted where optional parameters were omitted.
// |args| is expected to be an array.
function normalizeArgumentsAndValidate(args, funDef) {
if (funDef.allowAmbiguousOptionalArguments) {
validate(args, funDef.definition.parameters);
return args;
}
var definedSignature = funDef.definition.parameters;
var resolvedSignature = resolveSignature(args, definedSignature);
if (!resolvedSignature)
throw new Error("Invocation of form " +
getArgumentSignatureString(funDef.name, args) +
" doesn't match definition " +
getParameterSignatureString(funDef.name, definedSignature));
validate(args, resolvedSignature);
var normalizedArgs = [];
var ai = 0;
for (var si = 0; si < definedSignature.length; si++) {
// Handle integer -0 as 0.
if (JSONSchemaValidator.getType(args[ai]) === "integer" && args[ai] === 0)
args[ai] = 0;
if (definedSignature[si] === resolvedSignature[ai])
$Array.push(normalizedArgs, args[ai++]);
else
$Array.push(normalizedArgs, null);
}
return normalizedArgs;
};
// Validates that a given schema for an API function is not ambiguous.
function isFunctionSignatureAmbiguous(functionDef) {
if (functionDef.allowAmbiguousOptionalArguments)
return false;
var signaturesAmbiguous = function(signature1, signature2) {
if (signature1.length != signature2.length)
return false;
for (var i = 0; i < signature1.length; i++) {
if (!schemaValidator.checkSchemaOverlap(
signature1[i], signature2[i]))
return false;
}
return true;
};
var candidateSignatures = getSignatures(functionDef.parameters);
for (var i = 0; i < candidateSignatures.length; i++) {
for (var j = i + 1; j < candidateSignatures.length; j++) {
if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j]))
return true;
}
}
return false;
};
exports.$set('isFunctionSignatureAmbiguous', isFunctionSignatureAmbiguous);
exports.$set('normalizeArgumentsAndValidate', normalizeArgumentsAndValidate);
exports.$set('schemaValidator', schemaValidator);
exports.$set('validate', validate);
|