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