@microsoft/m365-spec-parser 0.1.1-alpha.4cb5c08a8.0 → 0.1.1-alpha.4e708f092.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 +539 -360
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +924 -641
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +539 -360
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +924 -639
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +27 -0
- package/dist/src/manifestUpdater.d.ts +1 -1
- package/dist/src/specParser.browser.d.ts +3 -3
- package/dist/src/specParser.d.ts +2 -1
- package/dist/src/utils.d.ts +4 -25
- package/package.json +3 -3
package/dist/index.esm5.js
CHANGED
|
@@ -137,6 +137,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
137
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
138
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
139
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
140
141
|
ConstantString.ContainerType = "Container";
|
|
141
142
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
142
143
|
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
@@ -200,7 +201,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
200
201
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
201
202
|
ConstantString.CommandTitleMaxLens = 32;
|
|
202
203
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
203
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
204
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
205
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
204
206
|
|
|
205
207
|
// Copyright (c) Microsoft Corporation.
|
|
206
208
|
class Utils {
|
|
@@ -215,249 +217,9 @@ class Utils {
|
|
|
215
217
|
}
|
|
216
218
|
return false;
|
|
217
219
|
}
|
|
218
|
-
static checkParameters(paramObject, isCopilot) {
|
|
219
|
-
const paramResult = {
|
|
220
|
-
requiredNum: 0,
|
|
221
|
-
optionalNum: 0,
|
|
222
|
-
isValid: true,
|
|
223
|
-
reason: [],
|
|
224
|
-
};
|
|
225
|
-
if (!paramObject) {
|
|
226
|
-
return paramResult;
|
|
227
|
-
}
|
|
228
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
229
|
-
const param = paramObject[i];
|
|
230
|
-
const schema = param.schema;
|
|
231
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
232
|
-
paramResult.isValid = false;
|
|
233
|
-
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
237
|
-
if (isCopilot) {
|
|
238
|
-
if (isRequiredWithoutDefault) {
|
|
239
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
243
|
-
}
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
247
|
-
if (isRequiredWithoutDefault) {
|
|
248
|
-
paramResult.isValid = false;
|
|
249
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
250
|
-
}
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
if (schema.type !== "boolean" &&
|
|
254
|
-
schema.type !== "string" &&
|
|
255
|
-
schema.type !== "number" &&
|
|
256
|
-
schema.type !== "integer") {
|
|
257
|
-
if (isRequiredWithoutDefault) {
|
|
258
|
-
paramResult.isValid = false;
|
|
259
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
260
|
-
}
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (param.in === "query" || param.in === "path") {
|
|
264
|
-
if (isRequiredWithoutDefault) {
|
|
265
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return paramResult;
|
|
273
|
-
}
|
|
274
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
275
|
-
var _a;
|
|
276
|
-
const paramResult = {
|
|
277
|
-
requiredNum: 0,
|
|
278
|
-
optionalNum: 0,
|
|
279
|
-
isValid: true,
|
|
280
|
-
reason: [],
|
|
281
|
-
};
|
|
282
|
-
if (Object.keys(schema).length === 0) {
|
|
283
|
-
return paramResult;
|
|
284
|
-
}
|
|
285
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
286
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
287
|
-
paramResult.isValid = false;
|
|
288
|
-
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
289
|
-
return paramResult;
|
|
290
|
-
}
|
|
291
|
-
if (schema.type === "string" ||
|
|
292
|
-
schema.type === "integer" ||
|
|
293
|
-
schema.type === "boolean" ||
|
|
294
|
-
schema.type === "number") {
|
|
295
|
-
if (isRequiredWithoutDefault) {
|
|
296
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
else if (schema.type === "object") {
|
|
303
|
-
const { properties } = schema;
|
|
304
|
-
for (const property in properties) {
|
|
305
|
-
let isRequired = false;
|
|
306
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
307
|
-
isRequired = true;
|
|
308
|
-
}
|
|
309
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
310
|
-
paramResult.requiredNum += result.requiredNum;
|
|
311
|
-
paramResult.optionalNum += result.optionalNum;
|
|
312
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
313
|
-
paramResult.reason.push(...result.reason);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
318
|
-
paramResult.isValid = false;
|
|
319
|
-
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return paramResult;
|
|
323
|
-
}
|
|
324
220
|
static containMultipleMediaTypes(bodyObject) {
|
|
325
221
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
326
222
|
}
|
|
327
|
-
/**
|
|
328
|
-
* Checks if the given API is supported.
|
|
329
|
-
* @param {string} method - The HTTP method of the API.
|
|
330
|
-
* @param {string} path - The path of the API.
|
|
331
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
332
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
333
|
-
* @description The following APIs are supported:
|
|
334
|
-
* 1. only support Get/Post operation without auth property
|
|
335
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
336
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
337
|
-
* 4. request body + required parameters <= 1
|
|
338
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
339
|
-
* 6. only support request body with “application/json” content type
|
|
340
|
-
*/
|
|
341
|
-
static isSupportedApi(method, path, spec, options) {
|
|
342
|
-
var _a;
|
|
343
|
-
const result = { isValid: true, reason: [] };
|
|
344
|
-
method = method.toLocaleLowerCase();
|
|
345
|
-
if (options.allowMethods && !options.allowMethods.includes(method)) {
|
|
346
|
-
result.isValid = false;
|
|
347
|
-
result.reason.push(ErrorType.MethodNotAllowed);
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
const pathObj = spec.paths[path];
|
|
351
|
-
if (!pathObj || !pathObj[method]) {
|
|
352
|
-
result.isValid = false;
|
|
353
|
-
result.reason.push(ErrorType.UrlPathNotExist);
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
const securities = pathObj[method].security;
|
|
357
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
358
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
359
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
360
|
-
if (!isTeamsAi) {
|
|
361
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
362
|
-
const authCheckResult = Utils.isSupportedAuth(authArray, options);
|
|
363
|
-
if (!authCheckResult.isValid) {
|
|
364
|
-
result.reason.push(...authCheckResult.reason);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
const operationObject = pathObj[method];
|
|
368
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
369
|
-
result.reason.push(ErrorType.MissingOperationId);
|
|
370
|
-
}
|
|
371
|
-
const rootServer = spec.servers && spec.servers[0];
|
|
372
|
-
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
373
|
-
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
374
|
-
const serverUrl = operationServer || methodServer || rootServer;
|
|
375
|
-
if (!serverUrl) {
|
|
376
|
-
result.reason.push(ErrorType.NoServerInformation);
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
380
|
-
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
381
|
-
}
|
|
382
|
-
const paramObject = operationObject.parameters;
|
|
383
|
-
const requestBody = operationObject.requestBody;
|
|
384
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
385
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
386
|
-
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
387
|
-
}
|
|
388
|
-
const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
389
|
-
if (multipleMediaType && !isTeamsAi) {
|
|
390
|
-
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
391
|
-
}
|
|
392
|
-
else if (Object.keys(json).length === 0) {
|
|
393
|
-
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
394
|
-
}
|
|
395
|
-
// Teams AI project doesn't care about request parameters/body
|
|
396
|
-
if (!isTeamsAi) {
|
|
397
|
-
let requestBodyParamResult = {
|
|
398
|
-
requiredNum: 0,
|
|
399
|
-
optionalNum: 0,
|
|
400
|
-
isValid: true,
|
|
401
|
-
reason: [],
|
|
402
|
-
};
|
|
403
|
-
if (requestJsonBody) {
|
|
404
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
405
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
406
|
-
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
407
|
-
}
|
|
408
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
409
|
-
if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
|
|
410
|
-
result.reason.push(...requestBodyParamResult.reason);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
414
|
-
if (!paramResult.isValid && paramResult.reason) {
|
|
415
|
-
result.reason.push(...paramResult.reason);
|
|
416
|
-
}
|
|
417
|
-
// Copilot support arbitrary parameters
|
|
418
|
-
if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
|
|
419
|
-
const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
|
|
420
|
-
const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
|
|
421
|
-
if (totalRequiredParams > 1) {
|
|
422
|
-
if (!options.allowMultipleParameters ||
|
|
423
|
-
totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
|
|
424
|
-
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
else if (totalParams === 0) {
|
|
428
|
-
result.reason.push(ErrorType.NoParameter);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (result.reason.length > 0) {
|
|
433
|
-
result.isValid = false;
|
|
434
|
-
}
|
|
435
|
-
return result;
|
|
436
|
-
}
|
|
437
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
438
|
-
if (authSchemeArray.length === 0) {
|
|
439
|
-
return { isValid: true, reason: [] };
|
|
440
|
-
}
|
|
441
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
442
|
-
// Currently we don't support multiple auth in one operation
|
|
443
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
444
|
-
return {
|
|
445
|
-
isValid: false,
|
|
446
|
-
reason: [ErrorType.MultipleAuthNotSupported],
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
for (const auths of authSchemeArray) {
|
|
450
|
-
if (auths.length === 1) {
|
|
451
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
452
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
453
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
454
|
-
return { isValid: true, reason: [] };
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
460
|
-
}
|
|
461
223
|
static isBearerTokenAuth(authScheme) {
|
|
462
224
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
463
225
|
}
|
|
@@ -465,10 +227,9 @@ class Utils {
|
|
|
465
227
|
return authScheme.type === "apiKey";
|
|
466
228
|
}
|
|
467
229
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return false;
|
|
230
|
+
return !!(authScheme.type === "oauth2" &&
|
|
231
|
+
authScheme.flows &&
|
|
232
|
+
authScheme.flows.authorizationCode);
|
|
472
233
|
}
|
|
473
234
|
static getAuthArray(securities, spec) {
|
|
474
235
|
var _a;
|
|
@@ -496,7 +257,7 @@ class Utils {
|
|
|
496
257
|
static updateFirstLetter(str) {
|
|
497
258
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
498
259
|
}
|
|
499
|
-
static getResponseJson(operationObject
|
|
260
|
+
static getResponseJson(operationObject) {
|
|
500
261
|
var _a, _b;
|
|
501
262
|
let json = {};
|
|
502
263
|
let multipleMediaType = false;
|
|
@@ -507,9 +268,6 @@ class Utils {
|
|
|
507
268
|
json = responseObject.content["application/json"];
|
|
508
269
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
509
270
|
multipleMediaType = true;
|
|
510
|
-
if (isTeamsAiProject) {
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
271
|
json = {};
|
|
514
272
|
}
|
|
515
273
|
else {
|
|
@@ -737,13 +495,7 @@ class Utils {
|
|
|
737
495
|
}
|
|
738
496
|
}
|
|
739
497
|
const operationId = operationItem.operationId;
|
|
740
|
-
const parameters = [];
|
|
741
|
-
if (requiredParams.length !== 0) {
|
|
742
|
-
parameters.push(...requiredParams);
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
parameters.push(optionalParams[0]);
|
|
746
|
-
}
|
|
498
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
747
499
|
const command = {
|
|
748
500
|
context: ["compose"],
|
|
749
501
|
type: "query",
|
|
@@ -752,26 +504,51 @@ class Utils {
|
|
|
752
504
|
parameters: parameters,
|
|
753
505
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
754
506
|
};
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
507
|
+
return command;
|
|
508
|
+
}
|
|
509
|
+
static format(str, ...args) {
|
|
510
|
+
let index = 0;
|
|
511
|
+
return str.replace(/%s/g, () => {
|
|
512
|
+
const arg = args[index++];
|
|
513
|
+
return arg !== undefined ? arg : "";
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
517
|
+
if (!authName) {
|
|
518
|
+
return "";
|
|
519
|
+
}
|
|
520
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
521
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
522
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
762
523
|
}
|
|
763
|
-
return
|
|
524
|
+
return safeRegistrationIdEnvName;
|
|
764
525
|
}
|
|
765
|
-
static
|
|
526
|
+
static getServerObject(spec, method, path) {
|
|
527
|
+
const pathObj = spec.paths[path];
|
|
528
|
+
const operationObject = pathObj[method];
|
|
529
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
530
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
531
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
532
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
533
|
+
return serverUrl;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Copyright (c) Microsoft Corporation.
|
|
538
|
+
class Validator {
|
|
539
|
+
listAPIs() {
|
|
766
540
|
var _a;
|
|
767
|
-
|
|
541
|
+
if (this.apiMap) {
|
|
542
|
+
return this.apiMap;
|
|
543
|
+
}
|
|
544
|
+
const paths = this.spec.paths;
|
|
768
545
|
const result = {};
|
|
769
546
|
for (const path in paths) {
|
|
770
547
|
const methods = paths[path];
|
|
771
548
|
for (const method in methods) {
|
|
772
549
|
const operationObject = methods[method];
|
|
773
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
774
|
-
const validateResult =
|
|
550
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
551
|
+
const validateResult = this.validateAPI(method, path);
|
|
775
552
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
776
553
|
operation: operationObject,
|
|
777
554
|
isValid: validateResult.isValid,
|
|
@@ -780,38 +557,48 @@ class Utils {
|
|
|
780
557
|
}
|
|
781
558
|
}
|
|
782
559
|
}
|
|
560
|
+
this.apiMap = result;
|
|
783
561
|
return result;
|
|
784
562
|
}
|
|
785
|
-
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
563
|
+
validateSpecVersion() {
|
|
564
|
+
const result = { errors: [], warnings: [] };
|
|
565
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
566
|
+
result.errors.push({
|
|
567
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
568
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
569
|
+
data: this.spec.openapi,
|
|
793
570
|
});
|
|
794
571
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
// No supported API
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
validateSpecServer() {
|
|
575
|
+
const result = { errors: [], warnings: [] };
|
|
576
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
577
|
+
result.errors.push(...serverErrors);
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
validateSpecNoSupportAPI() {
|
|
581
|
+
const result = { errors: [], warnings: [] };
|
|
582
|
+
const apiMap = this.listAPIs();
|
|
808
583
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
809
584
|
if (validAPIs.length === 0) {
|
|
810
|
-
|
|
585
|
+
const data = [];
|
|
586
|
+
for (const key in apiMap) {
|
|
587
|
+
const { reason } = apiMap[key];
|
|
588
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
589
|
+
data.push(apiInvalidReason);
|
|
590
|
+
}
|
|
591
|
+
result.errors.push({
|
|
811
592
|
type: ErrorType.NoSupportedApi,
|
|
812
593
|
content: ConstantString.NoSupportedApi,
|
|
594
|
+
data,
|
|
813
595
|
});
|
|
814
596
|
}
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
validateSpecOperationId() {
|
|
600
|
+
const result = { errors: [], warnings: [] };
|
|
601
|
+
const apiMap = this.listAPIs();
|
|
815
602
|
// OperationId missing
|
|
816
603
|
const apisMissingOperationId = [];
|
|
817
604
|
for (const key in apiMap) {
|
|
@@ -821,54 +608,431 @@ class Utils {
|
|
|
821
608
|
}
|
|
822
609
|
}
|
|
823
610
|
if (apisMissingOperationId.length > 0) {
|
|
824
|
-
warnings.push({
|
|
611
|
+
result.warnings.push({
|
|
825
612
|
type: WarningType.OperationIdMissing,
|
|
826
613
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
827
614
|
data: apisMissingOperationId,
|
|
828
615
|
});
|
|
829
616
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
validateMethodAndPath(method, path) {
|
|
620
|
+
const result = { isValid: true, reason: [] };
|
|
621
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
622
|
+
result.isValid = false;
|
|
623
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
624
|
+
return result;
|
|
833
625
|
}
|
|
834
|
-
|
|
835
|
-
|
|
626
|
+
const pathObj = this.spec.paths[path];
|
|
627
|
+
if (!pathObj || !pathObj[method]) {
|
|
628
|
+
result.isValid = false;
|
|
629
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
630
|
+
return result;
|
|
836
631
|
}
|
|
837
|
-
return
|
|
838
|
-
status,
|
|
839
|
-
warnings,
|
|
840
|
-
errors,
|
|
841
|
-
};
|
|
632
|
+
return result;
|
|
842
633
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
634
|
+
validateResponse(method, path) {
|
|
635
|
+
const result = { isValid: true, reason: [] };
|
|
636
|
+
const operationObject = this.spec.paths[path][method];
|
|
637
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
638
|
+
// only support response body only contains “application/json” content type
|
|
639
|
+
if (multipleMediaType) {
|
|
640
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
641
|
+
}
|
|
642
|
+
else if (Object.keys(json).length === 0) {
|
|
643
|
+
// response body should not be empty
|
|
644
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
645
|
+
}
|
|
646
|
+
return result;
|
|
849
647
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
648
|
+
validateServer(method, path) {
|
|
649
|
+
const result = { isValid: true, reason: [] };
|
|
650
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
651
|
+
if (!serverObj) {
|
|
652
|
+
// should contain server URL
|
|
653
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
853
654
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
655
|
+
else {
|
|
656
|
+
// server url should be absolute url with https protocol
|
|
657
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
658
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
857
659
|
}
|
|
858
|
-
return
|
|
660
|
+
return result;
|
|
859
661
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
662
|
+
validateAuth(method, path) {
|
|
663
|
+
const pathObj = this.spec.paths[path];
|
|
664
|
+
const operationObject = pathObj[method];
|
|
665
|
+
const securities = operationObject.security;
|
|
666
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
667
|
+
if (authSchemeArray.length === 0) {
|
|
668
|
+
return { isValid: true, reason: [] };
|
|
669
|
+
}
|
|
670
|
+
if (this.options.allowAPIKeyAuth ||
|
|
671
|
+
this.options.allowOauth2 ||
|
|
672
|
+
this.options.allowBearerTokenAuth) {
|
|
673
|
+
// Currently we don't support multiple auth in one operation
|
|
674
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
675
|
+
return {
|
|
676
|
+
isValid: false,
|
|
677
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
for (const auths of authSchemeArray) {
|
|
681
|
+
if (auths.length === 1) {
|
|
682
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
683
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
684
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
685
|
+
return { isValid: true, reason: [] };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
691
|
+
}
|
|
692
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
693
|
+
var _a;
|
|
694
|
+
const paramResult = {
|
|
695
|
+
requiredNum: 0,
|
|
696
|
+
optionalNum: 0,
|
|
697
|
+
isValid: true,
|
|
698
|
+
reason: [],
|
|
699
|
+
};
|
|
700
|
+
if (Object.keys(schema).length === 0) {
|
|
701
|
+
return paramResult;
|
|
702
|
+
}
|
|
703
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
704
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
705
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
706
|
+
paramResult.isValid = false;
|
|
707
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
708
|
+
return paramResult;
|
|
709
|
+
}
|
|
710
|
+
if (schema.type === "string" ||
|
|
711
|
+
schema.type === "integer" ||
|
|
712
|
+
schema.type === "boolean" ||
|
|
713
|
+
schema.type === "number") {
|
|
714
|
+
if (isRequiredWithoutDefault) {
|
|
715
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else if (schema.type === "object") {
|
|
722
|
+
const { properties } = schema;
|
|
723
|
+
for (const property in properties) {
|
|
724
|
+
let isRequired = false;
|
|
725
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
726
|
+
isRequired = true;
|
|
727
|
+
}
|
|
728
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
729
|
+
paramResult.requiredNum += result.requiredNum;
|
|
730
|
+
paramResult.optionalNum += result.optionalNum;
|
|
731
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
732
|
+
paramResult.reason.push(...result.reason);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
737
|
+
paramResult.isValid = false;
|
|
738
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return paramResult;
|
|
742
|
+
}
|
|
743
|
+
checkParamSchema(paramObject) {
|
|
744
|
+
const paramResult = {
|
|
745
|
+
requiredNum: 0,
|
|
746
|
+
optionalNum: 0,
|
|
747
|
+
isValid: true,
|
|
748
|
+
reason: [],
|
|
749
|
+
};
|
|
750
|
+
if (!paramObject) {
|
|
751
|
+
return paramResult;
|
|
752
|
+
}
|
|
753
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
754
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
755
|
+
const param = paramObject[i];
|
|
756
|
+
const schema = param.schema;
|
|
757
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
763
|
+
if (isCopilot) {
|
|
764
|
+
if (isRequiredWithoutDefault) {
|
|
765
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
769
|
+
}
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
773
|
+
if (isRequiredWithoutDefault) {
|
|
774
|
+
paramResult.isValid = false;
|
|
775
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
776
|
+
}
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (schema.type !== "boolean" &&
|
|
780
|
+
schema.type !== "string" &&
|
|
781
|
+
schema.type !== "number" &&
|
|
782
|
+
schema.type !== "integer") {
|
|
783
|
+
if (isRequiredWithoutDefault) {
|
|
784
|
+
paramResult.isValid = false;
|
|
785
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (param.in === "query" || param.in === "path") {
|
|
790
|
+
if (isRequiredWithoutDefault) {
|
|
791
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
868
795
|
}
|
|
869
796
|
}
|
|
870
797
|
}
|
|
871
|
-
return
|
|
798
|
+
return paramResult;
|
|
799
|
+
}
|
|
800
|
+
hasNestedObjectInSchema(schema) {
|
|
801
|
+
if (schema.type === "object") {
|
|
802
|
+
for (const property in schema.properties) {
|
|
803
|
+
const nestedSchema = schema.properties[property];
|
|
804
|
+
if (nestedSchema.type === "object") {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Copyright (c) Microsoft Corporation.
|
|
814
|
+
class CopilotValidator extends Validator {
|
|
815
|
+
constructor(spec, options) {
|
|
816
|
+
super();
|
|
817
|
+
this.projectType = ProjectType.Copilot;
|
|
818
|
+
this.options = options;
|
|
819
|
+
this.spec = spec;
|
|
820
|
+
}
|
|
821
|
+
validateSpec() {
|
|
822
|
+
const result = { errors: [], warnings: [] };
|
|
823
|
+
// validate spec version
|
|
824
|
+
let validationResult = this.validateSpecVersion();
|
|
825
|
+
result.errors.push(...validationResult.errors);
|
|
826
|
+
// validate spec server
|
|
827
|
+
validationResult = this.validateSpecServer();
|
|
828
|
+
result.errors.push(...validationResult.errors);
|
|
829
|
+
// validate no supported API
|
|
830
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
831
|
+
result.errors.push(...validationResult.errors);
|
|
832
|
+
// validate operationId missing
|
|
833
|
+
validationResult = this.validateSpecOperationId();
|
|
834
|
+
result.warnings.push(...validationResult.warnings);
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
validateAPI(method, path) {
|
|
838
|
+
const result = { isValid: true, reason: [] };
|
|
839
|
+
method = method.toLocaleLowerCase();
|
|
840
|
+
// validate method and path
|
|
841
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
842
|
+
if (!methodAndPathResult.isValid) {
|
|
843
|
+
return methodAndPathResult;
|
|
844
|
+
}
|
|
845
|
+
const operationObject = this.spec.paths[path][method];
|
|
846
|
+
// validate auth
|
|
847
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
848
|
+
result.reason.push(...authCheckResult.reason);
|
|
849
|
+
// validate operationId
|
|
850
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
851
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
852
|
+
}
|
|
853
|
+
// validate server
|
|
854
|
+
const validateServerResult = this.validateServer(method, path);
|
|
855
|
+
result.reason.push(...validateServerResult.reason);
|
|
856
|
+
// validate response
|
|
857
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
858
|
+
result.reason.push(...validateResponseResult.reason);
|
|
859
|
+
// validate requestBody
|
|
860
|
+
const requestBody = operationObject.requestBody;
|
|
861
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
862
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
863
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
864
|
+
}
|
|
865
|
+
if (requestJsonBody) {
|
|
866
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
867
|
+
if (requestBodySchema.type !== "object") {
|
|
868
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
869
|
+
}
|
|
870
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
871
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
872
|
+
}
|
|
873
|
+
// validate parameters
|
|
874
|
+
const paramObject = operationObject.parameters;
|
|
875
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
876
|
+
result.reason.push(...paramResult.reason);
|
|
877
|
+
if (result.reason.length > 0) {
|
|
878
|
+
result.isValid = false;
|
|
879
|
+
}
|
|
880
|
+
return result;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Copyright (c) Microsoft Corporation.
|
|
885
|
+
class SMEValidator extends Validator {
|
|
886
|
+
constructor(spec, options) {
|
|
887
|
+
super();
|
|
888
|
+
this.projectType = ProjectType.SME;
|
|
889
|
+
this.options = options;
|
|
890
|
+
this.spec = spec;
|
|
891
|
+
}
|
|
892
|
+
validateSpec() {
|
|
893
|
+
const result = { errors: [], warnings: [] };
|
|
894
|
+
// validate spec version
|
|
895
|
+
let validationResult = this.validateSpecVersion();
|
|
896
|
+
result.errors.push(...validationResult.errors);
|
|
897
|
+
// validate spec server
|
|
898
|
+
validationResult = this.validateSpecServer();
|
|
899
|
+
result.errors.push(...validationResult.errors);
|
|
900
|
+
// validate no supported API
|
|
901
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
902
|
+
result.errors.push(...validationResult.errors);
|
|
903
|
+
// validate operationId missing
|
|
904
|
+
if (this.options.allowMissingId) {
|
|
905
|
+
validationResult = this.validateSpecOperationId();
|
|
906
|
+
result.warnings.push(...validationResult.warnings);
|
|
907
|
+
}
|
|
908
|
+
return result;
|
|
909
|
+
}
|
|
910
|
+
validateAPI(method, path) {
|
|
911
|
+
const result = { isValid: true, reason: [] };
|
|
912
|
+
method = method.toLocaleLowerCase();
|
|
913
|
+
// validate method and path
|
|
914
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
915
|
+
if (!methodAndPathResult.isValid) {
|
|
916
|
+
return methodAndPathResult;
|
|
917
|
+
}
|
|
918
|
+
const operationObject = this.spec.paths[path][method];
|
|
919
|
+
// validate auth
|
|
920
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
921
|
+
result.reason.push(...authCheckResult.reason);
|
|
922
|
+
// validate operationId
|
|
923
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
924
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
925
|
+
}
|
|
926
|
+
// validate server
|
|
927
|
+
const validateServerResult = this.validateServer(method, path);
|
|
928
|
+
result.reason.push(...validateServerResult.reason);
|
|
929
|
+
// validate response
|
|
930
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
931
|
+
result.reason.push(...validateResponseResult.reason);
|
|
932
|
+
let postBodyResult = {
|
|
933
|
+
requiredNum: 0,
|
|
934
|
+
optionalNum: 0,
|
|
935
|
+
isValid: true,
|
|
936
|
+
reason: [],
|
|
937
|
+
};
|
|
938
|
+
// validate requestBody
|
|
939
|
+
const requestBody = operationObject.requestBody;
|
|
940
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
941
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
942
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
943
|
+
}
|
|
944
|
+
if (requestJsonBody) {
|
|
945
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
946
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
947
|
+
result.reason.push(...postBodyResult.reason);
|
|
948
|
+
}
|
|
949
|
+
// validate parameters
|
|
950
|
+
const paramObject = operationObject.parameters;
|
|
951
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
952
|
+
result.reason.push(...paramResult.reason);
|
|
953
|
+
// validate total parameters count
|
|
954
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
955
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
956
|
+
result.reason.push(...paramCountResult.reason);
|
|
957
|
+
}
|
|
958
|
+
if (result.reason.length > 0) {
|
|
959
|
+
result.isValid = false;
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
964
|
+
const result = { isValid: true, reason: [] };
|
|
965
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
966
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
967
|
+
if (totalRequiredParams > 1) {
|
|
968
|
+
if (!this.options.allowMultipleParameters ||
|
|
969
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
970
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
else if (totalParams === 0) {
|
|
974
|
+
result.reason.push(ErrorType.NoParameter);
|
|
975
|
+
}
|
|
976
|
+
return result;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
980
|
+
|
|
981
|
+
// Copyright (c) Microsoft Corporation.
|
|
982
|
+
class TeamsAIValidator extends Validator {
|
|
983
|
+
constructor(spec, options) {
|
|
984
|
+
super();
|
|
985
|
+
this.projectType = ProjectType.TeamsAi;
|
|
986
|
+
this.options = options;
|
|
987
|
+
this.spec = spec;
|
|
988
|
+
}
|
|
989
|
+
validateSpec() {
|
|
990
|
+
const result = { errors: [], warnings: [] };
|
|
991
|
+
// validate spec server
|
|
992
|
+
let validationResult = this.validateSpecServer();
|
|
993
|
+
result.errors.push(...validationResult.errors);
|
|
994
|
+
// validate no supported API
|
|
995
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
996
|
+
result.errors.push(...validationResult.errors);
|
|
997
|
+
return result;
|
|
998
|
+
}
|
|
999
|
+
validateAPI(method, path) {
|
|
1000
|
+
const result = { isValid: true, reason: [] };
|
|
1001
|
+
method = method.toLocaleLowerCase();
|
|
1002
|
+
// validate method and path
|
|
1003
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1004
|
+
if (!methodAndPathResult.isValid) {
|
|
1005
|
+
return methodAndPathResult;
|
|
1006
|
+
}
|
|
1007
|
+
const operationObject = this.spec.paths[path][method];
|
|
1008
|
+
// validate operationId
|
|
1009
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1010
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1011
|
+
}
|
|
1012
|
+
// validate server
|
|
1013
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1014
|
+
result.reason.push(...validateServerResult.reason);
|
|
1015
|
+
if (result.reason.length > 0) {
|
|
1016
|
+
result.isValid = false;
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
class ValidatorFactory {
|
|
1023
|
+
static create(spec, options) {
|
|
1024
|
+
var _a;
|
|
1025
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1026
|
+
switch (type) {
|
|
1027
|
+
case ProjectType.SME:
|
|
1028
|
+
return new SMEValidator(spec, options);
|
|
1029
|
+
case ProjectType.Copilot:
|
|
1030
|
+
return new CopilotValidator(spec, options);
|
|
1031
|
+
case ProjectType.TeamsAi:
|
|
1032
|
+
return new TeamsAIValidator(spec, options);
|
|
1033
|
+
default:
|
|
1034
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1035
|
+
}
|
|
872
1036
|
}
|
|
873
1037
|
}
|
|
874
1038
|
|
|
@@ -891,6 +1055,8 @@ class SpecParser {
|
|
|
891
1055
|
allowBearerTokenAuth: false,
|
|
892
1056
|
allowOauth2: false,
|
|
893
1057
|
allowMethods: ["get", "post"],
|
|
1058
|
+
allowConversationStarters: false,
|
|
1059
|
+
allowResponseSemantics: false,
|
|
894
1060
|
projectType: ProjectType.SME,
|
|
895
1061
|
};
|
|
896
1062
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -907,11 +1073,7 @@ class SpecParser {
|
|
|
907
1073
|
try {
|
|
908
1074
|
try {
|
|
909
1075
|
yield this.loadSpec();
|
|
910
|
-
yield this.parser.validate(this.spec
|
|
911
|
-
validate: {
|
|
912
|
-
schema: false,
|
|
913
|
-
},
|
|
914
|
-
});
|
|
1076
|
+
yield this.parser.validate(this.spec);
|
|
915
1077
|
}
|
|
916
1078
|
catch (e) {
|
|
917
1079
|
return {
|
|
@@ -920,16 +1082,46 @@ class SpecParser {
|
|
|
920
1082
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
921
1083
|
};
|
|
922
1084
|
}
|
|
1085
|
+
const errors = [];
|
|
1086
|
+
const warnings = [];
|
|
923
1087
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
924
1088
|
return {
|
|
925
1089
|
status: ValidationStatus.Error,
|
|
926
1090
|
warnings: [],
|
|
927
1091
|
errors: [
|
|
928
|
-
{
|
|
1092
|
+
{
|
|
1093
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1094
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1095
|
+
},
|
|
929
1096
|
],
|
|
930
1097
|
};
|
|
931
1098
|
}
|
|
932
|
-
|
|
1099
|
+
// Remote reference not supported
|
|
1100
|
+
const refPaths = this.parser.$refs.paths();
|
|
1101
|
+
// refPaths [0] is the current spec file path
|
|
1102
|
+
if (refPaths.length > 1) {
|
|
1103
|
+
errors.push({
|
|
1104
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1105
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1106
|
+
data: refPaths,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
const validator = this.getValidator(this.spec);
|
|
1110
|
+
const validationResult = validator.validateSpec();
|
|
1111
|
+
warnings.push(...validationResult.warnings);
|
|
1112
|
+
errors.push(...validationResult.errors);
|
|
1113
|
+
let status = ValidationStatus.Valid;
|
|
1114
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1115
|
+
status = ValidationStatus.Warning;
|
|
1116
|
+
}
|
|
1117
|
+
else if (errors.length > 0) {
|
|
1118
|
+
status = ValidationStatus.Error;
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
status: status,
|
|
1122
|
+
warnings: warnings,
|
|
1123
|
+
errors: errors,
|
|
1124
|
+
};
|
|
933
1125
|
}
|
|
934
1126
|
catch (err) {
|
|
935
1127
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -940,17 +1132,20 @@ class SpecParser {
|
|
|
940
1132
|
return __awaiter(this, void 0, void 0, function* () {
|
|
941
1133
|
try {
|
|
942
1134
|
yield this.loadSpec();
|
|
943
|
-
const apiMap = this.
|
|
1135
|
+
const apiMap = this.getAPIs(this.spec);
|
|
944
1136
|
const apiInfos = [];
|
|
945
1137
|
for (const key in apiMap) {
|
|
946
|
-
const
|
|
1138
|
+
const { operation, isValid } = apiMap[key];
|
|
1139
|
+
if (!isValid) {
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
947
1142
|
const [method, path] = key.split(" ");
|
|
948
|
-
const operationId =
|
|
1143
|
+
const operationId = operation.operationId;
|
|
949
1144
|
// In Browser environment, this api is by default not support api without operationId
|
|
950
1145
|
if (!operationId) {
|
|
951
1146
|
continue;
|
|
952
1147
|
}
|
|
953
|
-
const
|
|
1148
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
954
1149
|
const apiInfo = {
|
|
955
1150
|
method: method,
|
|
956
1151
|
path: path,
|
|
@@ -959,9 +1154,6 @@ class SpecParser {
|
|
|
959
1154
|
parameters: command.parameters,
|
|
960
1155
|
description: command.description,
|
|
961
1156
|
};
|
|
962
|
-
if (warning) {
|
|
963
|
-
apiInfo.warning = warning;
|
|
964
|
-
}
|
|
965
1157
|
apiInfos.push(apiInfo);
|
|
966
1158
|
}
|
|
967
1159
|
return apiInfos;
|
|
@@ -1031,31 +1223,18 @@ class SpecParser {
|
|
|
1031
1223
|
}
|
|
1032
1224
|
});
|
|
1033
1225
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const result = this.listSupportedAPIs(spec, this.options);
|
|
1039
|
-
this.apiMap = result;
|
|
1040
|
-
return result;
|
|
1226
|
+
getAPIs(spec) {
|
|
1227
|
+
const validator = this.getValidator(spec);
|
|
1228
|
+
const apiMap = validator.listAPIs();
|
|
1229
|
+
return apiMap;
|
|
1041
1230
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const result = {};
|
|
1046
|
-
for (const path in paths) {
|
|
1047
|
-
const methods = paths[path];
|
|
1048
|
-
for (const method in methods) {
|
|
1049
|
-
const operationObject = methods[method];
|
|
1050
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1051
|
-
const validateResult = Utils.isSupportedApi(method, path, spec, options);
|
|
1052
|
-
if (validateResult.isValid) {
|
|
1053
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1231
|
+
getValidator(spec) {
|
|
1232
|
+
if (this.validator) {
|
|
1233
|
+
return this.validator;
|
|
1057
1234
|
}
|
|
1058
|
-
|
|
1235
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1236
|
+
this.validator = validator;
|
|
1237
|
+
return validator;
|
|
1059
1238
|
}
|
|
1060
1239
|
}
|
|
1061
1240
|
|