@microsoft/m365-spec-parser 0.1.1-alpha.d1a09e202.0 → 0.1.1-alpha.ded43fb2d.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 +607 -318
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1143 -684
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +607 -318
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1086 -623
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +70 -15
- package/dist/src/manifestUpdater.d.ts +5 -2
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +12 -30
- 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";
|
|
@@ -120,6 +137,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
120
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
121
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
122
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
123
141
|
ConstantString.ContainerType = "Container";
|
|
124
142
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
125
143
|
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
@@ -183,7 +201,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
183
201
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
184
202
|
ConstantString.CommandTitleMaxLens = 32;
|
|
185
203
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
186
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
204
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
205
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
187
206
|
|
|
188
207
|
// Copyright (c) Microsoft Corporation.
|
|
189
208
|
class Utils {
|
|
@@ -198,221 +217,9 @@ class Utils {
|
|
|
198
217
|
}
|
|
199
218
|
return false;
|
|
200
219
|
}
|
|
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
220
|
static containMultipleMediaTypes(bodyObject) {
|
|
300
221
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
301
222
|
}
|
|
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
223
|
static isBearerTokenAuth(authScheme) {
|
|
417
224
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
418
225
|
}
|
|
@@ -420,10 +227,9 @@ class Utils {
|
|
|
420
227
|
return authScheme.type === "apiKey";
|
|
421
228
|
}
|
|
422
229
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return false;
|
|
230
|
+
return !!(authScheme.type === "oauth2" &&
|
|
231
|
+
authScheme.flows &&
|
|
232
|
+
authScheme.flows.authorizationCode);
|
|
427
233
|
}
|
|
428
234
|
static getAuthArray(securities, spec) {
|
|
429
235
|
var _a;
|
|
@@ -448,17 +254,39 @@ class Utils {
|
|
|
448
254
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
449
255
|
return result;
|
|
450
256
|
}
|
|
257
|
+
static getAuthInfo(spec) {
|
|
258
|
+
let authInfo = undefined;
|
|
259
|
+
for (const url in spec.paths) {
|
|
260
|
+
for (const method in spec.paths[url]) {
|
|
261
|
+
const operation = spec.paths[url][method];
|
|
262
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
263
|
+
if (authArray && authArray.length > 0) {
|
|
264
|
+
const currentAuth = authArray[0][0];
|
|
265
|
+
if (!authInfo) {
|
|
266
|
+
authInfo = authArray[0][0];
|
|
267
|
+
}
|
|
268
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
269
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return authInfo;
|
|
275
|
+
}
|
|
451
276
|
static updateFirstLetter(str) {
|
|
452
277
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
453
278
|
}
|
|
454
|
-
static getResponseJson(operationObject
|
|
279
|
+
static getResponseJson(operationObject) {
|
|
455
280
|
var _a, _b;
|
|
456
281
|
let json = {};
|
|
282
|
+
let multipleMediaType = false;
|
|
457
283
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
458
284
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
459
285
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
286
|
+
multipleMediaType = false;
|
|
460
287
|
json = responseObject.content["application/json"];
|
|
461
|
-
if (
|
|
288
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
289
|
+
multipleMediaType = true;
|
|
462
290
|
json = {};
|
|
463
291
|
}
|
|
464
292
|
else {
|
|
@@ -466,7 +294,7 @@ class Utils {
|
|
|
466
294
|
}
|
|
467
295
|
}
|
|
468
296
|
}
|
|
469
|
-
return json;
|
|
297
|
+
return { json, multipleMediaType };
|
|
470
298
|
}
|
|
471
299
|
static convertPathToCamelCase(path) {
|
|
472
300
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -486,10 +314,10 @@ class Utils {
|
|
|
486
314
|
return undefined;
|
|
487
315
|
}
|
|
488
316
|
}
|
|
489
|
-
static
|
|
317
|
+
static resolveEnv(str) {
|
|
490
318
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
491
|
-
let matches = placeHolderReg.exec(
|
|
492
|
-
let
|
|
319
|
+
let matches = placeHolderReg.exec(str);
|
|
320
|
+
let newStr = str;
|
|
493
321
|
while (matches != null) {
|
|
494
322
|
const envVar = matches[1];
|
|
495
323
|
const envVal = process.env[envVar];
|
|
@@ -497,17 +325,17 @@ class Utils {
|
|
|
497
325
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
498
326
|
}
|
|
499
327
|
else {
|
|
500
|
-
|
|
328
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
501
329
|
}
|
|
502
|
-
matches = placeHolderReg.exec(
|
|
330
|
+
matches = placeHolderReg.exec(str);
|
|
503
331
|
}
|
|
504
|
-
return
|
|
332
|
+
return newStr;
|
|
505
333
|
}
|
|
506
334
|
static checkServerUrl(servers) {
|
|
507
335
|
const errors = [];
|
|
508
336
|
let serverUrl;
|
|
509
337
|
try {
|
|
510
|
-
serverUrl = Utils.
|
|
338
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
511
339
|
}
|
|
512
340
|
catch (err) {
|
|
513
341
|
errors.push({
|
|
@@ -538,6 +366,7 @@ class Utils {
|
|
|
538
366
|
return errors;
|
|
539
367
|
}
|
|
540
368
|
static validateServer(spec, options) {
|
|
369
|
+
var _a;
|
|
541
370
|
const errors = [];
|
|
542
371
|
let hasTopLevelServers = false;
|
|
543
372
|
let hasPathLevelServers = false;
|
|
@@ -558,7 +387,7 @@ class Utils {
|
|
|
558
387
|
}
|
|
559
388
|
for (const method in methods) {
|
|
560
389
|
const operationObject = methods[method];
|
|
561
|
-
if (
|
|
390
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
562
391
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
563
392
|
hasOperationLevelServers = true;
|
|
564
393
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -601,6 +430,7 @@ class Utils {
|
|
|
601
430
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
602
431
|
}
|
|
603
432
|
if (isRequired && schema.default === undefined) {
|
|
433
|
+
parameter.isRequired = true;
|
|
604
434
|
requiredParams.push(parameter);
|
|
605
435
|
}
|
|
606
436
|
else {
|
|
@@ -664,6 +494,7 @@ class Utils {
|
|
|
664
494
|
}
|
|
665
495
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
666
496
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
497
|
+
parameter.isRequired = true;
|
|
667
498
|
requiredParams.push(parameter);
|
|
668
499
|
}
|
|
669
500
|
else {
|
|
@@ -683,13 +514,7 @@ class Utils {
|
|
|
683
514
|
}
|
|
684
515
|
}
|
|
685
516
|
const operationId = operationItem.operationId;
|
|
686
|
-
const parameters = [];
|
|
687
|
-
if (requiredParams.length !== 0) {
|
|
688
|
-
parameters.push(...requiredParams);
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
parameters.push(optionalParams[0]);
|
|
692
|
-
}
|
|
517
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
693
518
|
const command = {
|
|
694
519
|
context: ["compose"],
|
|
695
520
|
type: "query",
|
|
@@ -698,104 +523,534 @@ class Utils {
|
|
|
698
523
|
parameters: parameters,
|
|
699
524
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
700
525
|
};
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
526
|
+
return command;
|
|
527
|
+
}
|
|
528
|
+
static format(str, ...args) {
|
|
529
|
+
let index = 0;
|
|
530
|
+
return str.replace(/%s/g, () => {
|
|
531
|
+
const arg = args[index++];
|
|
532
|
+
return arg !== undefined ? arg : "";
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
536
|
+
if (!authName) {
|
|
537
|
+
return "";
|
|
708
538
|
}
|
|
709
|
-
|
|
539
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
540
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
541
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
542
|
+
}
|
|
543
|
+
return safeRegistrationIdEnvName;
|
|
710
544
|
}
|
|
711
|
-
static
|
|
712
|
-
const
|
|
545
|
+
static getServerObject(spec, method, path) {
|
|
546
|
+
const pathObj = spec.paths[path];
|
|
547
|
+
const operationObject = pathObj[method];
|
|
548
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
549
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
550
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
551
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
552
|
+
return serverUrl;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Copyright (c) Microsoft Corporation.
|
|
557
|
+
class Validator {
|
|
558
|
+
listAPIs() {
|
|
559
|
+
var _a;
|
|
560
|
+
if (this.apiMap) {
|
|
561
|
+
return this.apiMap;
|
|
562
|
+
}
|
|
563
|
+
const paths = this.spec.paths;
|
|
713
564
|
const result = {};
|
|
714
565
|
for (const path in paths) {
|
|
715
566
|
const methods = paths[path];
|
|
716
567
|
for (const method in methods) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
568
|
+
const operationObject = methods[method];
|
|
569
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
570
|
+
const validateResult = this.validateAPI(method, path);
|
|
571
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
572
|
+
operation: operationObject,
|
|
573
|
+
isValid: validateResult.isValid,
|
|
574
|
+
reason: validateResult.reason,
|
|
575
|
+
};
|
|
720
576
|
}
|
|
721
577
|
}
|
|
722
578
|
}
|
|
579
|
+
this.apiMap = result;
|
|
723
580
|
return result;
|
|
724
581
|
}
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
582
|
+
validateSpecVersion() {
|
|
583
|
+
const result = { errors: [], warnings: [] };
|
|
584
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
585
|
+
result.errors.push({
|
|
586
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
587
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
588
|
+
data: this.spec.openapi,
|
|
732
589
|
});
|
|
733
590
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
validateSpecServer() {
|
|
594
|
+
const result = { errors: [], warnings: [] };
|
|
595
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
596
|
+
result.errors.push(...serverErrors);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
validateSpecNoSupportAPI() {
|
|
600
|
+
const result = { errors: [], warnings: [] };
|
|
601
|
+
const apiMap = this.listAPIs();
|
|
602
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
603
|
+
if (validAPIs.length === 0) {
|
|
604
|
+
const data = [];
|
|
605
|
+
for (const key in apiMap) {
|
|
606
|
+
const { reason } = apiMap[key];
|
|
607
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
608
|
+
data.push(apiInvalidReason);
|
|
609
|
+
}
|
|
610
|
+
result.errors.push({
|
|
751
611
|
type: ErrorType.NoSupportedApi,
|
|
752
612
|
content: ConstantString.NoSupportedApi,
|
|
613
|
+
data,
|
|
753
614
|
});
|
|
754
615
|
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
validateSpecOperationId() {
|
|
619
|
+
const result = { errors: [], warnings: [] };
|
|
620
|
+
const apiMap = this.listAPIs();
|
|
755
621
|
// OperationId missing
|
|
756
622
|
const apisMissingOperationId = [];
|
|
757
623
|
for (const key in apiMap) {
|
|
758
|
-
const
|
|
759
|
-
if (!
|
|
624
|
+
const { operation } = apiMap[key];
|
|
625
|
+
if (!operation.operationId) {
|
|
760
626
|
apisMissingOperationId.push(key);
|
|
761
627
|
}
|
|
762
628
|
}
|
|
763
629
|
if (apisMissingOperationId.length > 0) {
|
|
764
|
-
warnings.push({
|
|
630
|
+
result.warnings.push({
|
|
765
631
|
type: WarningType.OperationIdMissing,
|
|
766
632
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
767
633
|
data: apisMissingOperationId,
|
|
768
634
|
});
|
|
769
635
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
validateMethodAndPath(method, path) {
|
|
639
|
+
const result = { isValid: true, reason: [] };
|
|
640
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
641
|
+
result.isValid = false;
|
|
642
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
const pathObj = this.spec.paths[path];
|
|
646
|
+
if (!pathObj || !pathObj[method]) {
|
|
647
|
+
result.isValid = false;
|
|
648
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
649
|
+
return result;
|
|
773
650
|
}
|
|
774
|
-
|
|
775
|
-
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
validateResponse(method, path) {
|
|
654
|
+
const result = { isValid: true, reason: [] };
|
|
655
|
+
const operationObject = this.spec.paths[path][method];
|
|
656
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
657
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
658
|
+
// only support response body only contains “application/json” content type
|
|
659
|
+
if (multipleMediaType) {
|
|
660
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
661
|
+
}
|
|
662
|
+
else if (Object.keys(json).length === 0) {
|
|
663
|
+
// response body should not be empty
|
|
664
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
665
|
+
}
|
|
776
666
|
}
|
|
777
|
-
return
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
validateServer(method, path) {
|
|
670
|
+
const result = { isValid: true, reason: [] };
|
|
671
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
672
|
+
if (!serverObj) {
|
|
673
|
+
// should contain server URL
|
|
674
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
// server url should be absolute url with https protocol
|
|
678
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
679
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
validateAuth(method, path) {
|
|
684
|
+
const pathObj = this.spec.paths[path];
|
|
685
|
+
const operationObject = pathObj[method];
|
|
686
|
+
const securities = operationObject.security;
|
|
687
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
688
|
+
if (authSchemeArray.length === 0) {
|
|
689
|
+
return { isValid: true, reason: [] };
|
|
690
|
+
}
|
|
691
|
+
if (this.options.allowAPIKeyAuth ||
|
|
692
|
+
this.options.allowOauth2 ||
|
|
693
|
+
this.options.allowBearerTokenAuth) {
|
|
694
|
+
// Currently we don't support multiple auth in one operation
|
|
695
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
696
|
+
return {
|
|
697
|
+
isValid: false,
|
|
698
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
for (const auths of authSchemeArray) {
|
|
702
|
+
if (auths.length === 1) {
|
|
703
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
704
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
705
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
706
|
+
return { isValid: true, reason: [] };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
712
|
+
}
|
|
713
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
714
|
+
var _a;
|
|
715
|
+
const paramResult = {
|
|
716
|
+
requiredNum: 0,
|
|
717
|
+
optionalNum: 0,
|
|
718
|
+
isValid: true,
|
|
719
|
+
reason: [],
|
|
781
720
|
};
|
|
721
|
+
if (Object.keys(schema).length === 0) {
|
|
722
|
+
return paramResult;
|
|
723
|
+
}
|
|
724
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
725
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
726
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
727
|
+
paramResult.isValid = false;
|
|
728
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
729
|
+
return paramResult;
|
|
730
|
+
}
|
|
731
|
+
if (schema.type === "string" ||
|
|
732
|
+
schema.type === "integer" ||
|
|
733
|
+
schema.type === "boolean" ||
|
|
734
|
+
schema.type === "number") {
|
|
735
|
+
if (isRequiredWithoutDefault) {
|
|
736
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
else if (schema.type === "object") {
|
|
743
|
+
const { properties } = schema;
|
|
744
|
+
for (const property in properties) {
|
|
745
|
+
let isRequired = false;
|
|
746
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
747
|
+
isRequired = true;
|
|
748
|
+
}
|
|
749
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
750
|
+
paramResult.requiredNum += result.requiredNum;
|
|
751
|
+
paramResult.optionalNum += result.optionalNum;
|
|
752
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
753
|
+
paramResult.reason.push(...result.reason);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return paramResult;
|
|
782
763
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
764
|
+
checkParamSchema(paramObject) {
|
|
765
|
+
const paramResult = {
|
|
766
|
+
requiredNum: 0,
|
|
767
|
+
optionalNum: 0,
|
|
768
|
+
isValid: true,
|
|
769
|
+
reason: [],
|
|
770
|
+
};
|
|
771
|
+
if (!paramObject) {
|
|
772
|
+
return paramResult;
|
|
773
|
+
}
|
|
774
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
775
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
776
|
+
const param = paramObject[i];
|
|
777
|
+
const schema = param.schema;
|
|
778
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
779
|
+
paramResult.isValid = false;
|
|
780
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
784
|
+
if (isCopilot) {
|
|
785
|
+
if (isRequiredWithoutDefault) {
|
|
786
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
790
|
+
}
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
794
|
+
if (isRequiredWithoutDefault) {
|
|
795
|
+
paramResult.isValid = false;
|
|
796
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
797
|
+
}
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (schema.type !== "boolean" &&
|
|
801
|
+
schema.type !== "string" &&
|
|
802
|
+
schema.type !== "number" &&
|
|
803
|
+
schema.type !== "integer") {
|
|
804
|
+
if (isRequiredWithoutDefault) {
|
|
805
|
+
paramResult.isValid = false;
|
|
806
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (param.in === "query" || param.in === "path") {
|
|
811
|
+
if (isRequiredWithoutDefault) {
|
|
812
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return paramResult;
|
|
789
820
|
}
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
821
|
+
hasNestedObjectInSchema(schema) {
|
|
822
|
+
if (schema.type === "object") {
|
|
823
|
+
for (const property in schema.properties) {
|
|
824
|
+
const nestedSchema = schema.properties[property];
|
|
825
|
+
if (nestedSchema.type === "object") {
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
793
829
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Copyright (c) Microsoft Corporation.
|
|
835
|
+
class CopilotValidator extends Validator {
|
|
836
|
+
constructor(spec, options) {
|
|
837
|
+
super();
|
|
838
|
+
this.projectType = ProjectType.Copilot;
|
|
839
|
+
this.options = options;
|
|
840
|
+
this.spec = spec;
|
|
841
|
+
}
|
|
842
|
+
validateSpec() {
|
|
843
|
+
const result = { errors: [], warnings: [] };
|
|
844
|
+
// validate spec version
|
|
845
|
+
let validationResult = this.validateSpecVersion();
|
|
846
|
+
result.errors.push(...validationResult.errors);
|
|
847
|
+
// validate spec server
|
|
848
|
+
validationResult = this.validateSpecServer();
|
|
849
|
+
result.errors.push(...validationResult.errors);
|
|
850
|
+
// validate no supported API
|
|
851
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
852
|
+
result.errors.push(...validationResult.errors);
|
|
853
|
+
// validate operationId missing
|
|
854
|
+
validationResult = this.validateSpecOperationId();
|
|
855
|
+
result.warnings.push(...validationResult.warnings);
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
validateAPI(method, path) {
|
|
859
|
+
const result = { isValid: true, reason: [] };
|
|
860
|
+
method = method.toLocaleLowerCase();
|
|
861
|
+
// validate method and path
|
|
862
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
863
|
+
if (!methodAndPathResult.isValid) {
|
|
864
|
+
return methodAndPathResult;
|
|
865
|
+
}
|
|
866
|
+
const operationObject = this.spec.paths[path][method];
|
|
867
|
+
// validate auth
|
|
868
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
869
|
+
result.reason.push(...authCheckResult.reason);
|
|
870
|
+
// validate operationId
|
|
871
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
872
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
873
|
+
}
|
|
874
|
+
// validate server
|
|
875
|
+
const validateServerResult = this.validateServer(method, path);
|
|
876
|
+
result.reason.push(...validateServerResult.reason);
|
|
877
|
+
// validate response
|
|
878
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
879
|
+
result.reason.push(...validateResponseResult.reason);
|
|
880
|
+
// validate requestBody
|
|
881
|
+
const requestBody = operationObject.requestBody;
|
|
882
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
883
|
+
if (requestJsonBody) {
|
|
884
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
885
|
+
if (requestBodySchema.type !== "object") {
|
|
886
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
887
|
+
}
|
|
888
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
889
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
890
|
+
}
|
|
891
|
+
// validate parameters
|
|
892
|
+
const paramObject = operationObject.parameters;
|
|
893
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
894
|
+
result.reason.push(...paramResult.reason);
|
|
895
|
+
if (result.reason.length > 0) {
|
|
896
|
+
result.isValid = false;
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Copyright (c) Microsoft Corporation.
|
|
903
|
+
class SMEValidator extends Validator {
|
|
904
|
+
constructor(spec, options) {
|
|
905
|
+
super();
|
|
906
|
+
this.projectType = ProjectType.SME;
|
|
907
|
+
this.options = options;
|
|
908
|
+
this.spec = spec;
|
|
909
|
+
}
|
|
910
|
+
validateSpec() {
|
|
911
|
+
const result = { errors: [], warnings: [] };
|
|
912
|
+
// validate spec version
|
|
913
|
+
let validationResult = this.validateSpecVersion();
|
|
914
|
+
result.errors.push(...validationResult.errors);
|
|
915
|
+
// validate spec server
|
|
916
|
+
validationResult = this.validateSpecServer();
|
|
917
|
+
result.errors.push(...validationResult.errors);
|
|
918
|
+
// validate no supported API
|
|
919
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
920
|
+
result.errors.push(...validationResult.errors);
|
|
921
|
+
// validate operationId missing
|
|
922
|
+
if (this.options.allowMissingId) {
|
|
923
|
+
validationResult = this.validateSpecOperationId();
|
|
924
|
+
result.warnings.push(...validationResult.warnings);
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
validateAPI(method, path) {
|
|
929
|
+
const result = { isValid: true, reason: [] };
|
|
930
|
+
method = method.toLocaleLowerCase();
|
|
931
|
+
// validate method and path
|
|
932
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
933
|
+
if (!methodAndPathResult.isValid) {
|
|
934
|
+
return methodAndPathResult;
|
|
935
|
+
}
|
|
936
|
+
const operationObject = this.spec.paths[path][method];
|
|
937
|
+
// validate auth
|
|
938
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
939
|
+
result.reason.push(...authCheckResult.reason);
|
|
940
|
+
// validate operationId
|
|
941
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
942
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
943
|
+
}
|
|
944
|
+
// validate server
|
|
945
|
+
const validateServerResult = this.validateServer(method, path);
|
|
946
|
+
result.reason.push(...validateServerResult.reason);
|
|
947
|
+
// validate response
|
|
948
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
949
|
+
result.reason.push(...validateResponseResult.reason);
|
|
950
|
+
let postBodyResult = {
|
|
951
|
+
requiredNum: 0,
|
|
952
|
+
optionalNum: 0,
|
|
953
|
+
isValid: true,
|
|
954
|
+
reason: [],
|
|
955
|
+
};
|
|
956
|
+
// validate requestBody
|
|
957
|
+
const requestBody = operationObject.requestBody;
|
|
958
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
959
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
960
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
961
|
+
}
|
|
962
|
+
if (requestJsonBody) {
|
|
963
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
964
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
965
|
+
result.reason.push(...postBodyResult.reason);
|
|
966
|
+
}
|
|
967
|
+
// validate parameters
|
|
968
|
+
const paramObject = operationObject.parameters;
|
|
969
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
970
|
+
result.reason.push(...paramResult.reason);
|
|
971
|
+
// validate total parameters count
|
|
972
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
973
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
974
|
+
result.reason.push(...paramCountResult.reason);
|
|
975
|
+
}
|
|
976
|
+
if (result.reason.length > 0) {
|
|
977
|
+
result.isValid = false;
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
}
|
|
981
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
982
|
+
const result = { isValid: true, reason: [] };
|
|
983
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
984
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
985
|
+
if (totalRequiredParams > 1) {
|
|
986
|
+
if (!this.options.allowMultipleParameters ||
|
|
987
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
988
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
else if (totalParams === 0) {
|
|
992
|
+
result.reason.push(ErrorType.NoParameter);
|
|
993
|
+
}
|
|
994
|
+
return result;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
998
|
+
|
|
999
|
+
// Copyright (c) Microsoft Corporation.
|
|
1000
|
+
class TeamsAIValidator extends Validator {
|
|
1001
|
+
constructor(spec, options) {
|
|
1002
|
+
super();
|
|
1003
|
+
this.projectType = ProjectType.TeamsAi;
|
|
1004
|
+
this.options = options;
|
|
1005
|
+
this.spec = spec;
|
|
1006
|
+
}
|
|
1007
|
+
validateSpec() {
|
|
1008
|
+
const result = { errors: [], warnings: [] };
|
|
1009
|
+
// validate spec server
|
|
1010
|
+
let validationResult = this.validateSpecServer();
|
|
1011
|
+
result.errors.push(...validationResult.errors);
|
|
1012
|
+
// validate no supported API
|
|
1013
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1014
|
+
result.errors.push(...validationResult.errors);
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
1017
|
+
validateAPI(method, path) {
|
|
1018
|
+
const result = { isValid: true, reason: [] };
|
|
1019
|
+
method = method.toLocaleLowerCase();
|
|
1020
|
+
// validate method and path
|
|
1021
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1022
|
+
if (!methodAndPathResult.isValid) {
|
|
1023
|
+
return methodAndPathResult;
|
|
1024
|
+
}
|
|
1025
|
+
const operationObject = this.spec.paths[path][method];
|
|
1026
|
+
// validate operationId
|
|
1027
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1028
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1029
|
+
}
|
|
1030
|
+
// validate server
|
|
1031
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1032
|
+
result.reason.push(...validateServerResult.reason);
|
|
1033
|
+
if (result.reason.length > 0) {
|
|
1034
|
+
result.isValid = false;
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
class ValidatorFactory {
|
|
1041
|
+
static create(spec, options) {
|
|
1042
|
+
var _a;
|
|
1043
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1044
|
+
switch (type) {
|
|
1045
|
+
case ProjectType.SME:
|
|
1046
|
+
return new SMEValidator(spec, options);
|
|
1047
|
+
case ProjectType.Copilot:
|
|
1048
|
+
return new CopilotValidator(spec, options);
|
|
1049
|
+
case ProjectType.TeamsAi:
|
|
1050
|
+
return new TeamsAIValidator(spec, options);
|
|
1051
|
+
default:
|
|
1052
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
797
1053
|
}
|
|
798
|
-
return safeRegistrationIdEnvName;
|
|
799
1054
|
}
|
|
800
1055
|
}
|
|
801
1056
|
|
|
@@ -818,6 +1073,9 @@ class SpecParser {
|
|
|
818
1073
|
allowBearerTokenAuth: false,
|
|
819
1074
|
allowOauth2: false,
|
|
820
1075
|
allowMethods: ["get", "post"],
|
|
1076
|
+
allowConversationStarters: false,
|
|
1077
|
+
allowResponseSemantics: false,
|
|
1078
|
+
allowConfirmation: false,
|
|
821
1079
|
projectType: ProjectType.SME,
|
|
822
1080
|
};
|
|
823
1081
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -834,11 +1092,7 @@ class SpecParser {
|
|
|
834
1092
|
try {
|
|
835
1093
|
try {
|
|
836
1094
|
yield this.loadSpec();
|
|
837
|
-
yield this.parser.validate(this.spec
|
|
838
|
-
validate: {
|
|
839
|
-
schema: false,
|
|
840
|
-
},
|
|
841
|
-
});
|
|
1095
|
+
yield this.parser.validate(this.spec);
|
|
842
1096
|
}
|
|
843
1097
|
catch (e) {
|
|
844
1098
|
return {
|
|
@@ -847,16 +1101,46 @@ class SpecParser {
|
|
|
847
1101
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
848
1102
|
};
|
|
849
1103
|
}
|
|
1104
|
+
const errors = [];
|
|
1105
|
+
const warnings = [];
|
|
850
1106
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
851
1107
|
return {
|
|
852
1108
|
status: ValidationStatus.Error,
|
|
853
1109
|
warnings: [],
|
|
854
1110
|
errors: [
|
|
855
|
-
{
|
|
1111
|
+
{
|
|
1112
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1113
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1114
|
+
},
|
|
856
1115
|
],
|
|
857
1116
|
};
|
|
858
1117
|
}
|
|
859
|
-
|
|
1118
|
+
// Remote reference not supported
|
|
1119
|
+
const refPaths = this.parser.$refs.paths();
|
|
1120
|
+
// refPaths [0] is the current spec file path
|
|
1121
|
+
if (refPaths.length > 1) {
|
|
1122
|
+
errors.push({
|
|
1123
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1124
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1125
|
+
data: refPaths,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const validator = this.getValidator(this.spec);
|
|
1129
|
+
const validationResult = validator.validateSpec();
|
|
1130
|
+
warnings.push(...validationResult.warnings);
|
|
1131
|
+
errors.push(...validationResult.errors);
|
|
1132
|
+
let status = ValidationStatus.Valid;
|
|
1133
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1134
|
+
status = ValidationStatus.Warning;
|
|
1135
|
+
}
|
|
1136
|
+
else if (errors.length > 0) {
|
|
1137
|
+
status = ValidationStatus.Error;
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
status: status,
|
|
1141
|
+
warnings: warnings,
|
|
1142
|
+
errors: errors,
|
|
1143
|
+
};
|
|
860
1144
|
}
|
|
861
1145
|
catch (err) {
|
|
862
1146
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -867,17 +1151,20 @@ class SpecParser {
|
|
|
867
1151
|
return __awaiter(this, void 0, void 0, function* () {
|
|
868
1152
|
try {
|
|
869
1153
|
yield this.loadSpec();
|
|
870
|
-
const apiMap = this.
|
|
1154
|
+
const apiMap = this.getAPIs(this.spec);
|
|
871
1155
|
const apiInfos = [];
|
|
872
1156
|
for (const key in apiMap) {
|
|
873
|
-
const
|
|
1157
|
+
const { operation, isValid } = apiMap[key];
|
|
1158
|
+
if (!isValid) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
874
1161
|
const [method, path] = key.split(" ");
|
|
875
|
-
const operationId =
|
|
1162
|
+
const operationId = operation.operationId;
|
|
876
1163
|
// In Browser environment, this api is by default not support api without operationId
|
|
877
1164
|
if (!operationId) {
|
|
878
1165
|
continue;
|
|
879
1166
|
}
|
|
880
|
-
const
|
|
1167
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
881
1168
|
const apiInfo = {
|
|
882
1169
|
method: method,
|
|
883
1170
|
path: path,
|
|
@@ -886,9 +1173,6 @@ class SpecParser {
|
|
|
886
1173
|
parameters: command.parameters,
|
|
887
1174
|
description: command.description,
|
|
888
1175
|
};
|
|
889
|
-
if (warning) {
|
|
890
|
-
apiInfo.warning = warning;
|
|
891
|
-
}
|
|
892
1176
|
apiInfos.push(apiInfo);
|
|
893
1177
|
}
|
|
894
1178
|
return apiInfos;
|
|
@@ -958,13 +1242,18 @@ class SpecParser {
|
|
|
958
1242
|
}
|
|
959
1243
|
});
|
|
960
1244
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1245
|
+
getAPIs(spec) {
|
|
1246
|
+
const validator = this.getValidator(spec);
|
|
1247
|
+
const apiMap = validator.listAPIs();
|
|
1248
|
+
return apiMap;
|
|
1249
|
+
}
|
|
1250
|
+
getValidator(spec) {
|
|
1251
|
+
if (this.validator) {
|
|
1252
|
+
return this.validator;
|
|
964
1253
|
}
|
|
965
|
-
const
|
|
966
|
-
this.
|
|
967
|
-
return
|
|
1254
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1255
|
+
this.validator = validator;
|
|
1256
|
+
return validator;
|
|
968
1257
|
}
|
|
969
1258
|
}
|
|
970
1259
|
|
|
@@ -972,7 +1261,7 @@ class SpecParser {
|
|
|
972
1261
|
class AdaptiveCardGenerator {
|
|
973
1262
|
static generateAdaptiveCard(operationItem) {
|
|
974
1263
|
try {
|
|
975
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1264
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
976
1265
|
let cardBody = [];
|
|
977
1266
|
let schema = json.schema;
|
|
978
1267
|
let jsonPath = "$";
|