@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.ad8f60cf1.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 +606 -333
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1147 -719
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +606 -333
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1092 -660
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +3 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +65 -1
- package/dist/src/manifestUpdater.d.ts +5 -2
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +4 -2
- package/dist/src/utils.d.ts +9 -28
- package/package.json +3 -3
package/dist/index.esm5.js
CHANGED
|
@@ -55,6 +55,21 @@ var ErrorType;
|
|
|
55
55
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
56
56
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
57
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";
|
|
58
73
|
ErrorType["Cancelled"] = "cancelled";
|
|
59
74
|
ErrorType["Unknown"] = "unknown";
|
|
60
75
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -102,7 +117,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
102
117
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
103
118
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
104
119
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
105
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
120
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
106
121
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
107
122
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
108
123
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -122,9 +137,9 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
122
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
123
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
124
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
125
141
|
ConstantString.ContainerType = "Container";
|
|
126
142
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
127
|
-
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
128
143
|
ConstantString.ResponseCodeFor20X = [
|
|
129
144
|
"200",
|
|
130
145
|
"201",
|
|
@@ -185,7 +200,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
185
200
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
186
201
|
ConstantString.CommandTitleMaxLens = 32;
|
|
187
202
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
188
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
203
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
204
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
189
205
|
|
|
190
206
|
// Copyright (c) Microsoft Corporation.
|
|
191
207
|
class Utils {
|
|
@@ -200,221 +216,9 @@ class Utils {
|
|
|
200
216
|
}
|
|
201
217
|
return false;
|
|
202
218
|
}
|
|
203
|
-
static checkParameters(paramObject, isCopilot) {
|
|
204
|
-
const paramResult = {
|
|
205
|
-
requiredNum: 0,
|
|
206
|
-
optionalNum: 0,
|
|
207
|
-
isValid: true,
|
|
208
|
-
};
|
|
209
|
-
if (!paramObject) {
|
|
210
|
-
return paramResult;
|
|
211
|
-
}
|
|
212
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
213
|
-
const param = paramObject[i];
|
|
214
|
-
const schema = param.schema;
|
|
215
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
216
|
-
paramResult.isValid = false;
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
220
|
-
if (isCopilot) {
|
|
221
|
-
if (isRequiredWithoutDefault) {
|
|
222
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
226
|
-
}
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
230
|
-
if (isRequiredWithoutDefault) {
|
|
231
|
-
paramResult.isValid = false;
|
|
232
|
-
}
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (schema.type !== "boolean" &&
|
|
236
|
-
schema.type !== "string" &&
|
|
237
|
-
schema.type !== "number" &&
|
|
238
|
-
schema.type !== "integer") {
|
|
239
|
-
if (isRequiredWithoutDefault) {
|
|
240
|
-
paramResult.isValid = false;
|
|
241
|
-
}
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (param.in === "query" || param.in === "path") {
|
|
245
|
-
if (isRequiredWithoutDefault) {
|
|
246
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return paramResult;
|
|
254
|
-
}
|
|
255
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
256
|
-
var _a;
|
|
257
|
-
const paramResult = {
|
|
258
|
-
requiredNum: 0,
|
|
259
|
-
optionalNum: 0,
|
|
260
|
-
isValid: true,
|
|
261
|
-
};
|
|
262
|
-
if (Object.keys(schema).length === 0) {
|
|
263
|
-
return paramResult;
|
|
264
|
-
}
|
|
265
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
266
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
267
|
-
paramResult.isValid = false;
|
|
268
|
-
return paramResult;
|
|
269
|
-
}
|
|
270
|
-
if (schema.type === "string" ||
|
|
271
|
-
schema.type === "integer" ||
|
|
272
|
-
schema.type === "boolean" ||
|
|
273
|
-
schema.type === "number") {
|
|
274
|
-
if (isRequiredWithoutDefault) {
|
|
275
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else if (schema.type === "object") {
|
|
282
|
-
const { properties } = schema;
|
|
283
|
-
for (const property in properties) {
|
|
284
|
-
let isRequired = false;
|
|
285
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
286
|
-
isRequired = true;
|
|
287
|
-
}
|
|
288
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
289
|
-
paramResult.requiredNum += result.requiredNum;
|
|
290
|
-
paramResult.optionalNum += result.optionalNum;
|
|
291
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
296
|
-
paramResult.isValid = false;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return paramResult;
|
|
300
|
-
}
|
|
301
219
|
static containMultipleMediaTypes(bodyObject) {
|
|
302
220
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
303
221
|
}
|
|
304
|
-
/**
|
|
305
|
-
* Checks if the given API is supported.
|
|
306
|
-
* @param {string} method - The HTTP method of the API.
|
|
307
|
-
* @param {string} path - The path of the API.
|
|
308
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
309
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
310
|
-
* @description The following APIs are supported:
|
|
311
|
-
* 1. only support Get/Post operation without auth property
|
|
312
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
313
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
314
|
-
* 4. request body + required parameters <= 1
|
|
315
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
316
|
-
* 6. only support request body with “application/json” content type
|
|
317
|
-
*/
|
|
318
|
-
static isSupportedApi(method, path, spec, options) {
|
|
319
|
-
var _a;
|
|
320
|
-
const pathObj = spec.paths[path];
|
|
321
|
-
method = method.toLocaleLowerCase();
|
|
322
|
-
if (pathObj) {
|
|
323
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
324
|
-
const securities = pathObj[method].security;
|
|
325
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
326
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
327
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
328
|
-
if (!isTeamsAi) {
|
|
329
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
330
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
const operationObject = pathObj[method];
|
|
335
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
const paramObject = operationObject.parameters;
|
|
339
|
-
const requestBody = operationObject.requestBody;
|
|
340
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
341
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
345
|
-
if (Object.keys(responseJson).length === 0) {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
// Teams AI project doesn't care about request parameters/body
|
|
349
|
-
if (isTeamsAi) {
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
let requestBodyParamResult = {
|
|
353
|
-
requiredNum: 0,
|
|
354
|
-
optionalNum: 0,
|
|
355
|
-
isValid: true,
|
|
356
|
-
};
|
|
357
|
-
if (requestJsonBody) {
|
|
358
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
359
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
363
|
-
}
|
|
364
|
-
if (!requestBodyParamResult.isValid) {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
368
|
-
if (!paramResult.isValid) {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
// Copilot support arbitrary parameters
|
|
372
|
-
if (isCopilot) {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
376
|
-
if (options.allowMultipleParameters &&
|
|
377
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
378
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
else if (requestBodyParamResult.requiredNum +
|
|
384
|
-
requestBodyParamResult.optionalNum +
|
|
385
|
-
paramResult.requiredNum +
|
|
386
|
-
paramResult.optionalNum ===
|
|
387
|
-
0) {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
398
|
-
if (authSchemeArray.length === 0) {
|
|
399
|
-
return true;
|
|
400
|
-
}
|
|
401
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
402
|
-
// Currently we don't support multiple auth in one operation
|
|
403
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
for (const auths of authSchemeArray) {
|
|
407
|
-
if (auths.length === 1) {
|
|
408
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
409
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
410
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
222
|
static isBearerTokenAuth(authScheme) {
|
|
419
223
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
420
224
|
}
|
|
@@ -422,18 +226,18 @@ class Utils {
|
|
|
422
226
|
return authScheme.type === "apiKey";
|
|
423
227
|
}
|
|
424
228
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return false;
|
|
229
|
+
return !!(authScheme.type === "oauth2" &&
|
|
230
|
+
authScheme.flows &&
|
|
231
|
+
authScheme.flows.authorizationCode);
|
|
429
232
|
}
|
|
430
233
|
static getAuthArray(securities, spec) {
|
|
431
234
|
var _a;
|
|
432
235
|
const result = [];
|
|
433
236
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
237
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
238
|
+
if (securitiesArr && securitySchemas) {
|
|
239
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
240
|
+
const security = securitiesArr[i];
|
|
437
241
|
const authArray = [];
|
|
438
242
|
for (const name in security) {
|
|
439
243
|
const auth = securitySchemas[name];
|
|
@@ -450,17 +254,39 @@ class Utils {
|
|
|
450
254
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
451
255
|
return result;
|
|
452
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
|
+
}
|
|
453
276
|
static updateFirstLetter(str) {
|
|
454
277
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
455
278
|
}
|
|
456
|
-
static getResponseJson(operationObject
|
|
279
|
+
static getResponseJson(operationObject) {
|
|
457
280
|
var _a, _b;
|
|
458
281
|
let json = {};
|
|
282
|
+
let multipleMediaType = false;
|
|
459
283
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
460
284
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
461
285
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
286
|
+
multipleMediaType = false;
|
|
462
287
|
json = responseObject.content["application/json"];
|
|
463
|
-
if (
|
|
288
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
289
|
+
multipleMediaType = true;
|
|
464
290
|
json = {};
|
|
465
291
|
}
|
|
466
292
|
else {
|
|
@@ -468,7 +294,7 @@ class Utils {
|
|
|
468
294
|
}
|
|
469
295
|
}
|
|
470
296
|
}
|
|
471
|
-
return json;
|
|
297
|
+
return { json, multipleMediaType };
|
|
472
298
|
}
|
|
473
299
|
static convertPathToCamelCase(path) {
|
|
474
300
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -488,10 +314,10 @@ class Utils {
|
|
|
488
314
|
return undefined;
|
|
489
315
|
}
|
|
490
316
|
}
|
|
491
|
-
static
|
|
317
|
+
static resolveEnv(str) {
|
|
492
318
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
493
|
-
let matches = placeHolderReg.exec(
|
|
494
|
-
let
|
|
319
|
+
let matches = placeHolderReg.exec(str);
|
|
320
|
+
let newStr = str;
|
|
495
321
|
while (matches != null) {
|
|
496
322
|
const envVar = matches[1];
|
|
497
323
|
const envVal = process.env[envVar];
|
|
@@ -499,17 +325,17 @@ class Utils {
|
|
|
499
325
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
500
326
|
}
|
|
501
327
|
else {
|
|
502
|
-
|
|
328
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
503
329
|
}
|
|
504
|
-
matches = placeHolderReg.exec(
|
|
330
|
+
matches = placeHolderReg.exec(str);
|
|
505
331
|
}
|
|
506
|
-
return
|
|
332
|
+
return newStr;
|
|
507
333
|
}
|
|
508
334
|
static checkServerUrl(servers) {
|
|
509
335
|
const errors = [];
|
|
510
336
|
let serverUrl;
|
|
511
337
|
try {
|
|
512
|
-
serverUrl = Utils.
|
|
338
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
513
339
|
}
|
|
514
340
|
catch (err) {
|
|
515
341
|
errors.push({
|
|
@@ -540,6 +366,7 @@ class Utils {
|
|
|
540
366
|
return errors;
|
|
541
367
|
}
|
|
542
368
|
static validateServer(spec, options) {
|
|
369
|
+
var _a;
|
|
543
370
|
const errors = [];
|
|
544
371
|
let hasTopLevelServers = false;
|
|
545
372
|
let hasPathLevelServers = false;
|
|
@@ -560,7 +387,7 @@ class Utils {
|
|
|
560
387
|
}
|
|
561
388
|
for (const method in methods) {
|
|
562
389
|
const operationObject = methods[method];
|
|
563
|
-
if (
|
|
390
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
564
391
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
565
392
|
hasOperationLevelServers = true;
|
|
566
393
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -687,13 +514,7 @@ class Utils {
|
|
|
687
514
|
}
|
|
688
515
|
}
|
|
689
516
|
const operationId = operationItem.operationId;
|
|
690
|
-
const parameters = [];
|
|
691
|
-
if (requiredParams.length !== 0) {
|
|
692
|
-
parameters.push(...requiredParams);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
parameters.push(optionalParams[0]);
|
|
696
|
-
}
|
|
517
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
697
518
|
const command = {
|
|
698
519
|
context: ["compose"],
|
|
699
520
|
type: "query",
|
|
@@ -702,117 +523,534 @@ class Utils {
|
|
|
702
523
|
parameters: parameters,
|
|
703
524
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
704
525
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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 "";
|
|
538
|
+
}
|
|
539
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
540
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
541
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
712
542
|
}
|
|
713
|
-
return
|
|
543
|
+
return safeRegistrationIdEnvName;
|
|
714
544
|
}
|
|
715
|
-
static
|
|
716
|
-
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;
|
|
717
564
|
const result = {};
|
|
718
565
|
for (const path in paths) {
|
|
719
566
|
const methods = paths[path];
|
|
720
567
|
for (const method in methods) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
+
};
|
|
724
576
|
}
|
|
725
577
|
}
|
|
726
578
|
}
|
|
579
|
+
this.apiMap = result;
|
|
727
580
|
return result;
|
|
728
581
|
}
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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,
|
|
736
589
|
});
|
|
737
590
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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({
|
|
755
611
|
type: ErrorType.NoSupportedApi,
|
|
756
612
|
content: ConstantString.NoSupportedApi,
|
|
613
|
+
data,
|
|
757
614
|
});
|
|
758
615
|
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
validateSpecOperationId() {
|
|
619
|
+
const result = { errors: [], warnings: [] };
|
|
620
|
+
const apiMap = this.listAPIs();
|
|
759
621
|
// OperationId missing
|
|
760
622
|
const apisMissingOperationId = [];
|
|
761
623
|
for (const key in apiMap) {
|
|
762
|
-
const
|
|
763
|
-
if (!
|
|
624
|
+
const { operation } = apiMap[key];
|
|
625
|
+
if (!operation.operationId) {
|
|
764
626
|
apisMissingOperationId.push(key);
|
|
765
627
|
}
|
|
766
628
|
}
|
|
767
629
|
if (apisMissingOperationId.length > 0) {
|
|
768
|
-
warnings.push({
|
|
630
|
+
result.warnings.push({
|
|
769
631
|
type: WarningType.OperationIdMissing,
|
|
770
632
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
771
633
|
data: apisMissingOperationId,
|
|
772
634
|
});
|
|
773
635
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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;
|
|
777
644
|
}
|
|
778
|
-
|
|
779
|
-
|
|
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;
|
|
780
650
|
}
|
|
781
|
-
return
|
|
782
|
-
status,
|
|
783
|
-
warnings,
|
|
784
|
-
errors,
|
|
785
|
-
};
|
|
651
|
+
return result;
|
|
786
652
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return result;
|
|
793
668
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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);
|
|
797
675
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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));
|
|
801
680
|
}
|
|
802
|
-
return
|
|
681
|
+
return result;
|
|
803
682
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
+
}
|
|
812
708
|
}
|
|
813
709
|
}
|
|
814
710
|
}
|
|
815
|
-
return
|
|
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: [],
|
|
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;
|
|
763
|
+
}
|
|
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;
|
|
820
|
+
}
|
|
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
|
+
}
|
|
829
|
+
}
|
|
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}`);
|
|
1053
|
+
}
|
|
816
1054
|
}
|
|
817
1055
|
}
|
|
818
1056
|
|
|
@@ -835,7 +1073,11 @@ class SpecParser {
|
|
|
835
1073
|
allowBearerTokenAuth: false,
|
|
836
1074
|
allowOauth2: false,
|
|
837
1075
|
allowMethods: ["get", "post"],
|
|
1076
|
+
allowConversationStarters: false,
|
|
1077
|
+
allowResponseSemantics: false,
|
|
1078
|
+
allowConfirmation: false,
|
|
838
1079
|
projectType: ProjectType.SME,
|
|
1080
|
+
isGptPlugin: false,
|
|
839
1081
|
};
|
|
840
1082
|
this.pathOrSpec = pathOrDoc;
|
|
841
1083
|
this.parser = new SwaggerParser();
|
|
@@ -851,11 +1093,7 @@ class SpecParser {
|
|
|
851
1093
|
try {
|
|
852
1094
|
try {
|
|
853
1095
|
yield this.loadSpec();
|
|
854
|
-
yield this.parser.validate(this.spec
|
|
855
|
-
validate: {
|
|
856
|
-
schema: false,
|
|
857
|
-
},
|
|
858
|
-
});
|
|
1096
|
+
yield this.parser.validate(this.spec);
|
|
859
1097
|
}
|
|
860
1098
|
catch (e) {
|
|
861
1099
|
return {
|
|
@@ -864,16 +1102,46 @@ class SpecParser {
|
|
|
864
1102
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
865
1103
|
};
|
|
866
1104
|
}
|
|
1105
|
+
const errors = [];
|
|
1106
|
+
const warnings = [];
|
|
867
1107
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
868
1108
|
return {
|
|
869
1109
|
status: ValidationStatus.Error,
|
|
870
1110
|
warnings: [],
|
|
871
1111
|
errors: [
|
|
872
|
-
{
|
|
1112
|
+
{
|
|
1113
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1114
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1115
|
+
},
|
|
873
1116
|
],
|
|
874
1117
|
};
|
|
875
1118
|
}
|
|
876
|
-
|
|
1119
|
+
// Remote reference not supported
|
|
1120
|
+
const refPaths = this.parser.$refs.paths();
|
|
1121
|
+
// refPaths [0] is the current spec file path
|
|
1122
|
+
if (refPaths.length > 1) {
|
|
1123
|
+
errors.push({
|
|
1124
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1125
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1126
|
+
data: refPaths,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
const validator = this.getValidator(this.spec);
|
|
1130
|
+
const validationResult = validator.validateSpec();
|
|
1131
|
+
warnings.push(...validationResult.warnings);
|
|
1132
|
+
errors.push(...validationResult.errors);
|
|
1133
|
+
let status = ValidationStatus.Valid;
|
|
1134
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1135
|
+
status = ValidationStatus.Warning;
|
|
1136
|
+
}
|
|
1137
|
+
else if (errors.length > 0) {
|
|
1138
|
+
status = ValidationStatus.Error;
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
status: status,
|
|
1142
|
+
warnings: warnings,
|
|
1143
|
+
errors: errors,
|
|
1144
|
+
};
|
|
877
1145
|
}
|
|
878
1146
|
catch (err) {
|
|
879
1147
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -884,17 +1152,20 @@ class SpecParser {
|
|
|
884
1152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
885
1153
|
try {
|
|
886
1154
|
yield this.loadSpec();
|
|
887
|
-
const apiMap = this.
|
|
1155
|
+
const apiMap = this.getAPIs(this.spec);
|
|
888
1156
|
const apiInfos = [];
|
|
889
1157
|
for (const key in apiMap) {
|
|
890
|
-
const
|
|
1158
|
+
const { operation, isValid } = apiMap[key];
|
|
1159
|
+
if (!isValid) {
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
891
1162
|
const [method, path] = key.split(" ");
|
|
892
|
-
const operationId =
|
|
1163
|
+
const operationId = operation.operationId;
|
|
893
1164
|
// In Browser environment, this api is by default not support api without operationId
|
|
894
1165
|
if (!operationId) {
|
|
895
1166
|
continue;
|
|
896
1167
|
}
|
|
897
|
-
const
|
|
1168
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
898
1169
|
const apiInfo = {
|
|
899
1170
|
method: method,
|
|
900
1171
|
path: path,
|
|
@@ -903,9 +1174,6 @@ class SpecParser {
|
|
|
903
1174
|
parameters: command.parameters,
|
|
904
1175
|
description: command.description,
|
|
905
1176
|
};
|
|
906
|
-
if (warning) {
|
|
907
|
-
apiInfo.warning = warning;
|
|
908
|
-
}
|
|
909
1177
|
apiInfos.push(apiInfo);
|
|
910
1178
|
}
|
|
911
1179
|
return apiInfos;
|
|
@@ -975,13 +1243,18 @@ class SpecParser {
|
|
|
975
1243
|
}
|
|
976
1244
|
});
|
|
977
1245
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1246
|
+
getAPIs(spec) {
|
|
1247
|
+
const validator = this.getValidator(spec);
|
|
1248
|
+
const apiMap = validator.listAPIs();
|
|
1249
|
+
return apiMap;
|
|
1250
|
+
}
|
|
1251
|
+
getValidator(spec) {
|
|
1252
|
+
if (this.validator) {
|
|
1253
|
+
return this.validator;
|
|
981
1254
|
}
|
|
982
|
-
const
|
|
983
|
-
this.
|
|
984
|
-
return
|
|
1255
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1256
|
+
this.validator = validator;
|
|
1257
|
+
return validator;
|
|
985
1258
|
}
|
|
986
1259
|
}
|
|
987
1260
|
|
|
@@ -989,7 +1262,7 @@ class SpecParser {
|
|
|
989
1262
|
class AdaptiveCardGenerator {
|
|
990
1263
|
static generateAdaptiveCard(operationItem) {
|
|
991
1264
|
try {
|
|
992
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1265
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
993
1266
|
let cardBody = [];
|
|
994
1267
|
let schema = json.schema;
|
|
995
1268
|
let jsonPath = "$";
|