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