@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.42af26ca6.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 +571 -325
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +640 -365
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +571 -325
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +640 -365
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/interfaces.d.ts +41 -1
- package/dist/src/manifestUpdater.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +3 -1
- package/dist/src/specParser.d.ts +3 -1
- package/dist/src/utils.d.ts +8 -28
- package/package.json +3 -3
package/dist/index.esm5.js
CHANGED
|
@@ -46,6 +46,7 @@ var ErrorType;
|
|
|
46
46
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
47
47
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
48
48
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
49
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
49
50
|
ErrorType["ListFailed"] = "list-failed";
|
|
50
51
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
51
52
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -54,6 +55,21 @@ var ErrorType;
|
|
|
54
55
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
55
56
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
56
57
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
58
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
59
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
60
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
61
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
62
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
63
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
64
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
65
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
66
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
67
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
68
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
69
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
70
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
71
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
72
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
57
73
|
ErrorType["Cancelled"] = "cancelled";
|
|
58
74
|
ErrorType["Unknown"] = "unknown";
|
|
59
75
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -109,6 +125,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
109
125
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
110
126
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
111
127
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
128
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
112
129
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
113
130
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
114
131
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -198,221 +215,9 @@ class Utils {
|
|
|
198
215
|
}
|
|
199
216
|
return false;
|
|
200
217
|
}
|
|
201
|
-
static checkParameters(paramObject, isCopilot) {
|
|
202
|
-
const paramResult = {
|
|
203
|
-
requiredNum: 0,
|
|
204
|
-
optionalNum: 0,
|
|
205
|
-
isValid: true,
|
|
206
|
-
};
|
|
207
|
-
if (!paramObject) {
|
|
208
|
-
return paramResult;
|
|
209
|
-
}
|
|
210
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
211
|
-
const param = paramObject[i];
|
|
212
|
-
const schema = param.schema;
|
|
213
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
214
|
-
paramResult.isValid = false;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
218
|
-
if (isCopilot) {
|
|
219
|
-
if (isRequiredWithoutDefault) {
|
|
220
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
224
|
-
}
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
228
|
-
if (isRequiredWithoutDefault) {
|
|
229
|
-
paramResult.isValid = false;
|
|
230
|
-
}
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (schema.type !== "boolean" &&
|
|
234
|
-
schema.type !== "string" &&
|
|
235
|
-
schema.type !== "number" &&
|
|
236
|
-
schema.type !== "integer") {
|
|
237
|
-
if (isRequiredWithoutDefault) {
|
|
238
|
-
paramResult.isValid = false;
|
|
239
|
-
}
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (param.in === "query" || param.in === "path") {
|
|
243
|
-
if (isRequiredWithoutDefault) {
|
|
244
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return paramResult;
|
|
252
|
-
}
|
|
253
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
254
|
-
var _a;
|
|
255
|
-
const paramResult = {
|
|
256
|
-
requiredNum: 0,
|
|
257
|
-
optionalNum: 0,
|
|
258
|
-
isValid: true,
|
|
259
|
-
};
|
|
260
|
-
if (Object.keys(schema).length === 0) {
|
|
261
|
-
return paramResult;
|
|
262
|
-
}
|
|
263
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
264
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
265
|
-
paramResult.isValid = false;
|
|
266
|
-
return paramResult;
|
|
267
|
-
}
|
|
268
|
-
if (schema.type === "string" ||
|
|
269
|
-
schema.type === "integer" ||
|
|
270
|
-
schema.type === "boolean" ||
|
|
271
|
-
schema.type === "number") {
|
|
272
|
-
if (isRequiredWithoutDefault) {
|
|
273
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else if (schema.type === "object") {
|
|
280
|
-
const { properties } = schema;
|
|
281
|
-
for (const property in properties) {
|
|
282
|
-
let isRequired = false;
|
|
283
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
284
|
-
isRequired = true;
|
|
285
|
-
}
|
|
286
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
287
|
-
paramResult.requiredNum += result.requiredNum;
|
|
288
|
-
paramResult.optionalNum += result.optionalNum;
|
|
289
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
294
|
-
paramResult.isValid = false;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return paramResult;
|
|
298
|
-
}
|
|
299
218
|
static containMultipleMediaTypes(bodyObject) {
|
|
300
219
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
301
220
|
}
|
|
302
|
-
/**
|
|
303
|
-
* Checks if the given API is supported.
|
|
304
|
-
* @param {string} method - The HTTP method of the API.
|
|
305
|
-
* @param {string} path - The path of the API.
|
|
306
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
307
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
308
|
-
* @description The following APIs are supported:
|
|
309
|
-
* 1. only support Get/Post operation without auth property
|
|
310
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
311
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
312
|
-
* 4. request body + required parameters <= 1
|
|
313
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
314
|
-
* 6. only support request body with “application/json” content type
|
|
315
|
-
*/
|
|
316
|
-
static isSupportedApi(method, path, spec, options) {
|
|
317
|
-
var _a;
|
|
318
|
-
const pathObj = spec.paths[path];
|
|
319
|
-
method = method.toLocaleLowerCase();
|
|
320
|
-
if (pathObj) {
|
|
321
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
322
|
-
const securities = pathObj[method].security;
|
|
323
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
324
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
325
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
326
|
-
if (!isTeamsAi) {
|
|
327
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
328
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
const operationObject = pathObj[method];
|
|
333
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
const paramObject = operationObject.parameters;
|
|
337
|
-
const requestBody = operationObject.requestBody;
|
|
338
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
339
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
343
|
-
if (Object.keys(responseJson).length === 0) {
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
// Teams AI project doesn't care about request parameters/body
|
|
347
|
-
if (isTeamsAi) {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
let requestBodyParamResult = {
|
|
351
|
-
requiredNum: 0,
|
|
352
|
-
optionalNum: 0,
|
|
353
|
-
isValid: true,
|
|
354
|
-
};
|
|
355
|
-
if (requestJsonBody) {
|
|
356
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
357
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
361
|
-
}
|
|
362
|
-
if (!requestBodyParamResult.isValid) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
366
|
-
if (!paramResult.isValid) {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
// Copilot support arbitrary parameters
|
|
370
|
-
if (isCopilot) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
374
|
-
if (options.allowMultipleParameters &&
|
|
375
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
376
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
else if (requestBodyParamResult.requiredNum +
|
|
382
|
-
requestBodyParamResult.optionalNum +
|
|
383
|
-
paramResult.requiredNum +
|
|
384
|
-
paramResult.optionalNum ===
|
|
385
|
-
0) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
396
|
-
if (authSchemeArray.length === 0) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
400
|
-
// Currently we don't support multiple auth in one operation
|
|
401
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
for (const auths of authSchemeArray) {
|
|
405
|
-
if (auths.length === 1) {
|
|
406
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
407
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
408
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
221
|
static isBearerTokenAuth(authScheme) {
|
|
417
222
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
418
223
|
}
|
|
@@ -420,10 +225,9 @@ class Utils {
|
|
|
420
225
|
return authScheme.type === "apiKey";
|
|
421
226
|
}
|
|
422
227
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return false;
|
|
228
|
+
return !!(authScheme.type === "oauth2" &&
|
|
229
|
+
authScheme.flows &&
|
|
230
|
+
authScheme.flows.authorizationCode);
|
|
427
231
|
}
|
|
428
232
|
static getAuthArray(securities, spec) {
|
|
429
233
|
var _a;
|
|
@@ -451,14 +255,17 @@ class Utils {
|
|
|
451
255
|
static updateFirstLetter(str) {
|
|
452
256
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
453
257
|
}
|
|
454
|
-
static getResponseJson(operationObject
|
|
258
|
+
static getResponseJson(operationObject) {
|
|
455
259
|
var _a, _b;
|
|
456
260
|
let json = {};
|
|
261
|
+
let multipleMediaType = false;
|
|
457
262
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
458
263
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
459
264
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
265
|
+
multipleMediaType = false;
|
|
460
266
|
json = responseObject.content["application/json"];
|
|
461
|
-
if (
|
|
267
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
268
|
+
multipleMediaType = true;
|
|
462
269
|
json = {};
|
|
463
270
|
}
|
|
464
271
|
else {
|
|
@@ -466,7 +273,7 @@ class Utils {
|
|
|
466
273
|
}
|
|
467
274
|
}
|
|
468
275
|
}
|
|
469
|
-
return json;
|
|
276
|
+
return { json, multipleMediaType };
|
|
470
277
|
}
|
|
471
278
|
static convertPathToCamelCase(path) {
|
|
472
279
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -486,10 +293,10 @@ class Utils {
|
|
|
486
293
|
return undefined;
|
|
487
294
|
}
|
|
488
295
|
}
|
|
489
|
-
static
|
|
296
|
+
static resolveEnv(str) {
|
|
490
297
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
491
|
-
let matches = placeHolderReg.exec(
|
|
492
|
-
let
|
|
298
|
+
let matches = placeHolderReg.exec(str);
|
|
299
|
+
let newStr = str;
|
|
493
300
|
while (matches != null) {
|
|
494
301
|
const envVar = matches[1];
|
|
495
302
|
const envVal = process.env[envVar];
|
|
@@ -497,17 +304,17 @@ class Utils {
|
|
|
497
304
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
498
305
|
}
|
|
499
306
|
else {
|
|
500
|
-
|
|
307
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
501
308
|
}
|
|
502
|
-
matches = placeHolderReg.exec(
|
|
309
|
+
matches = placeHolderReg.exec(str);
|
|
503
310
|
}
|
|
504
|
-
return
|
|
311
|
+
return newStr;
|
|
505
312
|
}
|
|
506
313
|
static checkServerUrl(servers) {
|
|
507
314
|
const errors = [];
|
|
508
315
|
let serverUrl;
|
|
509
316
|
try {
|
|
510
|
-
serverUrl = Utils.
|
|
317
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
511
318
|
}
|
|
512
319
|
catch (err) {
|
|
513
320
|
errors.push({
|
|
@@ -538,6 +345,7 @@ class Utils {
|
|
|
538
345
|
return errors;
|
|
539
346
|
}
|
|
540
347
|
static validateServer(spec, options) {
|
|
348
|
+
var _a;
|
|
541
349
|
const errors = [];
|
|
542
350
|
let hasTopLevelServers = false;
|
|
543
351
|
let hasPathLevelServers = false;
|
|
@@ -558,7 +366,7 @@ class Utils {
|
|
|
558
366
|
}
|
|
559
367
|
for (const method in methods) {
|
|
560
368
|
const operationObject = methods[method];
|
|
561
|
-
if (
|
|
369
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
562
370
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
563
371
|
hasOperationLevelServers = true;
|
|
564
372
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -685,13 +493,7 @@ class Utils {
|
|
|
685
493
|
}
|
|
686
494
|
}
|
|
687
495
|
const operationId = operationItem.operationId;
|
|
688
|
-
const parameters = [];
|
|
689
|
-
if (requiredParams.length !== 0) {
|
|
690
|
-
parameters.push(...requiredParams);
|
|
691
|
-
}
|
|
692
|
-
else {
|
|
693
|
-
parameters.push(optionalParams[0]);
|
|
694
|
-
}
|
|
496
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
695
497
|
const command = {
|
|
696
498
|
context: ["compose"],
|
|
697
499
|
type: "query",
|
|
@@ -700,117 +502,526 @@ class Utils {
|
|
|
700
502
|
parameters: parameters,
|
|
701
503
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
702
504
|
};
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
505
|
+
return command;
|
|
506
|
+
}
|
|
507
|
+
static format(str, ...args) {
|
|
508
|
+
let index = 0;
|
|
509
|
+
return str.replace(/%s/g, () => {
|
|
510
|
+
const arg = args[index++];
|
|
511
|
+
return arg !== undefined ? arg : "";
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
515
|
+
if (!authName) {
|
|
516
|
+
return "";
|
|
710
517
|
}
|
|
711
|
-
|
|
518
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
519
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
520
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
521
|
+
}
|
|
522
|
+
return safeRegistrationIdEnvName;
|
|
712
523
|
}
|
|
713
|
-
static
|
|
714
|
-
const
|
|
524
|
+
static getServerObject(spec, method, path) {
|
|
525
|
+
const pathObj = spec.paths[path];
|
|
526
|
+
const operationObject = pathObj[method];
|
|
527
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
528
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
529
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
530
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
531
|
+
return serverUrl;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Copyright (c) Microsoft Corporation.
|
|
536
|
+
class Validator {
|
|
537
|
+
listAPIs() {
|
|
538
|
+
var _a;
|
|
539
|
+
if (this.apiMap) {
|
|
540
|
+
return this.apiMap;
|
|
541
|
+
}
|
|
542
|
+
const paths = this.spec.paths;
|
|
715
543
|
const result = {};
|
|
716
544
|
for (const path in paths) {
|
|
717
545
|
const methods = paths[path];
|
|
718
546
|
for (const method in methods) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
547
|
+
const operationObject = methods[method];
|
|
548
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
549
|
+
const validateResult = this.validateAPI(method, path);
|
|
550
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
551
|
+
operation: operationObject,
|
|
552
|
+
isValid: validateResult.isValid,
|
|
553
|
+
reason: validateResult.reason,
|
|
554
|
+
};
|
|
722
555
|
}
|
|
723
556
|
}
|
|
724
557
|
}
|
|
558
|
+
this.apiMap = result;
|
|
725
559
|
return result;
|
|
726
560
|
}
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
561
|
+
validateSpecVersion() {
|
|
562
|
+
const result = { errors: [], warnings: [] };
|
|
563
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
564
|
+
result.errors.push({
|
|
565
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
566
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
567
|
+
data: this.spec.openapi,
|
|
734
568
|
});
|
|
735
569
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const apiMap = Utils.listSupportedAPIs(spec, options);
|
|
751
|
-
if (Object.keys(apiMap).length === 0) {
|
|
752
|
-
errors.push({
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
validateSpecServer() {
|
|
573
|
+
const result = { errors: [], warnings: [] };
|
|
574
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
575
|
+
result.errors.push(...serverErrors);
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
validateSpecNoSupportAPI() {
|
|
579
|
+
const result = { errors: [], warnings: [] };
|
|
580
|
+
const apiMap = this.listAPIs();
|
|
581
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
582
|
+
if (validAPIs.length === 0) {
|
|
583
|
+
result.errors.push({
|
|
753
584
|
type: ErrorType.NoSupportedApi,
|
|
754
585
|
content: ConstantString.NoSupportedApi,
|
|
755
586
|
});
|
|
756
587
|
}
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
validateSpecOperationId() {
|
|
591
|
+
const result = { errors: [], warnings: [] };
|
|
592
|
+
const apiMap = this.listAPIs();
|
|
757
593
|
// OperationId missing
|
|
758
594
|
const apisMissingOperationId = [];
|
|
759
595
|
for (const key in apiMap) {
|
|
760
|
-
const
|
|
761
|
-
if (!
|
|
596
|
+
const { operation } = apiMap[key];
|
|
597
|
+
if (!operation.operationId) {
|
|
762
598
|
apisMissingOperationId.push(key);
|
|
763
599
|
}
|
|
764
600
|
}
|
|
765
601
|
if (apisMissingOperationId.length > 0) {
|
|
766
|
-
warnings.push({
|
|
602
|
+
result.warnings.push({
|
|
767
603
|
type: WarningType.OperationIdMissing,
|
|
768
604
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
769
605
|
data: apisMissingOperationId,
|
|
770
606
|
});
|
|
771
607
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
validateMethodAndPath(method, path) {
|
|
611
|
+
const result = { isValid: true, reason: [] };
|
|
612
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
613
|
+
result.isValid = false;
|
|
614
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
615
|
+
return result;
|
|
775
616
|
}
|
|
776
|
-
|
|
777
|
-
|
|
617
|
+
const pathObj = this.spec.paths[path];
|
|
618
|
+
if (!pathObj || !pathObj[method]) {
|
|
619
|
+
result.isValid = false;
|
|
620
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
621
|
+
return result;
|
|
778
622
|
}
|
|
779
|
-
return
|
|
780
|
-
status,
|
|
781
|
-
warnings,
|
|
782
|
-
errors,
|
|
783
|
-
};
|
|
623
|
+
return result;
|
|
784
624
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
625
|
+
validateResponse(method, path) {
|
|
626
|
+
const result = { isValid: true, reason: [] };
|
|
627
|
+
const operationObject = this.spec.paths[path][method];
|
|
628
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
629
|
+
// only support response body only contains “application/json” content type
|
|
630
|
+
if (multipleMediaType) {
|
|
631
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
632
|
+
}
|
|
633
|
+
else if (Object.keys(json).length === 0) {
|
|
634
|
+
// response body should not be empty
|
|
635
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
791
638
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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);
|
|
795
645
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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));
|
|
799
650
|
}
|
|
800
|
-
return
|
|
651
|
+
return result;
|
|
801
652
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
+
}
|
|
810
678
|
}
|
|
811
679
|
}
|
|
812
680
|
}
|
|
813
|
-
return
|
|
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;
|
|
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);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
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 (Utils.containMultipleMediaTypes(requestBody)) {
|
|
854
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
855
|
+
}
|
|
856
|
+
if (requestJsonBody) {
|
|
857
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
858
|
+
if (requestBodySchema.type !== "object") {
|
|
859
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
860
|
+
}
|
|
861
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
862
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
863
|
+
}
|
|
864
|
+
// validate parameters
|
|
865
|
+
const paramObject = operationObject.parameters;
|
|
866
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
867
|
+
result.reason.push(...paramResult.reason);
|
|
868
|
+
if (result.reason.length > 0) {
|
|
869
|
+
result.isValid = false;
|
|
870
|
+
}
|
|
871
|
+
return result;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Copyright (c) Microsoft Corporation.
|
|
876
|
+
class SMEValidator extends Validator {
|
|
877
|
+
constructor(spec, options) {
|
|
878
|
+
super();
|
|
879
|
+
this.projectType = ProjectType.SME;
|
|
880
|
+
this.options = options;
|
|
881
|
+
this.spec = spec;
|
|
882
|
+
}
|
|
883
|
+
validateSpec() {
|
|
884
|
+
const result = { errors: [], warnings: [] };
|
|
885
|
+
// validate spec version
|
|
886
|
+
let validationResult = this.validateSpecVersion();
|
|
887
|
+
result.errors.push(...validationResult.errors);
|
|
888
|
+
// validate spec server
|
|
889
|
+
validationResult = this.validateSpecServer();
|
|
890
|
+
result.errors.push(...validationResult.errors);
|
|
891
|
+
// validate no supported API
|
|
892
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
893
|
+
result.errors.push(...validationResult.errors);
|
|
894
|
+
// validate operationId missing
|
|
895
|
+
validationResult = this.validateSpecOperationId();
|
|
896
|
+
result.warnings.push(...validationResult.warnings);
|
|
897
|
+
return result;
|
|
898
|
+
}
|
|
899
|
+
validateAPI(method, path) {
|
|
900
|
+
const result = { isValid: true, reason: [] };
|
|
901
|
+
method = method.toLocaleLowerCase();
|
|
902
|
+
// validate method and path
|
|
903
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
904
|
+
if (!methodAndPathResult.isValid) {
|
|
905
|
+
return methodAndPathResult;
|
|
906
|
+
}
|
|
907
|
+
const operationObject = this.spec.paths[path][method];
|
|
908
|
+
// validate auth
|
|
909
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
910
|
+
result.reason.push(...authCheckResult.reason);
|
|
911
|
+
// validate operationId
|
|
912
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
913
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
914
|
+
}
|
|
915
|
+
// validate server
|
|
916
|
+
const validateServerResult = this.validateServer(method, path);
|
|
917
|
+
result.reason.push(...validateServerResult.reason);
|
|
918
|
+
// validate response
|
|
919
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
920
|
+
result.reason.push(...validateResponseResult.reason);
|
|
921
|
+
let postBodyResult = {
|
|
922
|
+
requiredNum: 0,
|
|
923
|
+
optionalNum: 0,
|
|
924
|
+
isValid: true,
|
|
925
|
+
reason: [],
|
|
926
|
+
};
|
|
927
|
+
// validate requestBody
|
|
928
|
+
const requestBody = operationObject.requestBody;
|
|
929
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
930
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
931
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
932
|
+
}
|
|
933
|
+
if (requestJsonBody) {
|
|
934
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
935
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
936
|
+
result.reason.push(...postBodyResult.reason);
|
|
937
|
+
}
|
|
938
|
+
// validate parameters
|
|
939
|
+
const paramObject = operationObject.parameters;
|
|
940
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
941
|
+
result.reason.push(...paramResult.reason);
|
|
942
|
+
// validate total parameters count
|
|
943
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
944
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
945
|
+
result.reason.push(...paramCountResult.reason);
|
|
946
|
+
}
|
|
947
|
+
if (result.reason.length > 0) {
|
|
948
|
+
result.isValid = false;
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
953
|
+
const result = { isValid: true, reason: [] };
|
|
954
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
955
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
956
|
+
if (totalRequiredParams > 1) {
|
|
957
|
+
if (!this.options.allowMultipleParameters ||
|
|
958
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
959
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else if (totalParams === 0) {
|
|
963
|
+
result.reason.push(ErrorType.NoParameter);
|
|
964
|
+
}
|
|
965
|
+
return result;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
969
|
+
|
|
970
|
+
// Copyright (c) Microsoft Corporation.
|
|
971
|
+
class TeamsAIValidator extends Validator {
|
|
972
|
+
constructor(spec, options) {
|
|
973
|
+
super();
|
|
974
|
+
this.projectType = ProjectType.TeamsAi;
|
|
975
|
+
this.options = options;
|
|
976
|
+
this.spec = spec;
|
|
977
|
+
}
|
|
978
|
+
validateSpec() {
|
|
979
|
+
const result = { errors: [], warnings: [] };
|
|
980
|
+
// validate spec server
|
|
981
|
+
let validationResult = this.validateSpecServer();
|
|
982
|
+
result.errors.push(...validationResult.errors);
|
|
983
|
+
// validate no supported API
|
|
984
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
985
|
+
result.errors.push(...validationResult.errors);
|
|
986
|
+
return result;
|
|
987
|
+
}
|
|
988
|
+
validateAPI(method, path) {
|
|
989
|
+
const result = { isValid: true, reason: [] };
|
|
990
|
+
method = method.toLocaleLowerCase();
|
|
991
|
+
// validate method and path
|
|
992
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
993
|
+
if (!methodAndPathResult.isValid) {
|
|
994
|
+
return methodAndPathResult;
|
|
995
|
+
}
|
|
996
|
+
const operationObject = this.spec.paths[path][method];
|
|
997
|
+
// validate operationId
|
|
998
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
999
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1000
|
+
}
|
|
1001
|
+
// validate server
|
|
1002
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1003
|
+
result.reason.push(...validateServerResult.reason);
|
|
1004
|
+
if (result.reason.length > 0) {
|
|
1005
|
+
result.isValid = false;
|
|
1006
|
+
}
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
class ValidatorFactory {
|
|
1012
|
+
static create(spec, options) {
|
|
1013
|
+
var _a;
|
|
1014
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1015
|
+
switch (type) {
|
|
1016
|
+
case ProjectType.SME:
|
|
1017
|
+
return new SMEValidator(spec, options);
|
|
1018
|
+
case ProjectType.Copilot:
|
|
1019
|
+
return new CopilotValidator(spec, options);
|
|
1020
|
+
case ProjectType.TeamsAi:
|
|
1021
|
+
return new TeamsAIValidator(spec, options);
|
|
1022
|
+
default:
|
|
1023
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1024
|
+
}
|
|
814
1025
|
}
|
|
815
1026
|
}
|
|
816
1027
|
|
|
@@ -849,11 +1060,7 @@ class SpecParser {
|
|
|
849
1060
|
try {
|
|
850
1061
|
try {
|
|
851
1062
|
yield this.loadSpec();
|
|
852
|
-
yield this.parser.validate(this.spec
|
|
853
|
-
validate: {
|
|
854
|
-
schema: false,
|
|
855
|
-
},
|
|
856
|
-
});
|
|
1063
|
+
yield this.parser.validate(this.spec);
|
|
857
1064
|
}
|
|
858
1065
|
catch (e) {
|
|
859
1066
|
return {
|
|
@@ -862,16 +1069,46 @@ class SpecParser {
|
|
|
862
1069
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
863
1070
|
};
|
|
864
1071
|
}
|
|
1072
|
+
const errors = [];
|
|
1073
|
+
const warnings = [];
|
|
865
1074
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
866
1075
|
return {
|
|
867
1076
|
status: ValidationStatus.Error,
|
|
868
1077
|
warnings: [],
|
|
869
1078
|
errors: [
|
|
870
|
-
{
|
|
1079
|
+
{
|
|
1080
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1081
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1082
|
+
},
|
|
871
1083
|
],
|
|
872
1084
|
};
|
|
873
1085
|
}
|
|
874
|
-
|
|
1086
|
+
// Remote reference not supported
|
|
1087
|
+
const refPaths = this.parser.$refs.paths();
|
|
1088
|
+
// refPaths [0] is the current spec file path
|
|
1089
|
+
if (refPaths.length > 1) {
|
|
1090
|
+
errors.push({
|
|
1091
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1092
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1093
|
+
data: refPaths,
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
const validator = this.getValidator(this.spec);
|
|
1097
|
+
const validationResult = validator.validateSpec();
|
|
1098
|
+
warnings.push(...validationResult.warnings);
|
|
1099
|
+
errors.push(...validationResult.errors);
|
|
1100
|
+
let status = ValidationStatus.Valid;
|
|
1101
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1102
|
+
status = ValidationStatus.Warning;
|
|
1103
|
+
}
|
|
1104
|
+
else if (errors.length > 0) {
|
|
1105
|
+
status = ValidationStatus.Error;
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
status: status,
|
|
1109
|
+
warnings: warnings,
|
|
1110
|
+
errors: errors,
|
|
1111
|
+
};
|
|
875
1112
|
}
|
|
876
1113
|
catch (err) {
|
|
877
1114
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -882,17 +1119,20 @@ class SpecParser {
|
|
|
882
1119
|
return __awaiter(this, void 0, void 0, function* () {
|
|
883
1120
|
try {
|
|
884
1121
|
yield this.loadSpec();
|
|
885
|
-
const apiMap = this.
|
|
1122
|
+
const apiMap = this.getAPIs(this.spec);
|
|
886
1123
|
const apiInfos = [];
|
|
887
1124
|
for (const key in apiMap) {
|
|
888
|
-
const
|
|
1125
|
+
const { operation, isValid } = apiMap[key];
|
|
1126
|
+
if (!isValid) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
889
1129
|
const [method, path] = key.split(" ");
|
|
890
|
-
const operationId =
|
|
1130
|
+
const operationId = operation.operationId;
|
|
891
1131
|
// In Browser environment, this api is by default not support api without operationId
|
|
892
1132
|
if (!operationId) {
|
|
893
1133
|
continue;
|
|
894
1134
|
}
|
|
895
|
-
const
|
|
1135
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
896
1136
|
const apiInfo = {
|
|
897
1137
|
method: method,
|
|
898
1138
|
path: path,
|
|
@@ -901,9 +1141,6 @@ class SpecParser {
|
|
|
901
1141
|
parameters: command.parameters,
|
|
902
1142
|
description: command.description,
|
|
903
1143
|
};
|
|
904
|
-
if (warning) {
|
|
905
|
-
apiInfo.warning = warning;
|
|
906
|
-
}
|
|
907
1144
|
apiInfos.push(apiInfo);
|
|
908
1145
|
}
|
|
909
1146
|
return apiInfos;
|
|
@@ -973,13 +1210,22 @@ class SpecParser {
|
|
|
973
1210
|
}
|
|
974
1211
|
});
|
|
975
1212
|
}
|
|
976
|
-
|
|
1213
|
+
getAPIs(spec) {
|
|
977
1214
|
if (this.apiMap !== undefined) {
|
|
978
1215
|
return this.apiMap;
|
|
979
1216
|
}
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
1217
|
+
const validator = this.getValidator(spec);
|
|
1218
|
+
const apiMap = validator.listAPIs();
|
|
1219
|
+
this.apiMap = apiMap;
|
|
1220
|
+
return apiMap;
|
|
1221
|
+
}
|
|
1222
|
+
getValidator(spec) {
|
|
1223
|
+
if (this.validator) {
|
|
1224
|
+
return this.validator;
|
|
1225
|
+
}
|
|
1226
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1227
|
+
this.validator = validator;
|
|
1228
|
+
return validator;
|
|
983
1229
|
}
|
|
984
1230
|
}
|
|
985
1231
|
|
|
@@ -987,7 +1233,7 @@ class SpecParser {
|
|
|
987
1233
|
class AdaptiveCardGenerator {
|
|
988
1234
|
static generateAdaptiveCard(operationItem) {
|
|
989
1235
|
try {
|
|
990
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1236
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
991
1237
|
let cardBody = [];
|
|
992
1238
|
let schema = json.schema;
|
|
993
1239
|
let jsonPath = "$";
|