@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.node.cjs.js
CHANGED
|
@@ -62,6 +62,7 @@ exports.ErrorType = void 0;
|
|
|
62
62
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
63
63
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
64
64
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
65
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
65
66
|
ErrorType["ListFailed"] = "list-failed";
|
|
66
67
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
67
68
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -70,6 +71,21 @@ exports.ErrorType = void 0;
|
|
|
70
71
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
71
72
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
72
73
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
74
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
75
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
76
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
77
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
78
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
79
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
80
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
81
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
82
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
83
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
84
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
85
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
86
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
87
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
88
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
73
89
|
ErrorType["Cancelled"] = "cancelled";
|
|
74
90
|
ErrorType["Unknown"] = "unknown";
|
|
75
91
|
})(exports.ErrorType || (exports.ErrorType = {}));
|
|
@@ -117,6 +133,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
117
133
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
118
134
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
119
135
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
136
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
120
137
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
121
138
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
122
139
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -214,221 +231,9 @@ class Utils {
|
|
|
214
231
|
}
|
|
215
232
|
return false;
|
|
216
233
|
}
|
|
217
|
-
static checkParameters(paramObject, isCopilot) {
|
|
218
|
-
const paramResult = {
|
|
219
|
-
requiredNum: 0,
|
|
220
|
-
optionalNum: 0,
|
|
221
|
-
isValid: true,
|
|
222
|
-
};
|
|
223
|
-
if (!paramObject) {
|
|
224
|
-
return paramResult;
|
|
225
|
-
}
|
|
226
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
227
|
-
const param = paramObject[i];
|
|
228
|
-
const schema = param.schema;
|
|
229
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
230
|
-
paramResult.isValid = false;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
234
|
-
if (isCopilot) {
|
|
235
|
-
if (isRequiredWithoutDefault) {
|
|
236
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
240
|
-
}
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
244
|
-
if (isRequiredWithoutDefault) {
|
|
245
|
-
paramResult.isValid = false;
|
|
246
|
-
}
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
if (schema.type !== "boolean" &&
|
|
250
|
-
schema.type !== "string" &&
|
|
251
|
-
schema.type !== "number" &&
|
|
252
|
-
schema.type !== "integer") {
|
|
253
|
-
if (isRequiredWithoutDefault) {
|
|
254
|
-
paramResult.isValid = false;
|
|
255
|
-
}
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (param.in === "query" || param.in === "path") {
|
|
259
|
-
if (isRequiredWithoutDefault) {
|
|
260
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return paramResult;
|
|
268
|
-
}
|
|
269
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
270
|
-
var _a;
|
|
271
|
-
const paramResult = {
|
|
272
|
-
requiredNum: 0,
|
|
273
|
-
optionalNum: 0,
|
|
274
|
-
isValid: true,
|
|
275
|
-
};
|
|
276
|
-
if (Object.keys(schema).length === 0) {
|
|
277
|
-
return paramResult;
|
|
278
|
-
}
|
|
279
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
280
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
281
|
-
paramResult.isValid = false;
|
|
282
|
-
return paramResult;
|
|
283
|
-
}
|
|
284
|
-
if (schema.type === "string" ||
|
|
285
|
-
schema.type === "integer" ||
|
|
286
|
-
schema.type === "boolean" ||
|
|
287
|
-
schema.type === "number") {
|
|
288
|
-
if (isRequiredWithoutDefault) {
|
|
289
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
else if (schema.type === "object") {
|
|
296
|
-
const { properties } = schema;
|
|
297
|
-
for (const property in properties) {
|
|
298
|
-
let isRequired = false;
|
|
299
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
300
|
-
isRequired = true;
|
|
301
|
-
}
|
|
302
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
303
|
-
paramResult.requiredNum += result.requiredNum;
|
|
304
|
-
paramResult.optionalNum += result.optionalNum;
|
|
305
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
310
|
-
paramResult.isValid = false;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return paramResult;
|
|
314
|
-
}
|
|
315
234
|
static containMultipleMediaTypes(bodyObject) {
|
|
316
235
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
317
236
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Checks if the given API is supported.
|
|
320
|
-
* @param {string} method - The HTTP method of the API.
|
|
321
|
-
* @param {string} path - The path of the API.
|
|
322
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
323
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
324
|
-
* @description The following APIs are supported:
|
|
325
|
-
* 1. only support Get/Post operation without auth property
|
|
326
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
327
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
328
|
-
* 4. request body + required parameters <= 1
|
|
329
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
330
|
-
* 6. only support request body with “application/json” content type
|
|
331
|
-
*/
|
|
332
|
-
static isSupportedApi(method, path, spec, options) {
|
|
333
|
-
var _a;
|
|
334
|
-
const pathObj = spec.paths[path];
|
|
335
|
-
method = method.toLocaleLowerCase();
|
|
336
|
-
if (pathObj) {
|
|
337
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
338
|
-
const securities = pathObj[method].security;
|
|
339
|
-
const isTeamsAi = options.projectType === exports.ProjectType.TeamsAi;
|
|
340
|
-
const isCopilot = options.projectType === exports.ProjectType.Copilot;
|
|
341
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
342
|
-
if (!isTeamsAi) {
|
|
343
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
344
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
const operationObject = pathObj[method];
|
|
349
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
350
|
-
return false;
|
|
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
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
359
|
-
if (Object.keys(responseJson).length === 0) {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
// Teams AI project doesn't care about request parameters/body
|
|
363
|
-
if (isTeamsAi) {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
let requestBodyParamResult = {
|
|
367
|
-
requiredNum: 0,
|
|
368
|
-
optionalNum: 0,
|
|
369
|
-
isValid: true,
|
|
370
|
-
};
|
|
371
|
-
if (requestJsonBody) {
|
|
372
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
373
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
377
|
-
}
|
|
378
|
-
if (!requestBodyParamResult.isValid) {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
382
|
-
if (!paramResult.isValid) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
// Copilot support arbitrary parameters
|
|
386
|
-
if (isCopilot) {
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
390
|
-
if (options.allowMultipleParameters &&
|
|
391
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
392
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
else if (requestBodyParamResult.requiredNum +
|
|
398
|
-
requestBodyParamResult.optionalNum +
|
|
399
|
-
paramResult.requiredNum +
|
|
400
|
-
paramResult.optionalNum ===
|
|
401
|
-
0) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
412
|
-
if (authSchemeArray.length === 0) {
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
416
|
-
// Currently we don't support multiple auth in one operation
|
|
417
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
for (const auths of authSchemeArray) {
|
|
421
|
-
if (auths.length === 1) {
|
|
422
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
423
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
424
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
425
|
-
return true;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
237
|
static isBearerTokenAuth(authScheme) {
|
|
433
238
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
434
239
|
}
|
|
@@ -436,10 +241,9 @@ class Utils {
|
|
|
436
241
|
return authScheme.type === "apiKey";
|
|
437
242
|
}
|
|
438
243
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return false;
|
|
244
|
+
return !!(authScheme.type === "oauth2" &&
|
|
245
|
+
authScheme.flows &&
|
|
246
|
+
authScheme.flows.authorizationCode);
|
|
443
247
|
}
|
|
444
248
|
static getAuthArray(securities, spec) {
|
|
445
249
|
var _a;
|
|
@@ -467,14 +271,17 @@ class Utils {
|
|
|
467
271
|
static updateFirstLetter(str) {
|
|
468
272
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
469
273
|
}
|
|
470
|
-
static getResponseJson(operationObject
|
|
274
|
+
static getResponseJson(operationObject) {
|
|
471
275
|
var _a, _b;
|
|
472
276
|
let json = {};
|
|
277
|
+
let multipleMediaType = false;
|
|
473
278
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
474
279
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
475
280
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
281
|
+
multipleMediaType = false;
|
|
476
282
|
json = responseObject.content["application/json"];
|
|
477
|
-
if (
|
|
283
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
284
|
+
multipleMediaType = true;
|
|
478
285
|
json = {};
|
|
479
286
|
}
|
|
480
287
|
else {
|
|
@@ -482,7 +289,7 @@ class Utils {
|
|
|
482
289
|
}
|
|
483
290
|
}
|
|
484
291
|
}
|
|
485
|
-
return json;
|
|
292
|
+
return { json, multipleMediaType };
|
|
486
293
|
}
|
|
487
294
|
static convertPathToCamelCase(path) {
|
|
488
295
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -502,10 +309,10 @@ class Utils {
|
|
|
502
309
|
return undefined;
|
|
503
310
|
}
|
|
504
311
|
}
|
|
505
|
-
static
|
|
312
|
+
static resolveEnv(str) {
|
|
506
313
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
507
|
-
let matches = placeHolderReg.exec(
|
|
508
|
-
let
|
|
314
|
+
let matches = placeHolderReg.exec(str);
|
|
315
|
+
let newStr = str;
|
|
509
316
|
while (matches != null) {
|
|
510
317
|
const envVar = matches[1];
|
|
511
318
|
const envVal = process.env[envVar];
|
|
@@ -513,17 +320,17 @@ class Utils {
|
|
|
513
320
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
514
321
|
}
|
|
515
322
|
else {
|
|
516
|
-
|
|
323
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
517
324
|
}
|
|
518
|
-
matches = placeHolderReg.exec(
|
|
325
|
+
matches = placeHolderReg.exec(str);
|
|
519
326
|
}
|
|
520
|
-
return
|
|
327
|
+
return newStr;
|
|
521
328
|
}
|
|
522
329
|
static checkServerUrl(servers) {
|
|
523
330
|
const errors = [];
|
|
524
331
|
let serverUrl;
|
|
525
332
|
try {
|
|
526
|
-
serverUrl = Utils.
|
|
333
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
527
334
|
}
|
|
528
335
|
catch (err) {
|
|
529
336
|
errors.push({
|
|
@@ -554,6 +361,7 @@ class Utils {
|
|
|
554
361
|
return errors;
|
|
555
362
|
}
|
|
556
363
|
static validateServer(spec, options) {
|
|
364
|
+
var _a;
|
|
557
365
|
const errors = [];
|
|
558
366
|
let hasTopLevelServers = false;
|
|
559
367
|
let hasPathLevelServers = false;
|
|
@@ -574,7 +382,7 @@ class Utils {
|
|
|
574
382
|
}
|
|
575
383
|
for (const method in methods) {
|
|
576
384
|
const operationObject = methods[method];
|
|
577
|
-
if (
|
|
385
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
578
386
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
579
387
|
hasOperationLevelServers = true;
|
|
580
388
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -701,13 +509,7 @@ class Utils {
|
|
|
701
509
|
}
|
|
702
510
|
}
|
|
703
511
|
const operationId = operationItem.operationId;
|
|
704
|
-
const parameters = [];
|
|
705
|
-
if (requiredParams.length !== 0) {
|
|
706
|
-
parameters.push(...requiredParams);
|
|
707
|
-
}
|
|
708
|
-
else {
|
|
709
|
-
parameters.push(optionalParams[0]);
|
|
710
|
-
}
|
|
512
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
711
513
|
const command = {
|
|
712
514
|
context: ["compose"],
|
|
713
515
|
type: "query",
|
|
@@ -716,142 +518,559 @@ class Utils {
|
|
|
716
518
|
parameters: parameters,
|
|
717
519
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
718
520
|
};
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
521
|
+
return command;
|
|
522
|
+
}
|
|
523
|
+
static format(str, ...args) {
|
|
524
|
+
let index = 0;
|
|
525
|
+
return str.replace(/%s/g, () => {
|
|
526
|
+
const arg = args[index++];
|
|
527
|
+
return arg !== undefined ? arg : "";
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
531
|
+
if (!authName) {
|
|
532
|
+
return "";
|
|
726
533
|
}
|
|
727
|
-
|
|
534
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
535
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
536
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
537
|
+
}
|
|
538
|
+
return safeRegistrationIdEnvName;
|
|
728
539
|
}
|
|
729
|
-
static
|
|
730
|
-
const
|
|
540
|
+
static getServerObject(spec, method, path) {
|
|
541
|
+
const pathObj = spec.paths[path];
|
|
542
|
+
const operationObject = pathObj[method];
|
|
543
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
544
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
545
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
546
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
547
|
+
return serverUrl;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Copyright (c) Microsoft Corporation.
|
|
552
|
+
class Validator {
|
|
553
|
+
listAPIs() {
|
|
554
|
+
var _a;
|
|
555
|
+
if (this.apiMap) {
|
|
556
|
+
return this.apiMap;
|
|
557
|
+
}
|
|
558
|
+
const paths = this.spec.paths;
|
|
731
559
|
const result = {};
|
|
732
560
|
for (const path in paths) {
|
|
733
561
|
const methods = paths[path];
|
|
734
562
|
for (const method in methods) {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
563
|
+
const operationObject = methods[method];
|
|
564
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
565
|
+
const validateResult = this.validateAPI(method, path);
|
|
566
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
567
|
+
operation: operationObject,
|
|
568
|
+
isValid: validateResult.isValid,
|
|
569
|
+
reason: validateResult.reason,
|
|
570
|
+
};
|
|
738
571
|
}
|
|
739
572
|
}
|
|
740
573
|
}
|
|
574
|
+
this.apiMap = result;
|
|
741
575
|
return result;
|
|
742
576
|
}
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
// Server validation
|
|
753
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
754
|
-
errors.push(...serverErrors);
|
|
755
|
-
// Remote reference not supported
|
|
756
|
-
const refPaths = parser.$refs.paths();
|
|
757
|
-
// refPaths [0] is the current spec file path
|
|
758
|
-
if (refPaths.length > 1) {
|
|
759
|
-
errors.push({
|
|
760
|
-
type: exports.ErrorType.RemoteRefNotSupported,
|
|
761
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
762
|
-
data: refPaths,
|
|
577
|
+
validateSpecVersion() {
|
|
578
|
+
const result = { errors: [], warnings: [] };
|
|
579
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
580
|
+
result.errors.push({
|
|
581
|
+
type: exports.ErrorType.SpecVersionNotSupported,
|
|
582
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
583
|
+
data: this.spec.openapi,
|
|
763
584
|
});
|
|
764
585
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
validateSpecServer() {
|
|
589
|
+
const result = { errors: [], warnings: [] };
|
|
590
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
591
|
+
result.errors.push(...serverErrors);
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecNoSupportAPI() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
597
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
598
|
+
if (validAPIs.length === 0) {
|
|
599
|
+
result.errors.push({
|
|
769
600
|
type: exports.ErrorType.NoSupportedApi,
|
|
770
601
|
content: ConstantString.NoSupportedApi,
|
|
771
602
|
});
|
|
772
603
|
}
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
validateSpecOperationId() {
|
|
607
|
+
const result = { errors: [], warnings: [] };
|
|
608
|
+
const apiMap = this.listAPIs();
|
|
773
609
|
// OperationId missing
|
|
774
610
|
const apisMissingOperationId = [];
|
|
775
611
|
for (const key in apiMap) {
|
|
776
|
-
const
|
|
777
|
-
if (!
|
|
612
|
+
const { operation } = apiMap[key];
|
|
613
|
+
if (!operation.operationId) {
|
|
778
614
|
apisMissingOperationId.push(key);
|
|
779
615
|
}
|
|
780
616
|
}
|
|
781
617
|
if (apisMissingOperationId.length > 0) {
|
|
782
|
-
warnings.push({
|
|
618
|
+
result.warnings.push({
|
|
783
619
|
type: exports.WarningType.OperationIdMissing,
|
|
784
620
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
785
621
|
data: apisMissingOperationId,
|
|
786
622
|
});
|
|
787
623
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
624
|
+
return result;
|
|
625
|
+
}
|
|
626
|
+
validateMethodAndPath(method, path) {
|
|
627
|
+
const result = { isValid: true, reason: [] };
|
|
628
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
629
|
+
result.isValid = false;
|
|
630
|
+
result.reason.push(exports.ErrorType.MethodNotAllowed);
|
|
631
|
+
return result;
|
|
791
632
|
}
|
|
792
|
-
|
|
793
|
-
|
|
633
|
+
const pathObj = this.spec.paths[path];
|
|
634
|
+
if (!pathObj || !pathObj[method]) {
|
|
635
|
+
result.isValid = false;
|
|
636
|
+
result.reason.push(exports.ErrorType.UrlPathNotExist);
|
|
637
|
+
return result;
|
|
794
638
|
}
|
|
795
|
-
return
|
|
796
|
-
status,
|
|
797
|
-
warnings,
|
|
798
|
-
errors,
|
|
799
|
-
};
|
|
639
|
+
return result;
|
|
800
640
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
641
|
+
validateResponse(method, path) {
|
|
642
|
+
const result = { isValid: true, reason: [] };
|
|
643
|
+
const operationObject = this.spec.paths[path][method];
|
|
644
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
645
|
+
// only support response body only contains “application/json” content type
|
|
646
|
+
if (multipleMediaType) {
|
|
647
|
+
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
|
|
648
|
+
}
|
|
649
|
+
else if (Object.keys(json).length === 0) {
|
|
650
|
+
// response body should not be empty
|
|
651
|
+
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
|
|
652
|
+
}
|
|
653
|
+
return result;
|
|
807
654
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
655
|
+
validateServer(method, path) {
|
|
656
|
+
const result = { isValid: true, reason: [] };
|
|
657
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
658
|
+
if (!serverObj) {
|
|
659
|
+
// should contain server URL
|
|
660
|
+
result.reason.push(exports.ErrorType.NoServerInformation);
|
|
811
661
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
662
|
+
else {
|
|
663
|
+
// server url should be absolute url with https protocol
|
|
664
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
665
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
815
666
|
}
|
|
816
|
-
return
|
|
667
|
+
return result;
|
|
817
668
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
669
|
+
validateAuth(method, path) {
|
|
670
|
+
const pathObj = this.spec.paths[path];
|
|
671
|
+
const operationObject = pathObj[method];
|
|
672
|
+
const securities = operationObject.security;
|
|
673
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
674
|
+
if (authSchemeArray.length === 0) {
|
|
675
|
+
return { isValid: true, reason: [] };
|
|
676
|
+
}
|
|
677
|
+
if (this.options.allowAPIKeyAuth ||
|
|
678
|
+
this.options.allowOauth2 ||
|
|
679
|
+
this.options.allowBearerTokenAuth) {
|
|
680
|
+
// Currently we don't support multiple auth in one operation
|
|
681
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
682
|
+
return {
|
|
683
|
+
isValid: false,
|
|
684
|
+
reason: [exports.ErrorType.MultipleAuthNotSupported],
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
for (const auths of authSchemeArray) {
|
|
688
|
+
if (auths.length === 1) {
|
|
689
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
690
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
691
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
692
|
+
return { isValid: true, reason: [] };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
|
|
698
|
+
}
|
|
699
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
700
|
+
var _a;
|
|
701
|
+
const paramResult = {
|
|
702
|
+
requiredNum: 0,
|
|
703
|
+
optionalNum: 0,
|
|
704
|
+
isValid: true,
|
|
705
|
+
reason: [],
|
|
706
|
+
};
|
|
707
|
+
if (Object.keys(schema).length === 0) {
|
|
708
|
+
return paramResult;
|
|
709
|
+
}
|
|
710
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
711
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
712
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
713
|
+
paramResult.isValid = false;
|
|
714
|
+
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
|
|
715
|
+
return paramResult;
|
|
716
|
+
}
|
|
717
|
+
if (schema.type === "string" ||
|
|
718
|
+
schema.type === "integer" ||
|
|
719
|
+
schema.type === "boolean" ||
|
|
720
|
+
schema.type === "number") {
|
|
721
|
+
if (isRequiredWithoutDefault) {
|
|
722
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if (schema.type === "object") {
|
|
729
|
+
const { properties } = schema;
|
|
730
|
+
for (const property in properties) {
|
|
731
|
+
let isRequired = false;
|
|
732
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
733
|
+
isRequired = true;
|
|
826
734
|
}
|
|
735
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
736
|
+
paramResult.requiredNum += result.requiredNum;
|
|
737
|
+
paramResult.optionalNum += result.optionalNum;
|
|
738
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
739
|
+
paramResult.reason.push(...result.reason);
|
|
827
740
|
}
|
|
828
741
|
}
|
|
829
|
-
|
|
742
|
+
else {
|
|
743
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
744
|
+
paramResult.isValid = false;
|
|
745
|
+
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return paramResult;
|
|
749
|
+
}
|
|
750
|
+
checkParamSchema(paramObject) {
|
|
751
|
+
const paramResult = {
|
|
752
|
+
requiredNum: 0,
|
|
753
|
+
optionalNum: 0,
|
|
754
|
+
isValid: true,
|
|
755
|
+
reason: [],
|
|
756
|
+
};
|
|
757
|
+
if (!paramObject) {
|
|
758
|
+
return paramResult;
|
|
759
|
+
}
|
|
760
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
761
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
762
|
+
const param = paramObject[i];
|
|
763
|
+
const schema = param.schema;
|
|
764
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
765
|
+
paramResult.isValid = false;
|
|
766
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
770
|
+
if (isCopilot) {
|
|
771
|
+
if (isRequiredWithoutDefault) {
|
|
772
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
776
|
+
}
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
780
|
+
if (isRequiredWithoutDefault) {
|
|
781
|
+
paramResult.isValid = false;
|
|
782
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
783
|
+
}
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (schema.type !== "boolean" &&
|
|
787
|
+
schema.type !== "string" &&
|
|
788
|
+
schema.type !== "number" &&
|
|
789
|
+
schema.type !== "integer") {
|
|
790
|
+
if (isRequiredWithoutDefault) {
|
|
791
|
+
paramResult.isValid = false;
|
|
792
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
793
|
+
}
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (param.in === "query" || param.in === "path") {
|
|
797
|
+
if (isRequiredWithoutDefault) {
|
|
798
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return paramResult;
|
|
806
|
+
}
|
|
807
|
+
hasNestedObjectInSchema(schema) {
|
|
808
|
+
if (schema.type === "object") {
|
|
809
|
+
for (const property in schema.properties) {
|
|
810
|
+
const nestedSchema = schema.properties[property];
|
|
811
|
+
if (nestedSchema.type === "object") {
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Copyright (c) Microsoft Corporation.
|
|
821
|
+
class CopilotValidator extends Validator {
|
|
822
|
+
constructor(spec, options) {
|
|
823
|
+
super();
|
|
824
|
+
this.projectType = exports.ProjectType.Copilot;
|
|
825
|
+
this.options = options;
|
|
826
|
+
this.spec = spec;
|
|
827
|
+
}
|
|
828
|
+
validateSpec() {
|
|
829
|
+
const result = { errors: [], warnings: [] };
|
|
830
|
+
// validate spec version
|
|
831
|
+
let validationResult = this.validateSpecVersion();
|
|
832
|
+
result.errors.push(...validationResult.errors);
|
|
833
|
+
// validate spec server
|
|
834
|
+
validationResult = this.validateSpecServer();
|
|
835
|
+
result.errors.push(...validationResult.errors);
|
|
836
|
+
// validate no supported API
|
|
837
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
838
|
+
result.errors.push(...validationResult.errors);
|
|
839
|
+
// validate operationId missing
|
|
840
|
+
validationResult = this.validateSpecOperationId();
|
|
841
|
+
result.warnings.push(...validationResult.warnings);
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
validateAPI(method, path) {
|
|
845
|
+
const result = { isValid: true, reason: [] };
|
|
846
|
+
method = method.toLocaleLowerCase();
|
|
847
|
+
// validate method and path
|
|
848
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
849
|
+
if (!methodAndPathResult.isValid) {
|
|
850
|
+
return methodAndPathResult;
|
|
851
|
+
}
|
|
852
|
+
const operationObject = this.spec.paths[path][method];
|
|
853
|
+
// validate auth
|
|
854
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
855
|
+
result.reason.push(...authCheckResult.reason);
|
|
856
|
+
// validate operationId
|
|
857
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
858
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
859
|
+
}
|
|
860
|
+
// validate server
|
|
861
|
+
const validateServerResult = this.validateServer(method, path);
|
|
862
|
+
result.reason.push(...validateServerResult.reason);
|
|
863
|
+
// validate response
|
|
864
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
865
|
+
result.reason.push(...validateResponseResult.reason);
|
|
866
|
+
// validate requestBody
|
|
867
|
+
const requestBody = operationObject.requestBody;
|
|
868
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
869
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
870
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
871
|
+
}
|
|
872
|
+
if (requestJsonBody) {
|
|
873
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
874
|
+
if (requestBodySchema.type !== "object") {
|
|
875
|
+
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
|
|
876
|
+
}
|
|
877
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
878
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
879
|
+
}
|
|
880
|
+
// validate parameters
|
|
881
|
+
const paramObject = operationObject.parameters;
|
|
882
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
883
|
+
result.reason.push(...paramResult.reason);
|
|
884
|
+
if (result.reason.length > 0) {
|
|
885
|
+
result.isValid = false;
|
|
886
|
+
}
|
|
887
|
+
return result;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Copyright (c) Microsoft Corporation.
|
|
892
|
+
class SMEValidator extends Validator {
|
|
893
|
+
constructor(spec, options) {
|
|
894
|
+
super();
|
|
895
|
+
this.projectType = exports.ProjectType.SME;
|
|
896
|
+
this.options = options;
|
|
897
|
+
this.spec = spec;
|
|
898
|
+
}
|
|
899
|
+
validateSpec() {
|
|
900
|
+
const result = { errors: [], warnings: [] };
|
|
901
|
+
// validate spec version
|
|
902
|
+
let validationResult = this.validateSpecVersion();
|
|
903
|
+
result.errors.push(...validationResult.errors);
|
|
904
|
+
// validate spec server
|
|
905
|
+
validationResult = this.validateSpecServer();
|
|
906
|
+
result.errors.push(...validationResult.errors);
|
|
907
|
+
// validate no supported API
|
|
908
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
909
|
+
result.errors.push(...validationResult.errors);
|
|
910
|
+
// validate operationId missing
|
|
911
|
+
validationResult = this.validateSpecOperationId();
|
|
912
|
+
result.warnings.push(...validationResult.warnings);
|
|
913
|
+
return result;
|
|
914
|
+
}
|
|
915
|
+
validateAPI(method, path) {
|
|
916
|
+
const result = { isValid: true, reason: [] };
|
|
917
|
+
method = method.toLocaleLowerCase();
|
|
918
|
+
// validate method and path
|
|
919
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
920
|
+
if (!methodAndPathResult.isValid) {
|
|
921
|
+
return methodAndPathResult;
|
|
922
|
+
}
|
|
923
|
+
const operationObject = this.spec.paths[path][method];
|
|
924
|
+
// validate auth
|
|
925
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
926
|
+
result.reason.push(...authCheckResult.reason);
|
|
927
|
+
// validate operationId
|
|
928
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
929
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
930
|
+
}
|
|
931
|
+
// validate server
|
|
932
|
+
const validateServerResult = this.validateServer(method, path);
|
|
933
|
+
result.reason.push(...validateServerResult.reason);
|
|
934
|
+
// validate response
|
|
935
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
936
|
+
result.reason.push(...validateResponseResult.reason);
|
|
937
|
+
let postBodyResult = {
|
|
938
|
+
requiredNum: 0,
|
|
939
|
+
optionalNum: 0,
|
|
940
|
+
isValid: true,
|
|
941
|
+
reason: [],
|
|
942
|
+
};
|
|
943
|
+
// validate requestBody
|
|
944
|
+
const requestBody = operationObject.requestBody;
|
|
945
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
946
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
947
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
948
|
+
}
|
|
949
|
+
if (requestJsonBody) {
|
|
950
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
951
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
952
|
+
result.reason.push(...postBodyResult.reason);
|
|
953
|
+
}
|
|
954
|
+
// validate parameters
|
|
955
|
+
const paramObject = operationObject.parameters;
|
|
956
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
957
|
+
result.reason.push(...paramResult.reason);
|
|
958
|
+
// validate total parameters count
|
|
959
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
960
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
961
|
+
result.reason.push(...paramCountResult.reason);
|
|
962
|
+
}
|
|
963
|
+
if (result.reason.length > 0) {
|
|
964
|
+
result.isValid = false;
|
|
965
|
+
}
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
969
|
+
const result = { isValid: true, reason: [] };
|
|
970
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
971
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
972
|
+
if (totalRequiredParams > 1) {
|
|
973
|
+
if (!this.options.allowMultipleParameters ||
|
|
974
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
975
|
+
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
else if (totalParams === 0) {
|
|
979
|
+
result.reason.push(exports.ErrorType.NoParameter);
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
985
|
+
|
|
986
|
+
// Copyright (c) Microsoft Corporation.
|
|
987
|
+
class TeamsAIValidator extends Validator {
|
|
988
|
+
constructor(spec, options) {
|
|
989
|
+
super();
|
|
990
|
+
this.projectType = exports.ProjectType.TeamsAi;
|
|
991
|
+
this.options = options;
|
|
992
|
+
this.spec = spec;
|
|
993
|
+
}
|
|
994
|
+
validateSpec() {
|
|
995
|
+
const result = { errors: [], warnings: [] };
|
|
996
|
+
// validate spec server
|
|
997
|
+
let validationResult = this.validateSpecServer();
|
|
998
|
+
result.errors.push(...validationResult.errors);
|
|
999
|
+
// validate no supported API
|
|
1000
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1001
|
+
result.errors.push(...validationResult.errors);
|
|
1002
|
+
return result;
|
|
1003
|
+
}
|
|
1004
|
+
validateAPI(method, path) {
|
|
1005
|
+
const result = { isValid: true, reason: [] };
|
|
1006
|
+
method = method.toLocaleLowerCase();
|
|
1007
|
+
// validate method and path
|
|
1008
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1009
|
+
if (!methodAndPathResult.isValid) {
|
|
1010
|
+
return methodAndPathResult;
|
|
1011
|
+
}
|
|
1012
|
+
const operationObject = this.spec.paths[path][method];
|
|
1013
|
+
// validate operationId
|
|
1014
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1015
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
1016
|
+
}
|
|
1017
|
+
// validate server
|
|
1018
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1019
|
+
result.reason.push(...validateServerResult.reason);
|
|
1020
|
+
if (result.reason.length > 0) {
|
|
1021
|
+
result.isValid = false;
|
|
1022
|
+
}
|
|
1023
|
+
return result;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
class ValidatorFactory {
|
|
1028
|
+
static create(spec, options) {
|
|
1029
|
+
var _a;
|
|
1030
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : exports.ProjectType.SME;
|
|
1031
|
+
switch (type) {
|
|
1032
|
+
case exports.ProjectType.SME:
|
|
1033
|
+
return new SMEValidator(spec, options);
|
|
1034
|
+
case exports.ProjectType.Copilot:
|
|
1035
|
+
return new CopilotValidator(spec, options);
|
|
1036
|
+
case exports.ProjectType.TeamsAi:
|
|
1037
|
+
return new TeamsAIValidator(spec, options);
|
|
1038
|
+
default:
|
|
1039
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1040
|
+
}
|
|
830
1041
|
}
|
|
831
1042
|
}
|
|
832
1043
|
|
|
833
1044
|
// Copyright (c) Microsoft Corporation.
|
|
834
1045
|
class SpecFilter {
|
|
835
1046
|
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1047
|
+
var _a;
|
|
836
1048
|
try {
|
|
837
1049
|
const newSpec = Object.assign({}, unResolveSpec);
|
|
838
1050
|
const newPaths = {};
|
|
839
1051
|
for (const filterItem of filter) {
|
|
840
1052
|
const [method, path] = filterItem.split(" ");
|
|
841
1053
|
const methodName = method.toLowerCase();
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1054
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1055
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1056
|
+
pathObj &&
|
|
1057
|
+
pathObj[methodName]) {
|
|
1058
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1059
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1060
|
+
if (!validateResult.isValid) {
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if (!newPaths[path]) {
|
|
1064
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1065
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1066
|
+
delete newPaths[path][m];
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1070
|
+
// Add the operationId if missing
|
|
1071
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1072
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
849
1073
|
}
|
|
850
|
-
}
|
|
851
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
852
|
-
// Add the operationId if missing
|
|
853
|
-
if (!newPaths[path][methodName].operationId) {
|
|
854
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
855
1074
|
}
|
|
856
1075
|
}
|
|
857
1076
|
newSpec.paths = newPaths;
|
|
@@ -874,9 +1093,10 @@ class ManifestUpdater {
|
|
|
874
1093
|
pluginFile: apiPluginRelativePath,
|
|
875
1094
|
},
|
|
876
1095
|
];
|
|
1096
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
877
1097
|
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
878
1098
|
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
879
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
|
|
1099
|
+
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
880
1100
|
return [manifest, apiPlugin];
|
|
881
1101
|
});
|
|
882
1102
|
}
|
|
@@ -901,7 +1121,7 @@ class ManifestUpdater {
|
|
|
901
1121
|
}
|
|
902
1122
|
return parameter;
|
|
903
1123
|
}
|
|
904
|
-
static generatePluginManifestSchema(spec, specRelativePath, options) {
|
|
1124
|
+
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
905
1125
|
var _a, _b, _c;
|
|
906
1126
|
const functions = [];
|
|
907
1127
|
const functionNames = [];
|
|
@@ -966,7 +1186,7 @@ class ManifestUpdater {
|
|
|
966
1186
|
}
|
|
967
1187
|
const apiPlugin = {
|
|
968
1188
|
schema_version: "v2",
|
|
969
|
-
name_for_human:
|
|
1189
|
+
name_for_human: appName,
|
|
970
1190
|
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
971
1191
|
functions: functions,
|
|
972
1192
|
runtimes: [
|
|
@@ -1053,16 +1273,26 @@ class ManifestUpdater {
|
|
|
1053
1273
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1054
1274
|
const operationItem = operations[method];
|
|
1055
1275
|
if (operationItem) {
|
|
1056
|
-
const
|
|
1276
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1277
|
+
if (command.parameters &&
|
|
1278
|
+
command.parameters.length >= 1 &&
|
|
1279
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1280
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1281
|
+
}
|
|
1282
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1283
|
+
command.parameters = [command.parameters[0]];
|
|
1284
|
+
warnings.push({
|
|
1285
|
+
type: exports.WarningType.OperationOnlyContainsOptionalParam,
|
|
1286
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1287
|
+
data: command.id,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1057
1290
|
if (adaptiveCardFolder) {
|
|
1058
1291
|
const adaptiveCardPath = path__default['default'].join(adaptiveCardFolder, command.id + ".json");
|
|
1059
1292
|
command.apiResponseRenderingTemplateFile = (yield fs__default['default'].pathExists(adaptiveCardPath))
|
|
1060
1293
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1061
1294
|
: "";
|
|
1062
1295
|
}
|
|
1063
|
-
if (warning) {
|
|
1064
|
-
warnings.push(warning);
|
|
1065
|
-
}
|
|
1066
1296
|
commands.push(command);
|
|
1067
1297
|
}
|
|
1068
1298
|
}
|
|
@@ -1077,13 +1307,22 @@ class ManifestUpdater {
|
|
|
1077
1307
|
const relativePath = path__default['default'].relative(path__default['default'].dirname(from), to);
|
|
1078
1308
|
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
|
|
1079
1309
|
}
|
|
1310
|
+
static removeEnvs(str) {
|
|
1311
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1312
|
+
const matches = placeHolderReg.exec(str);
|
|
1313
|
+
let newStr = str;
|
|
1314
|
+
if (matches != null) {
|
|
1315
|
+
newStr = newStr.replace(matches[0], "");
|
|
1316
|
+
}
|
|
1317
|
+
return newStr;
|
|
1318
|
+
}
|
|
1080
1319
|
}
|
|
1081
1320
|
|
|
1082
1321
|
// Copyright (c) Microsoft Corporation.
|
|
1083
1322
|
class AdaptiveCardGenerator {
|
|
1084
1323
|
static generateAdaptiveCard(operationItem) {
|
|
1085
1324
|
try {
|
|
1086
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1325
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1087
1326
|
let cardBody = [];
|
|
1088
1327
|
let schema = json.schema;
|
|
1089
1328
|
let jsonPath = "$";
|
|
@@ -1372,6 +1611,8 @@ class SpecParser {
|
|
|
1372
1611
|
errors: [{ type: exports.ErrorType.SpecNotValid, content: e.toString() }],
|
|
1373
1612
|
};
|
|
1374
1613
|
}
|
|
1614
|
+
const errors = [];
|
|
1615
|
+
const warnings = [];
|
|
1375
1616
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1376
1617
|
return {
|
|
1377
1618
|
status: exports.ValidationStatus.Error,
|
|
@@ -1381,7 +1622,38 @@ class SpecParser {
|
|
|
1381
1622
|
],
|
|
1382
1623
|
};
|
|
1383
1624
|
}
|
|
1384
|
-
|
|
1625
|
+
// Remote reference not supported
|
|
1626
|
+
const refPaths = this.parser.$refs.paths();
|
|
1627
|
+
// refPaths [0] is the current spec file path
|
|
1628
|
+
if (refPaths.length > 1) {
|
|
1629
|
+
errors.push({
|
|
1630
|
+
type: exports.ErrorType.RemoteRefNotSupported,
|
|
1631
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1632
|
+
data: refPaths,
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1636
|
+
warnings.push({
|
|
1637
|
+
type: exports.WarningType.ConvertSwaggerToOpenAPI,
|
|
1638
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
const validator = this.getValidator(this.spec);
|
|
1642
|
+
const validationResult = validator.validateSpec();
|
|
1643
|
+
warnings.push(...validationResult.warnings);
|
|
1644
|
+
errors.push(...validationResult.errors);
|
|
1645
|
+
let status = exports.ValidationStatus.Valid;
|
|
1646
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1647
|
+
status = exports.ValidationStatus.Warning;
|
|
1648
|
+
}
|
|
1649
|
+
else if (errors.length > 0) {
|
|
1650
|
+
status = exports.ValidationStatus.Error;
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
status: status,
|
|
1654
|
+
warnings: warnings,
|
|
1655
|
+
errors: errors,
|
|
1656
|
+
};
|
|
1385
1657
|
}
|
|
1386
1658
|
catch (err) {
|
|
1387
1659
|
throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
|
|
@@ -1405,45 +1677,40 @@ class SpecParser {
|
|
|
1405
1677
|
try {
|
|
1406
1678
|
yield this.loadSpec();
|
|
1407
1679
|
const spec = this.spec;
|
|
1408
|
-
const apiMap = this.
|
|
1680
|
+
const apiMap = this.getAPIs(spec);
|
|
1409
1681
|
const result = {
|
|
1410
|
-
|
|
1682
|
+
APIs: [],
|
|
1411
1683
|
allAPICount: 0,
|
|
1412
1684
|
validAPICount: 0,
|
|
1413
1685
|
};
|
|
1414
1686
|
for (const apiKey in apiMap) {
|
|
1687
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1688
|
+
const [method, path] = apiKey.split(" ");
|
|
1689
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1415
1690
|
const apiResult = {
|
|
1416
|
-
api:
|
|
1691
|
+
api: apiKey,
|
|
1417
1692
|
server: "",
|
|
1418
|
-
operationId:
|
|
1693
|
+
operationId: operationId,
|
|
1694
|
+
isValid: isValid,
|
|
1695
|
+
reason: reason,
|
|
1419
1696
|
};
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
if (!operationId) {
|
|
1432
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1433
|
-
}
|
|
1434
|
-
apiResult.operationId = operationId;
|
|
1435
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1436
|
-
for (const auths of authArray) {
|
|
1437
|
-
if (auths.length === 1) {
|
|
1438
|
-
apiResult.auth = auths[0];
|
|
1439
|
-
break;
|
|
1697
|
+
if (isValid) {
|
|
1698
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1699
|
+
if (serverObj) {
|
|
1700
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1701
|
+
}
|
|
1702
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1703
|
+
for (const auths of authArray) {
|
|
1704
|
+
if (auths.length === 1) {
|
|
1705
|
+
apiResult.auth = auths[0];
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1440
1708
|
}
|
|
1441
1709
|
}
|
|
1442
|
-
apiResult
|
|
1443
|
-
result.validAPIs.push(apiResult);
|
|
1710
|
+
result.APIs.push(apiResult);
|
|
1444
1711
|
}
|
|
1445
|
-
result.allAPICount =
|
|
1446
|
-
result.validAPICount = result.
|
|
1712
|
+
result.allAPICount = result.APIs.length;
|
|
1713
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1447
1714
|
return result;
|
|
1448
1715
|
}
|
|
1449
1716
|
catch (err) {
|
|
@@ -1541,15 +1808,18 @@ class SpecParser {
|
|
|
1541
1808
|
const newSpecs = yield this.getFilteredSpecs(filter, signal);
|
|
1542
1809
|
const newUnResolvedSpec = newSpecs[0];
|
|
1543
1810
|
const newSpec = newSpecs[1];
|
|
1544
|
-
const authSet = new Set();
|
|
1545
1811
|
let hasMultipleAuth = false;
|
|
1812
|
+
let authInfo = undefined;
|
|
1546
1813
|
for (const url in newSpec.paths) {
|
|
1547
1814
|
for (const method in newSpec.paths[url]) {
|
|
1548
1815
|
const operation = newSpec.paths[url][method];
|
|
1549
1816
|
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1550
1817
|
if (authArray && authArray.length > 0) {
|
|
1551
|
-
|
|
1552
|
-
if (
|
|
1818
|
+
const currentAuth = authArray[0][0];
|
|
1819
|
+
if (!authInfo) {
|
|
1820
|
+
authInfo = authArray[0][0];
|
|
1821
|
+
}
|
|
1822
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
1553
1823
|
hasMultipleAuth = true;
|
|
1554
1824
|
break;
|
|
1555
1825
|
}
|
|
@@ -1596,7 +1866,6 @@ class SpecParser {
|
|
|
1596
1866
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1597
1867
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1598
1868
|
}
|
|
1599
|
-
const authInfo = Array.from(authSet)[0];
|
|
1600
1869
|
const [updatedManifest, warnings] = yield ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1601
1870
|
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1602
1871
|
result.warnings.push(...warnings);
|
|
@@ -1625,13 +1894,19 @@ class SpecParser {
|
|
|
1625
1894
|
}
|
|
1626
1895
|
});
|
|
1627
1896
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1897
|
+
getAPIs(spec) {
|
|
1898
|
+
const validator = this.getValidator(spec);
|
|
1899
|
+
const apiMap = validator.listAPIs();
|
|
1900
|
+
this.apiMap = apiMap;
|
|
1901
|
+
return apiMap;
|
|
1902
|
+
}
|
|
1903
|
+
getValidator(spec) {
|
|
1904
|
+
if (this.validator) {
|
|
1905
|
+
return this.validator;
|
|
1631
1906
|
}
|
|
1632
|
-
const
|
|
1633
|
-
this.
|
|
1634
|
-
return
|
|
1907
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1908
|
+
this.validator = validator;
|
|
1909
|
+
return validator;
|
|
1635
1910
|
}
|
|
1636
1911
|
}
|
|
1637
1912
|
|