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