@microsoft/m365-spec-parser 0.1.1-alpha.784d957e7.0 → 0.1.1-alpha.79c26ed38.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 +583 -328
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1046 -677
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +583 -328
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +990 -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,537 @@ 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
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
615
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
763
619
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
620
|
+
validateServer(method, path) {
|
|
621
|
+
const result = { isValid: true, reason: [] };
|
|
622
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
623
|
+
if (!serverObj) {
|
|
624
|
+
// should contain server URL
|
|
625
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
767
626
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
627
|
+
else {
|
|
628
|
+
// server url should be absolute url with https protocol
|
|
629
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
630
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
771
631
|
}
|
|
772
|
-
return
|
|
632
|
+
return result;
|
|
773
633
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
634
|
+
validateAuth(method, path) {
|
|
635
|
+
const pathObj = this.spec.paths[path];
|
|
636
|
+
const operationObject = pathObj[method];
|
|
637
|
+
const securities = operationObject.security;
|
|
638
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
639
|
+
if (authSchemeArray.length === 0) {
|
|
640
|
+
return { isValid: true, reason: [] };
|
|
641
|
+
}
|
|
642
|
+
if (this.options.allowAPIKeyAuth ||
|
|
643
|
+
this.options.allowOauth2 ||
|
|
644
|
+
this.options.allowBearerTokenAuth) {
|
|
645
|
+
// Currently we don't support multiple auth in one operation
|
|
646
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
647
|
+
return {
|
|
648
|
+
isValid: false,
|
|
649
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
for (const auths of authSchemeArray) {
|
|
653
|
+
if (auths.length === 1) {
|
|
654
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
655
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
656
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
657
|
+
return { isValid: true, reason: [] };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
663
|
+
}
|
|
664
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
665
|
+
var _a;
|
|
666
|
+
const paramResult = {
|
|
667
|
+
requiredNum: 0,
|
|
668
|
+
optionalNum: 0,
|
|
669
|
+
isValid: true,
|
|
670
|
+
reason: [],
|
|
671
|
+
};
|
|
672
|
+
if (Object.keys(schema).length === 0) {
|
|
673
|
+
return paramResult;
|
|
674
|
+
}
|
|
675
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
676
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
677
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
678
|
+
paramResult.isValid = false;
|
|
679
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
680
|
+
return paramResult;
|
|
681
|
+
}
|
|
682
|
+
if (schema.type === "string" ||
|
|
683
|
+
schema.type === "integer" ||
|
|
684
|
+
schema.type === "boolean" ||
|
|
685
|
+
schema.type === "number") {
|
|
686
|
+
if (isRequiredWithoutDefault) {
|
|
687
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
else if (schema.type === "object") {
|
|
694
|
+
const { properties } = schema;
|
|
695
|
+
for (const property in properties) {
|
|
696
|
+
let isRequired = false;
|
|
697
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
698
|
+
isRequired = true;
|
|
699
|
+
}
|
|
700
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
701
|
+
paramResult.requiredNum += result.requiredNum;
|
|
702
|
+
paramResult.optionalNum += result.optionalNum;
|
|
703
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
704
|
+
paramResult.reason.push(...result.reason);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
709
|
+
paramResult.isValid = false;
|
|
710
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return paramResult;
|
|
714
|
+
}
|
|
715
|
+
checkParamSchema(paramObject) {
|
|
716
|
+
const paramResult = {
|
|
717
|
+
requiredNum: 0,
|
|
718
|
+
optionalNum: 0,
|
|
719
|
+
isValid: true,
|
|
720
|
+
reason: [],
|
|
721
|
+
};
|
|
722
|
+
if (!paramObject) {
|
|
723
|
+
return paramResult;
|
|
724
|
+
}
|
|
725
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
726
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
727
|
+
const param = paramObject[i];
|
|
728
|
+
const schema = param.schema;
|
|
729
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
730
|
+
paramResult.isValid = false;
|
|
731
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
735
|
+
if (isCopilot) {
|
|
736
|
+
if (isRequiredWithoutDefault) {
|
|
737
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
745
|
+
if (isRequiredWithoutDefault) {
|
|
746
|
+
paramResult.isValid = false;
|
|
747
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
748
|
+
}
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (schema.type !== "boolean" &&
|
|
752
|
+
schema.type !== "string" &&
|
|
753
|
+
schema.type !== "number" &&
|
|
754
|
+
schema.type !== "integer") {
|
|
755
|
+
if (isRequiredWithoutDefault) {
|
|
756
|
+
paramResult.isValid = false;
|
|
757
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
758
|
+
}
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (param.in === "query" || param.in === "path") {
|
|
762
|
+
if (isRequiredWithoutDefault) {
|
|
763
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return paramResult;
|
|
771
|
+
}
|
|
772
|
+
hasNestedObjectInSchema(schema) {
|
|
773
|
+
if (schema.type === "object") {
|
|
774
|
+
for (const property in schema.properties) {
|
|
775
|
+
const nestedSchema = schema.properties[property];
|
|
776
|
+
if (nestedSchema.type === "object") {
|
|
777
|
+
return true;
|
|
782
778
|
}
|
|
783
779
|
}
|
|
784
780
|
}
|
|
785
|
-
return
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Copyright (c) Microsoft Corporation.
|
|
786
|
+
class CopilotValidator extends Validator {
|
|
787
|
+
constructor(spec, options) {
|
|
788
|
+
super();
|
|
789
|
+
this.projectType = ProjectType.Copilot;
|
|
790
|
+
this.options = options;
|
|
791
|
+
this.spec = spec;
|
|
792
|
+
}
|
|
793
|
+
validateSpec() {
|
|
794
|
+
const result = { errors: [], warnings: [] };
|
|
795
|
+
// validate spec version
|
|
796
|
+
let validationResult = this.validateSpecVersion();
|
|
797
|
+
result.errors.push(...validationResult.errors);
|
|
798
|
+
// validate spec server
|
|
799
|
+
validationResult = this.validateSpecServer();
|
|
800
|
+
result.errors.push(...validationResult.errors);
|
|
801
|
+
// validate no supported API
|
|
802
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
803
|
+
result.errors.push(...validationResult.errors);
|
|
804
|
+
// validate operationId missing
|
|
805
|
+
validationResult = this.validateSpecOperationId();
|
|
806
|
+
result.warnings.push(...validationResult.warnings);
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
validateAPI(method, path) {
|
|
810
|
+
const result = { isValid: true, reason: [] };
|
|
811
|
+
method = method.toLocaleLowerCase();
|
|
812
|
+
// validate method and path
|
|
813
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
814
|
+
if (!methodAndPathResult.isValid) {
|
|
815
|
+
return methodAndPathResult;
|
|
816
|
+
}
|
|
817
|
+
const operationObject = this.spec.paths[path][method];
|
|
818
|
+
// validate auth
|
|
819
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
820
|
+
result.reason.push(...authCheckResult.reason);
|
|
821
|
+
// validate operationId
|
|
822
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
823
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
824
|
+
}
|
|
825
|
+
// validate server
|
|
826
|
+
const validateServerResult = this.validateServer(method, path);
|
|
827
|
+
result.reason.push(...validateServerResult.reason);
|
|
828
|
+
// validate response
|
|
829
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
830
|
+
result.reason.push(...validateResponseResult.reason);
|
|
831
|
+
// validate requestBody
|
|
832
|
+
const requestBody = operationObject.requestBody;
|
|
833
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
834
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
835
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
836
|
+
}
|
|
837
|
+
if (requestJsonBody) {
|
|
838
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
839
|
+
if (requestBodySchema.type !== "object") {
|
|
840
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
841
|
+
}
|
|
842
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
843
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
844
|
+
}
|
|
845
|
+
// validate parameters
|
|
846
|
+
const paramObject = operationObject.parameters;
|
|
847
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
848
|
+
result.reason.push(...paramResult.reason);
|
|
849
|
+
if (result.reason.length > 0) {
|
|
850
|
+
result.isValid = false;
|
|
851
|
+
}
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Copyright (c) Microsoft Corporation.
|
|
857
|
+
class SMEValidator extends Validator {
|
|
858
|
+
constructor(spec, options) {
|
|
859
|
+
super();
|
|
860
|
+
this.projectType = ProjectType.SME;
|
|
861
|
+
this.options = options;
|
|
862
|
+
this.spec = spec;
|
|
863
|
+
}
|
|
864
|
+
validateSpec() {
|
|
865
|
+
const result = { errors: [], warnings: [] };
|
|
866
|
+
// validate spec version
|
|
867
|
+
let validationResult = this.validateSpecVersion();
|
|
868
|
+
result.errors.push(...validationResult.errors);
|
|
869
|
+
// validate spec server
|
|
870
|
+
validationResult = this.validateSpecServer();
|
|
871
|
+
result.errors.push(...validationResult.errors);
|
|
872
|
+
// validate no supported API
|
|
873
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
874
|
+
result.errors.push(...validationResult.errors);
|
|
875
|
+
// validate operationId missing
|
|
876
|
+
if (this.options.allowMissingId) {
|
|
877
|
+
validationResult = this.validateSpecOperationId();
|
|
878
|
+
result.warnings.push(...validationResult.warnings);
|
|
879
|
+
}
|
|
880
|
+
return result;
|
|
881
|
+
}
|
|
882
|
+
validateAPI(method, path) {
|
|
883
|
+
const result = { isValid: true, reason: [] };
|
|
884
|
+
method = method.toLocaleLowerCase();
|
|
885
|
+
// validate method and path
|
|
886
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
887
|
+
if (!methodAndPathResult.isValid) {
|
|
888
|
+
return methodAndPathResult;
|
|
889
|
+
}
|
|
890
|
+
const operationObject = this.spec.paths[path][method];
|
|
891
|
+
// validate auth
|
|
892
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
893
|
+
result.reason.push(...authCheckResult.reason);
|
|
894
|
+
// validate operationId
|
|
895
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
896
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
897
|
+
}
|
|
898
|
+
// validate server
|
|
899
|
+
const validateServerResult = this.validateServer(method, path);
|
|
900
|
+
result.reason.push(...validateServerResult.reason);
|
|
901
|
+
// validate response
|
|
902
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
903
|
+
result.reason.push(...validateResponseResult.reason);
|
|
904
|
+
let postBodyResult = {
|
|
905
|
+
requiredNum: 0,
|
|
906
|
+
optionalNum: 0,
|
|
907
|
+
isValid: true,
|
|
908
|
+
reason: [],
|
|
909
|
+
};
|
|
910
|
+
// validate requestBody
|
|
911
|
+
const requestBody = operationObject.requestBody;
|
|
912
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
913
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
914
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
915
|
+
}
|
|
916
|
+
if (requestJsonBody) {
|
|
917
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
918
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
919
|
+
result.reason.push(...postBodyResult.reason);
|
|
920
|
+
}
|
|
921
|
+
// validate parameters
|
|
922
|
+
const paramObject = operationObject.parameters;
|
|
923
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
924
|
+
result.reason.push(...paramResult.reason);
|
|
925
|
+
// validate total parameters count
|
|
926
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
927
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
928
|
+
result.reason.push(...paramCountResult.reason);
|
|
929
|
+
}
|
|
930
|
+
if (result.reason.length > 0) {
|
|
931
|
+
result.isValid = false;
|
|
932
|
+
}
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
936
|
+
const result = { isValid: true, reason: [] };
|
|
937
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
938
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
939
|
+
if (totalRequiredParams > 1) {
|
|
940
|
+
if (!this.options.allowMultipleParameters ||
|
|
941
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
942
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
else if (totalParams === 0) {
|
|
946
|
+
result.reason.push(ErrorType.NoParameter);
|
|
947
|
+
}
|
|
948
|
+
return result;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
952
|
+
|
|
953
|
+
// Copyright (c) Microsoft Corporation.
|
|
954
|
+
class TeamsAIValidator extends Validator {
|
|
955
|
+
constructor(spec, options) {
|
|
956
|
+
super();
|
|
957
|
+
this.projectType = ProjectType.TeamsAi;
|
|
958
|
+
this.options = options;
|
|
959
|
+
this.spec = spec;
|
|
960
|
+
}
|
|
961
|
+
validateSpec() {
|
|
962
|
+
const result = { errors: [], warnings: [] };
|
|
963
|
+
// validate spec server
|
|
964
|
+
let validationResult = this.validateSpecServer();
|
|
965
|
+
result.errors.push(...validationResult.errors);
|
|
966
|
+
// validate no supported API
|
|
967
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
968
|
+
result.errors.push(...validationResult.errors);
|
|
969
|
+
return result;
|
|
970
|
+
}
|
|
971
|
+
validateAPI(method, path) {
|
|
972
|
+
const result = { isValid: true, reason: [] };
|
|
973
|
+
method = method.toLocaleLowerCase();
|
|
974
|
+
// validate method and path
|
|
975
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
976
|
+
if (!methodAndPathResult.isValid) {
|
|
977
|
+
return methodAndPathResult;
|
|
978
|
+
}
|
|
979
|
+
const operationObject = this.spec.paths[path][method];
|
|
980
|
+
// validate operationId
|
|
981
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
982
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
983
|
+
}
|
|
984
|
+
// validate server
|
|
985
|
+
const validateServerResult = this.validateServer(method, path);
|
|
986
|
+
result.reason.push(...validateServerResult.reason);
|
|
987
|
+
if (result.reason.length > 0) {
|
|
988
|
+
result.isValid = false;
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
class ValidatorFactory {
|
|
995
|
+
static create(spec, options) {
|
|
996
|
+
var _a;
|
|
997
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
998
|
+
switch (type) {
|
|
999
|
+
case ProjectType.SME:
|
|
1000
|
+
return new SMEValidator(spec, options);
|
|
1001
|
+
case ProjectType.Copilot:
|
|
1002
|
+
return new CopilotValidator(spec, options);
|
|
1003
|
+
case ProjectType.TeamsAi:
|
|
1004
|
+
return new TeamsAIValidator(spec, options);
|
|
1005
|
+
default:
|
|
1006
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1007
|
+
}
|
|
786
1008
|
}
|
|
787
1009
|
}
|
|
788
1010
|
|
|
@@ -805,6 +1027,8 @@ class SpecParser {
|
|
|
805
1027
|
allowBearerTokenAuth: false,
|
|
806
1028
|
allowOauth2: false,
|
|
807
1029
|
allowMethods: ["get", "post"],
|
|
1030
|
+
allowConversationStarters: false,
|
|
1031
|
+
allowResponseSemantics: false,
|
|
808
1032
|
projectType: ProjectType.SME,
|
|
809
1033
|
};
|
|
810
1034
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -820,11 +1044,7 @@ class SpecParser {
|
|
|
820
1044
|
try {
|
|
821
1045
|
try {
|
|
822
1046
|
await this.loadSpec();
|
|
823
|
-
await this.parser.validate(this.spec
|
|
824
|
-
validate: {
|
|
825
|
-
schema: false,
|
|
826
|
-
},
|
|
827
|
-
});
|
|
1047
|
+
await this.parser.validate(this.spec);
|
|
828
1048
|
}
|
|
829
1049
|
catch (e) {
|
|
830
1050
|
return {
|
|
@@ -833,16 +1053,46 @@ class SpecParser {
|
|
|
833
1053
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
834
1054
|
};
|
|
835
1055
|
}
|
|
1056
|
+
const errors = [];
|
|
1057
|
+
const warnings = [];
|
|
836
1058
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
837
1059
|
return {
|
|
838
1060
|
status: ValidationStatus.Error,
|
|
839
1061
|
warnings: [],
|
|
840
1062
|
errors: [
|
|
841
|
-
{
|
|
1063
|
+
{
|
|
1064
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1065
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1066
|
+
},
|
|
842
1067
|
],
|
|
843
1068
|
};
|
|
844
1069
|
}
|
|
845
|
-
|
|
1070
|
+
// Remote reference not supported
|
|
1071
|
+
const refPaths = this.parser.$refs.paths();
|
|
1072
|
+
// refPaths [0] is the current spec file path
|
|
1073
|
+
if (refPaths.length > 1) {
|
|
1074
|
+
errors.push({
|
|
1075
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1076
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1077
|
+
data: refPaths,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
const validator = this.getValidator(this.spec);
|
|
1081
|
+
const validationResult = validator.validateSpec();
|
|
1082
|
+
warnings.push(...validationResult.warnings);
|
|
1083
|
+
errors.push(...validationResult.errors);
|
|
1084
|
+
let status = ValidationStatus.Valid;
|
|
1085
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1086
|
+
status = ValidationStatus.Warning;
|
|
1087
|
+
}
|
|
1088
|
+
else if (errors.length > 0) {
|
|
1089
|
+
status = ValidationStatus.Error;
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
status: status,
|
|
1093
|
+
warnings: warnings,
|
|
1094
|
+
errors: errors,
|
|
1095
|
+
};
|
|
846
1096
|
}
|
|
847
1097
|
catch (err) {
|
|
848
1098
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -851,17 +1101,20 @@ class SpecParser {
|
|
|
851
1101
|
async listSupportedAPIInfo() {
|
|
852
1102
|
try {
|
|
853
1103
|
await this.loadSpec();
|
|
854
|
-
const apiMap = this.
|
|
1104
|
+
const apiMap = this.getAPIs(this.spec);
|
|
855
1105
|
const apiInfos = [];
|
|
856
1106
|
for (const key in apiMap) {
|
|
857
|
-
const
|
|
1107
|
+
const { operation, isValid } = apiMap[key];
|
|
1108
|
+
if (!isValid) {
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
858
1111
|
const [method, path] = key.split(" ");
|
|
859
|
-
const operationId =
|
|
1112
|
+
const operationId = operation.operationId;
|
|
860
1113
|
// In Browser environment, this api is by default not support api without operationId
|
|
861
1114
|
if (!operationId) {
|
|
862
1115
|
continue;
|
|
863
1116
|
}
|
|
864
|
-
const
|
|
1117
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
865
1118
|
const apiInfo = {
|
|
866
1119
|
method: method,
|
|
867
1120
|
path: path,
|
|
@@ -870,9 +1123,6 @@ class SpecParser {
|
|
|
870
1123
|
parameters: command.parameters,
|
|
871
1124
|
description: command.description,
|
|
872
1125
|
};
|
|
873
|
-
if (warning) {
|
|
874
|
-
apiInfo.warning = warning;
|
|
875
|
-
}
|
|
876
1126
|
apiInfos.push(apiInfo);
|
|
877
1127
|
}
|
|
878
1128
|
return apiInfos;
|
|
@@ -931,13 +1181,18 @@ class SpecParser {
|
|
|
931
1181
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
932
1182
|
}
|
|
933
1183
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1184
|
+
getAPIs(spec) {
|
|
1185
|
+
const validator = this.getValidator(spec);
|
|
1186
|
+
const apiMap = validator.listAPIs();
|
|
1187
|
+
return apiMap;
|
|
1188
|
+
}
|
|
1189
|
+
getValidator(spec) {
|
|
1190
|
+
if (this.validator) {
|
|
1191
|
+
return this.validator;
|
|
937
1192
|
}
|
|
938
|
-
const
|
|
939
|
-
this.
|
|
940
|
-
return
|
|
1193
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1194
|
+
this.validator = validator;
|
|
1195
|
+
return validator;
|
|
941
1196
|
}
|
|
942
1197
|
}
|
|
943
1198
|
|
|
@@ -945,7 +1200,7 @@ class SpecParser {
|
|
|
945
1200
|
class AdaptiveCardGenerator {
|
|
946
1201
|
static generateAdaptiveCard(operationItem) {
|
|
947
1202
|
try {
|
|
948
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1203
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
949
1204
|
let cardBody = [];
|
|
950
1205
|
let schema = json.schema;
|
|
951
1206
|
let jsonPath = "$";
|