@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.ad8f60cf1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm2017.js +606 -333
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1147 -719
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +606 -333
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1092 -660
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +3 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +65 -1
- package/dist/src/manifestUpdater.d.ts +5 -2
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +4 -2
- package/dist/src/utils.d.ts +9 -28
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -25,6 +25,21 @@ var ErrorType;
|
|
|
25
25
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
26
26
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
27
27
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
28
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
29
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
30
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
31
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
32
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
33
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
34
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
35
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
36
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
37
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
38
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
39
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
40
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
41
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
42
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
28
43
|
ErrorType["Cancelled"] = "cancelled";
|
|
29
44
|
ErrorType["Unknown"] = "unknown";
|
|
30
45
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -72,7 +87,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
72
87
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
73
88
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
74
89
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
75
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
90
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
76
91
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
77
92
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
78
93
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -92,9 +107,9 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
92
107
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
93
108
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
94
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
110
|
+
ConstantString.ImageType = "Image";
|
|
95
111
|
ConstantString.ContainerType = "Container";
|
|
96
112
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
97
|
-
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
98
113
|
ConstantString.ResponseCodeFor20X = [
|
|
99
114
|
"200",
|
|
100
115
|
"201",
|
|
@@ -155,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
155
170
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
156
171
|
ConstantString.CommandTitleMaxLens = 32;
|
|
157
172
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
158
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
173
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
174
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
159
175
|
|
|
160
176
|
// Copyright (c) Microsoft Corporation.
|
|
161
177
|
class Utils {
|
|
@@ -170,221 +186,9 @@ class Utils {
|
|
|
170
186
|
}
|
|
171
187
|
return false;
|
|
172
188
|
}
|
|
173
|
-
static checkParameters(paramObject, isCopilot) {
|
|
174
|
-
const paramResult = {
|
|
175
|
-
requiredNum: 0,
|
|
176
|
-
optionalNum: 0,
|
|
177
|
-
isValid: true,
|
|
178
|
-
};
|
|
179
|
-
if (!paramObject) {
|
|
180
|
-
return paramResult;
|
|
181
|
-
}
|
|
182
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
183
|
-
const param = paramObject[i];
|
|
184
|
-
const schema = param.schema;
|
|
185
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
186
|
-
paramResult.isValid = false;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
190
|
-
if (isCopilot) {
|
|
191
|
-
if (isRequiredWithoutDefault) {
|
|
192
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
196
|
-
}
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
200
|
-
if (isRequiredWithoutDefault) {
|
|
201
|
-
paramResult.isValid = false;
|
|
202
|
-
}
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (schema.type !== "boolean" &&
|
|
206
|
-
schema.type !== "string" &&
|
|
207
|
-
schema.type !== "number" &&
|
|
208
|
-
schema.type !== "integer") {
|
|
209
|
-
if (isRequiredWithoutDefault) {
|
|
210
|
-
paramResult.isValid = false;
|
|
211
|
-
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (param.in === "query" || param.in === "path") {
|
|
215
|
-
if (isRequiredWithoutDefault) {
|
|
216
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return paramResult;
|
|
224
|
-
}
|
|
225
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
226
|
-
var _a;
|
|
227
|
-
const paramResult = {
|
|
228
|
-
requiredNum: 0,
|
|
229
|
-
optionalNum: 0,
|
|
230
|
-
isValid: true,
|
|
231
|
-
};
|
|
232
|
-
if (Object.keys(schema).length === 0) {
|
|
233
|
-
return paramResult;
|
|
234
|
-
}
|
|
235
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
236
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
237
|
-
paramResult.isValid = false;
|
|
238
|
-
return paramResult;
|
|
239
|
-
}
|
|
240
|
-
if (schema.type === "string" ||
|
|
241
|
-
schema.type === "integer" ||
|
|
242
|
-
schema.type === "boolean" ||
|
|
243
|
-
schema.type === "number") {
|
|
244
|
-
if (isRequiredWithoutDefault) {
|
|
245
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else if (schema.type === "object") {
|
|
252
|
-
const { properties } = schema;
|
|
253
|
-
for (const property in properties) {
|
|
254
|
-
let isRequired = false;
|
|
255
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
256
|
-
isRequired = true;
|
|
257
|
-
}
|
|
258
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
259
|
-
paramResult.requiredNum += result.requiredNum;
|
|
260
|
-
paramResult.optionalNum += result.optionalNum;
|
|
261
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
266
|
-
paramResult.isValid = false;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return paramResult;
|
|
270
|
-
}
|
|
271
189
|
static containMultipleMediaTypes(bodyObject) {
|
|
272
190
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
273
191
|
}
|
|
274
|
-
/**
|
|
275
|
-
* Checks if the given API is supported.
|
|
276
|
-
* @param {string} method - The HTTP method of the API.
|
|
277
|
-
* @param {string} path - The path of the API.
|
|
278
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
279
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
280
|
-
* @description The following APIs are supported:
|
|
281
|
-
* 1. only support Get/Post operation without auth property
|
|
282
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
283
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
284
|
-
* 4. request body + required parameters <= 1
|
|
285
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
286
|
-
* 6. only support request body with “application/json” content type
|
|
287
|
-
*/
|
|
288
|
-
static isSupportedApi(method, path, spec, options) {
|
|
289
|
-
var _a;
|
|
290
|
-
const pathObj = spec.paths[path];
|
|
291
|
-
method = method.toLocaleLowerCase();
|
|
292
|
-
if (pathObj) {
|
|
293
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
294
|
-
const securities = pathObj[method].security;
|
|
295
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
296
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
297
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
298
|
-
if (!isTeamsAi) {
|
|
299
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
300
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const operationObject = pathObj[method];
|
|
305
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
const paramObject = operationObject.parameters;
|
|
309
|
-
const requestBody = operationObject.requestBody;
|
|
310
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
311
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
315
|
-
if (Object.keys(responseJson).length === 0) {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
// Teams AI project doesn't care about request parameters/body
|
|
319
|
-
if (isTeamsAi) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
let requestBodyParamResult = {
|
|
323
|
-
requiredNum: 0,
|
|
324
|
-
optionalNum: 0,
|
|
325
|
-
isValid: true,
|
|
326
|
-
};
|
|
327
|
-
if (requestJsonBody) {
|
|
328
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
329
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
333
|
-
}
|
|
334
|
-
if (!requestBodyParamResult.isValid) {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
338
|
-
if (!paramResult.isValid) {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
// Copilot support arbitrary parameters
|
|
342
|
-
if (isCopilot) {
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
346
|
-
if (options.allowMultipleParameters &&
|
|
347
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
348
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
else if (requestBodyParamResult.requiredNum +
|
|
354
|
-
requestBodyParamResult.optionalNum +
|
|
355
|
-
paramResult.requiredNum +
|
|
356
|
-
paramResult.optionalNum ===
|
|
357
|
-
0) {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
return true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
368
|
-
if (authSchemeArray.length === 0) {
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
372
|
-
// Currently we don't support multiple auth in one operation
|
|
373
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
for (const auths of authSchemeArray) {
|
|
377
|
-
if (auths.length === 1) {
|
|
378
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
379
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
380
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
192
|
static isBearerTokenAuth(authScheme) {
|
|
389
193
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
390
194
|
}
|
|
@@ -392,18 +196,18 @@ class Utils {
|
|
|
392
196
|
return authScheme.type === "apiKey";
|
|
393
197
|
}
|
|
394
198
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return false;
|
|
199
|
+
return !!(authScheme.type === "oauth2" &&
|
|
200
|
+
authScheme.flows &&
|
|
201
|
+
authScheme.flows.authorizationCode);
|
|
399
202
|
}
|
|
400
203
|
static getAuthArray(securities, spec) {
|
|
401
204
|
var _a;
|
|
402
205
|
const result = [];
|
|
403
206
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
207
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
208
|
+
if (securitiesArr && securitySchemas) {
|
|
209
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
210
|
+
const security = securitiesArr[i];
|
|
407
211
|
const authArray = [];
|
|
408
212
|
for (const name in security) {
|
|
409
213
|
const auth = securitySchemas[name];
|
|
@@ -420,17 +224,39 @@ class Utils {
|
|
|
420
224
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
421
225
|
return result;
|
|
422
226
|
}
|
|
227
|
+
static getAuthInfo(spec) {
|
|
228
|
+
let authInfo = undefined;
|
|
229
|
+
for (const url in spec.paths) {
|
|
230
|
+
for (const method in spec.paths[url]) {
|
|
231
|
+
const operation = spec.paths[url][method];
|
|
232
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
233
|
+
if (authArray && authArray.length > 0) {
|
|
234
|
+
const currentAuth = authArray[0][0];
|
|
235
|
+
if (!authInfo) {
|
|
236
|
+
authInfo = authArray[0][0];
|
|
237
|
+
}
|
|
238
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
239
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return authInfo;
|
|
245
|
+
}
|
|
423
246
|
static updateFirstLetter(str) {
|
|
424
247
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
425
248
|
}
|
|
426
|
-
static getResponseJson(operationObject
|
|
249
|
+
static getResponseJson(operationObject) {
|
|
427
250
|
var _a, _b;
|
|
428
251
|
let json = {};
|
|
252
|
+
let multipleMediaType = false;
|
|
429
253
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
430
254
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
431
255
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
256
|
+
multipleMediaType = false;
|
|
432
257
|
json = responseObject.content["application/json"];
|
|
433
|
-
if (
|
|
258
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
259
|
+
multipleMediaType = true;
|
|
434
260
|
json = {};
|
|
435
261
|
}
|
|
436
262
|
else {
|
|
@@ -438,7 +264,7 @@ class Utils {
|
|
|
438
264
|
}
|
|
439
265
|
}
|
|
440
266
|
}
|
|
441
|
-
return json;
|
|
267
|
+
return { json, multipleMediaType };
|
|
442
268
|
}
|
|
443
269
|
static convertPathToCamelCase(path) {
|
|
444
270
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -458,10 +284,10 @@ class Utils {
|
|
|
458
284
|
return undefined;
|
|
459
285
|
}
|
|
460
286
|
}
|
|
461
|
-
static
|
|
287
|
+
static resolveEnv(str) {
|
|
462
288
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
463
|
-
let matches = placeHolderReg.exec(
|
|
464
|
-
let
|
|
289
|
+
let matches = placeHolderReg.exec(str);
|
|
290
|
+
let newStr = str;
|
|
465
291
|
while (matches != null) {
|
|
466
292
|
const envVar = matches[1];
|
|
467
293
|
const envVal = process.env[envVar];
|
|
@@ -469,17 +295,17 @@ class Utils {
|
|
|
469
295
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
470
296
|
}
|
|
471
297
|
else {
|
|
472
|
-
|
|
298
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
473
299
|
}
|
|
474
|
-
matches = placeHolderReg.exec(
|
|
300
|
+
matches = placeHolderReg.exec(str);
|
|
475
301
|
}
|
|
476
|
-
return
|
|
302
|
+
return newStr;
|
|
477
303
|
}
|
|
478
304
|
static checkServerUrl(servers) {
|
|
479
305
|
const errors = [];
|
|
480
306
|
let serverUrl;
|
|
481
307
|
try {
|
|
482
|
-
serverUrl = Utils.
|
|
308
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
483
309
|
}
|
|
484
310
|
catch (err) {
|
|
485
311
|
errors.push({
|
|
@@ -510,6 +336,7 @@ class Utils {
|
|
|
510
336
|
return errors;
|
|
511
337
|
}
|
|
512
338
|
static validateServer(spec, options) {
|
|
339
|
+
var _a;
|
|
513
340
|
const errors = [];
|
|
514
341
|
let hasTopLevelServers = false;
|
|
515
342
|
let hasPathLevelServers = false;
|
|
@@ -530,7 +357,7 @@ class Utils {
|
|
|
530
357
|
}
|
|
531
358
|
for (const method in methods) {
|
|
532
359
|
const operationObject = methods[method];
|
|
533
|
-
if (
|
|
360
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
534
361
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
535
362
|
hasOperationLevelServers = true;
|
|
536
363
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -657,13 +484,7 @@ class Utils {
|
|
|
657
484
|
}
|
|
658
485
|
}
|
|
659
486
|
const operationId = operationItem.operationId;
|
|
660
|
-
const parameters = [];
|
|
661
|
-
if (requiredParams.length !== 0) {
|
|
662
|
-
parameters.push(...requiredParams);
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
parameters.push(optionalParams[0]);
|
|
666
|
-
}
|
|
487
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
667
488
|
const command = {
|
|
668
489
|
context: ["compose"],
|
|
669
490
|
type: "query",
|
|
@@ -672,117 +493,534 @@ class Utils {
|
|
|
672
493
|
parameters: parameters,
|
|
673
494
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
674
495
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
496
|
+
return command;
|
|
497
|
+
}
|
|
498
|
+
static format(str, ...args) {
|
|
499
|
+
let index = 0;
|
|
500
|
+
return str.replace(/%s/g, () => {
|
|
501
|
+
const arg = args[index++];
|
|
502
|
+
return arg !== undefined ? arg : "";
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
506
|
+
if (!authName) {
|
|
507
|
+
return "";
|
|
508
|
+
}
|
|
509
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
510
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
511
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
682
512
|
}
|
|
683
|
-
return
|
|
513
|
+
return safeRegistrationIdEnvName;
|
|
684
514
|
}
|
|
685
|
-
static
|
|
686
|
-
const
|
|
515
|
+
static getServerObject(spec, method, path) {
|
|
516
|
+
const pathObj = spec.paths[path];
|
|
517
|
+
const operationObject = pathObj[method];
|
|
518
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
519
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
520
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
521
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
522
|
+
return serverUrl;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Copyright (c) Microsoft Corporation.
|
|
527
|
+
class Validator {
|
|
528
|
+
listAPIs() {
|
|
529
|
+
var _a;
|
|
530
|
+
if (this.apiMap) {
|
|
531
|
+
return this.apiMap;
|
|
532
|
+
}
|
|
533
|
+
const paths = this.spec.paths;
|
|
687
534
|
const result = {};
|
|
688
535
|
for (const path in paths) {
|
|
689
536
|
const methods = paths[path];
|
|
690
537
|
for (const method in methods) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
538
|
+
const operationObject = methods[method];
|
|
539
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
540
|
+
const validateResult = this.validateAPI(method, path);
|
|
541
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
542
|
+
operation: operationObject,
|
|
543
|
+
isValid: validateResult.isValid,
|
|
544
|
+
reason: validateResult.reason,
|
|
545
|
+
};
|
|
694
546
|
}
|
|
695
547
|
}
|
|
696
548
|
}
|
|
549
|
+
this.apiMap = result;
|
|
697
550
|
return result;
|
|
698
551
|
}
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
552
|
+
validateSpecVersion() {
|
|
553
|
+
const result = { errors: [], warnings: [] };
|
|
554
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
555
|
+
result.errors.push({
|
|
556
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
557
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
558
|
+
data: this.spec.openapi,
|
|
706
559
|
});
|
|
707
560
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
validateSpecServer() {
|
|
564
|
+
const result = { errors: [], warnings: [] };
|
|
565
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
566
|
+
result.errors.push(...serverErrors);
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
validateSpecNoSupportAPI() {
|
|
570
|
+
const result = { errors: [], warnings: [] };
|
|
571
|
+
const apiMap = this.listAPIs();
|
|
572
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
573
|
+
if (validAPIs.length === 0) {
|
|
574
|
+
const data = [];
|
|
575
|
+
for (const key in apiMap) {
|
|
576
|
+
const { reason } = apiMap[key];
|
|
577
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
578
|
+
data.push(apiInvalidReason);
|
|
579
|
+
}
|
|
580
|
+
result.errors.push({
|
|
725
581
|
type: ErrorType.NoSupportedApi,
|
|
726
582
|
content: ConstantString.NoSupportedApi,
|
|
583
|
+
data,
|
|
727
584
|
});
|
|
728
585
|
}
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
validateSpecOperationId() {
|
|
589
|
+
const result = { errors: [], warnings: [] };
|
|
590
|
+
const apiMap = this.listAPIs();
|
|
729
591
|
// OperationId missing
|
|
730
592
|
const apisMissingOperationId = [];
|
|
731
593
|
for (const key in apiMap) {
|
|
732
|
-
const
|
|
733
|
-
if (!
|
|
594
|
+
const { operation } = apiMap[key];
|
|
595
|
+
if (!operation.operationId) {
|
|
734
596
|
apisMissingOperationId.push(key);
|
|
735
597
|
}
|
|
736
598
|
}
|
|
737
599
|
if (apisMissingOperationId.length > 0) {
|
|
738
|
-
warnings.push({
|
|
600
|
+
result.warnings.push({
|
|
739
601
|
type: WarningType.OperationIdMissing,
|
|
740
602
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
741
603
|
data: apisMissingOperationId,
|
|
742
604
|
});
|
|
743
605
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
606
|
+
return result;
|
|
607
|
+
}
|
|
608
|
+
validateMethodAndPath(method, path) {
|
|
609
|
+
const result = { isValid: true, reason: [] };
|
|
610
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
611
|
+
result.isValid = false;
|
|
612
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
613
|
+
return result;
|
|
747
614
|
}
|
|
748
|
-
|
|
749
|
-
|
|
615
|
+
const pathObj = this.spec.paths[path];
|
|
616
|
+
if (!pathObj || !pathObj[method]) {
|
|
617
|
+
result.isValid = false;
|
|
618
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
619
|
+
return result;
|
|
750
620
|
}
|
|
751
|
-
return
|
|
752
|
-
status,
|
|
753
|
-
warnings,
|
|
754
|
-
errors,
|
|
755
|
-
};
|
|
621
|
+
return result;
|
|
756
622
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
623
|
+
validateResponse(method, path) {
|
|
624
|
+
const result = { isValid: true, reason: [] };
|
|
625
|
+
const operationObject = this.spec.paths[path][method];
|
|
626
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
627
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
628
|
+
// only support response body only contains “application/json” content type
|
|
629
|
+
if (multipleMediaType) {
|
|
630
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
631
|
+
}
|
|
632
|
+
else if (Object.keys(json).length === 0) {
|
|
633
|
+
// response body should not be empty
|
|
634
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
763
638
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
639
|
+
validateServer(method, path) {
|
|
640
|
+
const result = { isValid: true, reason: [] };
|
|
641
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
642
|
+
if (!serverObj) {
|
|
643
|
+
// should contain server URL
|
|
644
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
767
645
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
646
|
+
else {
|
|
647
|
+
// server url should be absolute url with https protocol
|
|
648
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
649
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
771
650
|
}
|
|
772
|
-
return
|
|
651
|
+
return result;
|
|
773
652
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
653
|
+
validateAuth(method, path) {
|
|
654
|
+
const pathObj = this.spec.paths[path];
|
|
655
|
+
const operationObject = pathObj[method];
|
|
656
|
+
const securities = operationObject.security;
|
|
657
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
658
|
+
if (authSchemeArray.length === 0) {
|
|
659
|
+
return { isValid: true, reason: [] };
|
|
660
|
+
}
|
|
661
|
+
if (this.options.allowAPIKeyAuth ||
|
|
662
|
+
this.options.allowOauth2 ||
|
|
663
|
+
this.options.allowBearerTokenAuth) {
|
|
664
|
+
// Currently we don't support multiple auth in one operation
|
|
665
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
666
|
+
return {
|
|
667
|
+
isValid: false,
|
|
668
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
for (const auths of authSchemeArray) {
|
|
672
|
+
if (auths.length === 1) {
|
|
673
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
674
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
675
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
676
|
+
return { isValid: true, reason: [] };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
682
|
+
}
|
|
683
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
684
|
+
var _a;
|
|
685
|
+
const paramResult = {
|
|
686
|
+
requiredNum: 0,
|
|
687
|
+
optionalNum: 0,
|
|
688
|
+
isValid: true,
|
|
689
|
+
reason: [],
|
|
690
|
+
};
|
|
691
|
+
if (Object.keys(schema).length === 0) {
|
|
692
|
+
return paramResult;
|
|
693
|
+
}
|
|
694
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
695
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
696
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
697
|
+
paramResult.isValid = false;
|
|
698
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
699
|
+
return paramResult;
|
|
700
|
+
}
|
|
701
|
+
if (schema.type === "string" ||
|
|
702
|
+
schema.type === "integer" ||
|
|
703
|
+
schema.type === "boolean" ||
|
|
704
|
+
schema.type === "number") {
|
|
705
|
+
if (isRequiredWithoutDefault) {
|
|
706
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
else if (schema.type === "object") {
|
|
713
|
+
const { properties } = schema;
|
|
714
|
+
for (const property in properties) {
|
|
715
|
+
let isRequired = false;
|
|
716
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
717
|
+
isRequired = true;
|
|
782
718
|
}
|
|
719
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
720
|
+
paramResult.requiredNum += result.requiredNum;
|
|
721
|
+
paramResult.optionalNum += result.optionalNum;
|
|
722
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
723
|
+
paramResult.reason.push(...result.reason);
|
|
783
724
|
}
|
|
784
725
|
}
|
|
785
|
-
|
|
726
|
+
else {
|
|
727
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
728
|
+
paramResult.isValid = false;
|
|
729
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return paramResult;
|
|
733
|
+
}
|
|
734
|
+
checkParamSchema(paramObject) {
|
|
735
|
+
const paramResult = {
|
|
736
|
+
requiredNum: 0,
|
|
737
|
+
optionalNum: 0,
|
|
738
|
+
isValid: true,
|
|
739
|
+
reason: [],
|
|
740
|
+
};
|
|
741
|
+
if (!paramObject) {
|
|
742
|
+
return paramResult;
|
|
743
|
+
}
|
|
744
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
745
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
746
|
+
const param = paramObject[i];
|
|
747
|
+
const schema = param.schema;
|
|
748
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
749
|
+
paramResult.isValid = false;
|
|
750
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
754
|
+
if (isCopilot) {
|
|
755
|
+
if (isRequiredWithoutDefault) {
|
|
756
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
764
|
+
if (isRequiredWithoutDefault) {
|
|
765
|
+
paramResult.isValid = false;
|
|
766
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
767
|
+
}
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (schema.type !== "boolean" &&
|
|
771
|
+
schema.type !== "string" &&
|
|
772
|
+
schema.type !== "number" &&
|
|
773
|
+
schema.type !== "integer") {
|
|
774
|
+
if (isRequiredWithoutDefault) {
|
|
775
|
+
paramResult.isValid = false;
|
|
776
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
777
|
+
}
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (param.in === "query" || param.in === "path") {
|
|
781
|
+
if (isRequiredWithoutDefault) {
|
|
782
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return paramResult;
|
|
790
|
+
}
|
|
791
|
+
hasNestedObjectInSchema(schema) {
|
|
792
|
+
if (schema.type === "object") {
|
|
793
|
+
for (const property in schema.properties) {
|
|
794
|
+
const nestedSchema = schema.properties[property];
|
|
795
|
+
if (nestedSchema.type === "object") {
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Copyright (c) Microsoft Corporation.
|
|
805
|
+
class CopilotValidator extends Validator {
|
|
806
|
+
constructor(spec, options) {
|
|
807
|
+
super();
|
|
808
|
+
this.projectType = ProjectType.Copilot;
|
|
809
|
+
this.options = options;
|
|
810
|
+
this.spec = spec;
|
|
811
|
+
}
|
|
812
|
+
validateSpec() {
|
|
813
|
+
const result = { errors: [], warnings: [] };
|
|
814
|
+
// validate spec version
|
|
815
|
+
let validationResult = this.validateSpecVersion();
|
|
816
|
+
result.errors.push(...validationResult.errors);
|
|
817
|
+
// validate spec server
|
|
818
|
+
validationResult = this.validateSpecServer();
|
|
819
|
+
result.errors.push(...validationResult.errors);
|
|
820
|
+
// validate no supported API
|
|
821
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
822
|
+
result.errors.push(...validationResult.errors);
|
|
823
|
+
// validate operationId missing
|
|
824
|
+
validationResult = this.validateSpecOperationId();
|
|
825
|
+
result.warnings.push(...validationResult.warnings);
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
validateAPI(method, path) {
|
|
829
|
+
const result = { isValid: true, reason: [] };
|
|
830
|
+
method = method.toLocaleLowerCase();
|
|
831
|
+
// validate method and path
|
|
832
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
833
|
+
if (!methodAndPathResult.isValid) {
|
|
834
|
+
return methodAndPathResult;
|
|
835
|
+
}
|
|
836
|
+
const operationObject = this.spec.paths[path][method];
|
|
837
|
+
// validate auth
|
|
838
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
839
|
+
result.reason.push(...authCheckResult.reason);
|
|
840
|
+
// validate operationId
|
|
841
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
842
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
843
|
+
}
|
|
844
|
+
// validate server
|
|
845
|
+
const validateServerResult = this.validateServer(method, path);
|
|
846
|
+
result.reason.push(...validateServerResult.reason);
|
|
847
|
+
// validate response
|
|
848
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
849
|
+
result.reason.push(...validateResponseResult.reason);
|
|
850
|
+
// validate requestBody
|
|
851
|
+
const requestBody = operationObject.requestBody;
|
|
852
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
853
|
+
if (requestJsonBody) {
|
|
854
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
855
|
+
if (requestBodySchema.type !== "object") {
|
|
856
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
857
|
+
}
|
|
858
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
859
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
860
|
+
}
|
|
861
|
+
// validate parameters
|
|
862
|
+
const paramObject = operationObject.parameters;
|
|
863
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
864
|
+
result.reason.push(...paramResult.reason);
|
|
865
|
+
if (result.reason.length > 0) {
|
|
866
|
+
result.isValid = false;
|
|
867
|
+
}
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Copyright (c) Microsoft Corporation.
|
|
873
|
+
class SMEValidator extends Validator {
|
|
874
|
+
constructor(spec, options) {
|
|
875
|
+
super();
|
|
876
|
+
this.projectType = ProjectType.SME;
|
|
877
|
+
this.options = options;
|
|
878
|
+
this.spec = spec;
|
|
879
|
+
}
|
|
880
|
+
validateSpec() {
|
|
881
|
+
const result = { errors: [], warnings: [] };
|
|
882
|
+
// validate spec version
|
|
883
|
+
let validationResult = this.validateSpecVersion();
|
|
884
|
+
result.errors.push(...validationResult.errors);
|
|
885
|
+
// validate spec server
|
|
886
|
+
validationResult = this.validateSpecServer();
|
|
887
|
+
result.errors.push(...validationResult.errors);
|
|
888
|
+
// validate no supported API
|
|
889
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
890
|
+
result.errors.push(...validationResult.errors);
|
|
891
|
+
// validate operationId missing
|
|
892
|
+
if (this.options.allowMissingId) {
|
|
893
|
+
validationResult = this.validateSpecOperationId();
|
|
894
|
+
result.warnings.push(...validationResult.warnings);
|
|
895
|
+
}
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
validateAPI(method, path) {
|
|
899
|
+
const result = { isValid: true, reason: [] };
|
|
900
|
+
method = method.toLocaleLowerCase();
|
|
901
|
+
// validate method and path
|
|
902
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
903
|
+
if (!methodAndPathResult.isValid) {
|
|
904
|
+
return methodAndPathResult;
|
|
905
|
+
}
|
|
906
|
+
const operationObject = this.spec.paths[path][method];
|
|
907
|
+
// validate auth
|
|
908
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
909
|
+
result.reason.push(...authCheckResult.reason);
|
|
910
|
+
// validate operationId
|
|
911
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
912
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
913
|
+
}
|
|
914
|
+
// validate server
|
|
915
|
+
const validateServerResult = this.validateServer(method, path);
|
|
916
|
+
result.reason.push(...validateServerResult.reason);
|
|
917
|
+
// validate response
|
|
918
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
919
|
+
result.reason.push(...validateResponseResult.reason);
|
|
920
|
+
let postBodyResult = {
|
|
921
|
+
requiredNum: 0,
|
|
922
|
+
optionalNum: 0,
|
|
923
|
+
isValid: true,
|
|
924
|
+
reason: [],
|
|
925
|
+
};
|
|
926
|
+
// validate requestBody
|
|
927
|
+
const requestBody = operationObject.requestBody;
|
|
928
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
929
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
930
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
931
|
+
}
|
|
932
|
+
if (requestJsonBody) {
|
|
933
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
934
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
935
|
+
result.reason.push(...postBodyResult.reason);
|
|
936
|
+
}
|
|
937
|
+
// validate parameters
|
|
938
|
+
const paramObject = operationObject.parameters;
|
|
939
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
940
|
+
result.reason.push(...paramResult.reason);
|
|
941
|
+
// validate total parameters count
|
|
942
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
943
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
944
|
+
result.reason.push(...paramCountResult.reason);
|
|
945
|
+
}
|
|
946
|
+
if (result.reason.length > 0) {
|
|
947
|
+
result.isValid = false;
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
952
|
+
const result = { isValid: true, reason: [] };
|
|
953
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
954
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
955
|
+
if (totalRequiredParams > 1) {
|
|
956
|
+
if (!this.options.allowMultipleParameters ||
|
|
957
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
958
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
else if (totalParams === 0) {
|
|
962
|
+
result.reason.push(ErrorType.NoParameter);
|
|
963
|
+
}
|
|
964
|
+
return result;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
968
|
+
|
|
969
|
+
// Copyright (c) Microsoft Corporation.
|
|
970
|
+
class TeamsAIValidator extends Validator {
|
|
971
|
+
constructor(spec, options) {
|
|
972
|
+
super();
|
|
973
|
+
this.projectType = ProjectType.TeamsAi;
|
|
974
|
+
this.options = options;
|
|
975
|
+
this.spec = spec;
|
|
976
|
+
}
|
|
977
|
+
validateSpec() {
|
|
978
|
+
const result = { errors: [], warnings: [] };
|
|
979
|
+
// validate spec server
|
|
980
|
+
let validationResult = this.validateSpecServer();
|
|
981
|
+
result.errors.push(...validationResult.errors);
|
|
982
|
+
// validate no supported API
|
|
983
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
984
|
+
result.errors.push(...validationResult.errors);
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
validateAPI(method, path) {
|
|
988
|
+
const result = { isValid: true, reason: [] };
|
|
989
|
+
method = method.toLocaleLowerCase();
|
|
990
|
+
// validate method and path
|
|
991
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
992
|
+
if (!methodAndPathResult.isValid) {
|
|
993
|
+
return methodAndPathResult;
|
|
994
|
+
}
|
|
995
|
+
const operationObject = this.spec.paths[path][method];
|
|
996
|
+
// validate operationId
|
|
997
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
998
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
999
|
+
}
|
|
1000
|
+
// validate server
|
|
1001
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1002
|
+
result.reason.push(...validateServerResult.reason);
|
|
1003
|
+
if (result.reason.length > 0) {
|
|
1004
|
+
result.isValid = false;
|
|
1005
|
+
}
|
|
1006
|
+
return result;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
class ValidatorFactory {
|
|
1011
|
+
static create(spec, options) {
|
|
1012
|
+
var _a;
|
|
1013
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1014
|
+
switch (type) {
|
|
1015
|
+
case ProjectType.SME:
|
|
1016
|
+
return new SMEValidator(spec, options);
|
|
1017
|
+
case ProjectType.Copilot:
|
|
1018
|
+
return new CopilotValidator(spec, options);
|
|
1019
|
+
case ProjectType.TeamsAi:
|
|
1020
|
+
return new TeamsAIValidator(spec, options);
|
|
1021
|
+
default:
|
|
1022
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1023
|
+
}
|
|
786
1024
|
}
|
|
787
1025
|
}
|
|
788
1026
|
|
|
@@ -805,7 +1043,11 @@ class SpecParser {
|
|
|
805
1043
|
allowBearerTokenAuth: false,
|
|
806
1044
|
allowOauth2: false,
|
|
807
1045
|
allowMethods: ["get", "post"],
|
|
1046
|
+
allowConversationStarters: false,
|
|
1047
|
+
allowResponseSemantics: false,
|
|
1048
|
+
allowConfirmation: false,
|
|
808
1049
|
projectType: ProjectType.SME,
|
|
1050
|
+
isGptPlugin: false,
|
|
809
1051
|
};
|
|
810
1052
|
this.pathOrSpec = pathOrDoc;
|
|
811
1053
|
this.parser = new SwaggerParser();
|
|
@@ -820,11 +1062,7 @@ class SpecParser {
|
|
|
820
1062
|
try {
|
|
821
1063
|
try {
|
|
822
1064
|
await this.loadSpec();
|
|
823
|
-
await this.parser.validate(this.spec
|
|
824
|
-
validate: {
|
|
825
|
-
schema: false,
|
|
826
|
-
},
|
|
827
|
-
});
|
|
1065
|
+
await this.parser.validate(this.spec);
|
|
828
1066
|
}
|
|
829
1067
|
catch (e) {
|
|
830
1068
|
return {
|
|
@@ -833,16 +1071,46 @@ class SpecParser {
|
|
|
833
1071
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
834
1072
|
};
|
|
835
1073
|
}
|
|
1074
|
+
const errors = [];
|
|
1075
|
+
const warnings = [];
|
|
836
1076
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
837
1077
|
return {
|
|
838
1078
|
status: ValidationStatus.Error,
|
|
839
1079
|
warnings: [],
|
|
840
1080
|
errors: [
|
|
841
|
-
{
|
|
1081
|
+
{
|
|
1082
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1083
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1084
|
+
},
|
|
842
1085
|
],
|
|
843
1086
|
};
|
|
844
1087
|
}
|
|
845
|
-
|
|
1088
|
+
// Remote reference not supported
|
|
1089
|
+
const refPaths = this.parser.$refs.paths();
|
|
1090
|
+
// refPaths [0] is the current spec file path
|
|
1091
|
+
if (refPaths.length > 1) {
|
|
1092
|
+
errors.push({
|
|
1093
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1094
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1095
|
+
data: refPaths,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
const validator = this.getValidator(this.spec);
|
|
1099
|
+
const validationResult = validator.validateSpec();
|
|
1100
|
+
warnings.push(...validationResult.warnings);
|
|
1101
|
+
errors.push(...validationResult.errors);
|
|
1102
|
+
let status = ValidationStatus.Valid;
|
|
1103
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1104
|
+
status = ValidationStatus.Warning;
|
|
1105
|
+
}
|
|
1106
|
+
else if (errors.length > 0) {
|
|
1107
|
+
status = ValidationStatus.Error;
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
status: status,
|
|
1111
|
+
warnings: warnings,
|
|
1112
|
+
errors: errors,
|
|
1113
|
+
};
|
|
846
1114
|
}
|
|
847
1115
|
catch (err) {
|
|
848
1116
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -851,17 +1119,20 @@ class SpecParser {
|
|
|
851
1119
|
async listSupportedAPIInfo() {
|
|
852
1120
|
try {
|
|
853
1121
|
await this.loadSpec();
|
|
854
|
-
const apiMap = this.
|
|
1122
|
+
const apiMap = this.getAPIs(this.spec);
|
|
855
1123
|
const apiInfos = [];
|
|
856
1124
|
for (const key in apiMap) {
|
|
857
|
-
const
|
|
1125
|
+
const { operation, isValid } = apiMap[key];
|
|
1126
|
+
if (!isValid) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
858
1129
|
const [method, path] = key.split(" ");
|
|
859
|
-
const operationId =
|
|
1130
|
+
const operationId = operation.operationId;
|
|
860
1131
|
// In Browser environment, this api is by default not support api without operationId
|
|
861
1132
|
if (!operationId) {
|
|
862
1133
|
continue;
|
|
863
1134
|
}
|
|
864
|
-
const
|
|
1135
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
865
1136
|
const apiInfo = {
|
|
866
1137
|
method: method,
|
|
867
1138
|
path: path,
|
|
@@ -870,9 +1141,6 @@ class SpecParser {
|
|
|
870
1141
|
parameters: command.parameters,
|
|
871
1142
|
description: command.description,
|
|
872
1143
|
};
|
|
873
|
-
if (warning) {
|
|
874
|
-
apiInfo.warning = warning;
|
|
875
|
-
}
|
|
876
1144
|
apiInfos.push(apiInfo);
|
|
877
1145
|
}
|
|
878
1146
|
return apiInfos;
|
|
@@ -931,13 +1199,18 @@ class SpecParser {
|
|
|
931
1199
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
932
1200
|
}
|
|
933
1201
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1202
|
+
getAPIs(spec) {
|
|
1203
|
+
const validator = this.getValidator(spec);
|
|
1204
|
+
const apiMap = validator.listAPIs();
|
|
1205
|
+
return apiMap;
|
|
1206
|
+
}
|
|
1207
|
+
getValidator(spec) {
|
|
1208
|
+
if (this.validator) {
|
|
1209
|
+
return this.validator;
|
|
937
1210
|
}
|
|
938
|
-
const
|
|
939
|
-
this.
|
|
940
|
-
return
|
|
1211
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1212
|
+
this.validator = validator;
|
|
1213
|
+
return validator;
|
|
941
1214
|
}
|
|
942
1215
|
}
|
|
943
1216
|
|
|
@@ -945,7 +1218,7 @@ class SpecParser {
|
|
|
945
1218
|
class AdaptiveCardGenerator {
|
|
946
1219
|
static generateAdaptiveCard(operationItem) {
|
|
947
1220
|
try {
|
|
948
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1221
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
949
1222
|
let cardBody = [];
|
|
950
1223
|
let schema = json.schema;
|
|
951
1224
|
let jsonPath = "$";
|