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