@microsoft/m365-spec-parser 0.1.1-alpha.1c9557de8.0 → 0.1.1-alpha.268ff5845.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 +582 -326
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +699 -384
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +582 -326
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +747 -430
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +45 -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
|
@@ -16,6 +16,7 @@ var ErrorType;
|
|
|
16
16
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
17
17
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
18
18
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
19
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
19
20
|
ErrorType["ListFailed"] = "list-failed";
|
|
20
21
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
21
22
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -24,6 +25,21 @@ var ErrorType;
|
|
|
24
25
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
25
26
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
26
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";
|
|
27
43
|
ErrorType["Cancelled"] = "cancelled";
|
|
28
44
|
ErrorType["Unknown"] = "unknown";
|
|
29
45
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -79,6 +95,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
79
95
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
80
96
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
81
97
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
98
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
82
99
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
83
100
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
84
101
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -153,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
153
170
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
154
171
|
ConstantString.CommandTitleMaxLens = 32;
|
|
155
172
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
156
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
173
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
174
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
157
175
|
|
|
158
176
|
// Copyright (c) Microsoft Corporation.
|
|
159
177
|
class Utils {
|
|
@@ -168,221 +186,9 @@ class Utils {
|
|
|
168
186
|
}
|
|
169
187
|
return false;
|
|
170
188
|
}
|
|
171
|
-
static checkParameters(paramObject, isCopilot) {
|
|
172
|
-
const paramResult = {
|
|
173
|
-
requiredNum: 0,
|
|
174
|
-
optionalNum: 0,
|
|
175
|
-
isValid: true,
|
|
176
|
-
};
|
|
177
|
-
if (!paramObject) {
|
|
178
|
-
return paramResult;
|
|
179
|
-
}
|
|
180
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
181
|
-
const param = paramObject[i];
|
|
182
|
-
const schema = param.schema;
|
|
183
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
184
|
-
paramResult.isValid = false;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
188
|
-
if (isCopilot) {
|
|
189
|
-
if (isRequiredWithoutDefault) {
|
|
190
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
194
|
-
}
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
198
|
-
if (isRequiredWithoutDefault) {
|
|
199
|
-
paramResult.isValid = false;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (schema.type !== "boolean" &&
|
|
204
|
-
schema.type !== "string" &&
|
|
205
|
-
schema.type !== "number" &&
|
|
206
|
-
schema.type !== "integer") {
|
|
207
|
-
if (isRequiredWithoutDefault) {
|
|
208
|
-
paramResult.isValid = false;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (param.in === "query" || param.in === "path") {
|
|
213
|
-
if (isRequiredWithoutDefault) {
|
|
214
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return paramResult;
|
|
222
|
-
}
|
|
223
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
224
|
-
var _a;
|
|
225
|
-
const paramResult = {
|
|
226
|
-
requiredNum: 0,
|
|
227
|
-
optionalNum: 0,
|
|
228
|
-
isValid: true,
|
|
229
|
-
};
|
|
230
|
-
if (Object.keys(schema).length === 0) {
|
|
231
|
-
return paramResult;
|
|
232
|
-
}
|
|
233
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
234
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
235
|
-
paramResult.isValid = false;
|
|
236
|
-
return paramResult;
|
|
237
|
-
}
|
|
238
|
-
if (schema.type === "string" ||
|
|
239
|
-
schema.type === "integer" ||
|
|
240
|
-
schema.type === "boolean" ||
|
|
241
|
-
schema.type === "number") {
|
|
242
|
-
if (isRequiredWithoutDefault) {
|
|
243
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
else if (schema.type === "object") {
|
|
250
|
-
const { properties } = schema;
|
|
251
|
-
for (const property in properties) {
|
|
252
|
-
let isRequired = false;
|
|
253
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
254
|
-
isRequired = true;
|
|
255
|
-
}
|
|
256
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
257
|
-
paramResult.requiredNum += result.requiredNum;
|
|
258
|
-
paramResult.optionalNum += result.optionalNum;
|
|
259
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
264
|
-
paramResult.isValid = false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return paramResult;
|
|
268
|
-
}
|
|
269
189
|
static containMultipleMediaTypes(bodyObject) {
|
|
270
190
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
271
191
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Checks if the given API is supported.
|
|
274
|
-
* @param {string} method - The HTTP method of the API.
|
|
275
|
-
* @param {string} path - The path of the API.
|
|
276
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
277
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
278
|
-
* @description The following APIs are supported:
|
|
279
|
-
* 1. only support Get/Post operation without auth property
|
|
280
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
281
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
282
|
-
* 4. request body + required parameters <= 1
|
|
283
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
284
|
-
* 6. only support request body with “application/json” content type
|
|
285
|
-
*/
|
|
286
|
-
static isSupportedApi(method, path, spec, options) {
|
|
287
|
-
var _a;
|
|
288
|
-
const pathObj = spec.paths[path];
|
|
289
|
-
method = method.toLocaleLowerCase();
|
|
290
|
-
if (pathObj) {
|
|
291
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
292
|
-
const securities = pathObj[method].security;
|
|
293
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
294
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
295
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
296
|
-
if (!isTeamsAi) {
|
|
297
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
298
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const operationObject = pathObj[method];
|
|
303
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
const paramObject = operationObject.parameters;
|
|
307
|
-
const requestBody = operationObject.requestBody;
|
|
308
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
309
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
313
|
-
if (Object.keys(responseJson).length === 0) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
// Teams AI project doesn't care about request parameters/body
|
|
317
|
-
if (isTeamsAi) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
let requestBodyParamResult = {
|
|
321
|
-
requiredNum: 0,
|
|
322
|
-
optionalNum: 0,
|
|
323
|
-
isValid: true,
|
|
324
|
-
};
|
|
325
|
-
if (requestJsonBody) {
|
|
326
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
327
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
331
|
-
}
|
|
332
|
-
if (!requestBodyParamResult.isValid) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
336
|
-
if (!paramResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
// Copilot support arbitrary parameters
|
|
340
|
-
if (isCopilot) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
344
|
-
if (options.allowMultipleParameters &&
|
|
345
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
346
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
else if (requestBodyParamResult.requiredNum +
|
|
352
|
-
requestBodyParamResult.optionalNum +
|
|
353
|
-
paramResult.requiredNum +
|
|
354
|
-
paramResult.optionalNum ===
|
|
355
|
-
0) {
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
366
|
-
if (authSchemeArray.length === 0) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
370
|
-
// Currently we don't support multiple auth in one operation
|
|
371
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
for (const auths of authSchemeArray) {
|
|
375
|
-
if (auths.length === 1) {
|
|
376
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
377
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
378
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
192
|
static isBearerTokenAuth(authScheme) {
|
|
387
193
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
388
194
|
}
|
|
@@ -390,10 +196,9 @@ class Utils {
|
|
|
390
196
|
return authScheme.type === "apiKey";
|
|
391
197
|
}
|
|
392
198
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return false;
|
|
199
|
+
return !!(authScheme.type === "oauth2" &&
|
|
200
|
+
authScheme.flows &&
|
|
201
|
+
authScheme.flows.authorizationCode);
|
|
397
202
|
}
|
|
398
203
|
static getAuthArray(securities, spec) {
|
|
399
204
|
var _a;
|
|
@@ -421,14 +226,17 @@ class Utils {
|
|
|
421
226
|
static updateFirstLetter(str) {
|
|
422
227
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
423
228
|
}
|
|
424
|
-
static getResponseJson(operationObject
|
|
229
|
+
static getResponseJson(operationObject) {
|
|
425
230
|
var _a, _b;
|
|
426
231
|
let json = {};
|
|
232
|
+
let multipleMediaType = false;
|
|
427
233
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
428
234
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
429
235
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
236
|
+
multipleMediaType = false;
|
|
430
237
|
json = responseObject.content["application/json"];
|
|
431
|
-
if (
|
|
238
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
239
|
+
multipleMediaType = true;
|
|
432
240
|
json = {};
|
|
433
241
|
}
|
|
434
242
|
else {
|
|
@@ -436,7 +244,7 @@ class Utils {
|
|
|
436
244
|
}
|
|
437
245
|
}
|
|
438
246
|
}
|
|
439
|
-
return json;
|
|
247
|
+
return { json, multipleMediaType };
|
|
440
248
|
}
|
|
441
249
|
static convertPathToCamelCase(path) {
|
|
442
250
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -456,10 +264,10 @@ class Utils {
|
|
|
456
264
|
return undefined;
|
|
457
265
|
}
|
|
458
266
|
}
|
|
459
|
-
static
|
|
267
|
+
static resolveEnv(str) {
|
|
460
268
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
461
|
-
let matches = placeHolderReg.exec(
|
|
462
|
-
let
|
|
269
|
+
let matches = placeHolderReg.exec(str);
|
|
270
|
+
let newStr = str;
|
|
463
271
|
while (matches != null) {
|
|
464
272
|
const envVar = matches[1];
|
|
465
273
|
const envVal = process.env[envVar];
|
|
@@ -467,17 +275,17 @@ class Utils {
|
|
|
467
275
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
468
276
|
}
|
|
469
277
|
else {
|
|
470
|
-
|
|
278
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
471
279
|
}
|
|
472
|
-
matches = placeHolderReg.exec(
|
|
280
|
+
matches = placeHolderReg.exec(str);
|
|
473
281
|
}
|
|
474
|
-
return
|
|
282
|
+
return newStr;
|
|
475
283
|
}
|
|
476
284
|
static checkServerUrl(servers) {
|
|
477
285
|
const errors = [];
|
|
478
286
|
let serverUrl;
|
|
479
287
|
try {
|
|
480
|
-
serverUrl = Utils.
|
|
288
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
481
289
|
}
|
|
482
290
|
catch (err) {
|
|
483
291
|
errors.push({
|
|
@@ -508,6 +316,7 @@ class Utils {
|
|
|
508
316
|
return errors;
|
|
509
317
|
}
|
|
510
318
|
static validateServer(spec, options) {
|
|
319
|
+
var _a;
|
|
511
320
|
const errors = [];
|
|
512
321
|
let hasTopLevelServers = false;
|
|
513
322
|
let hasPathLevelServers = false;
|
|
@@ -528,7 +337,7 @@ class Utils {
|
|
|
528
337
|
}
|
|
529
338
|
for (const method in methods) {
|
|
530
339
|
const operationObject = methods[method];
|
|
531
|
-
if (
|
|
340
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
532
341
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
533
342
|
hasOperationLevelServers = true;
|
|
534
343
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -655,13 +464,7 @@ class Utils {
|
|
|
655
464
|
}
|
|
656
465
|
}
|
|
657
466
|
const operationId = operationItem.operationId;
|
|
658
|
-
const parameters = [];
|
|
659
|
-
if (requiredParams.length !== 0) {
|
|
660
|
-
parameters.push(...requiredParams);
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
parameters.push(optionalParams[0]);
|
|
664
|
-
}
|
|
467
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
665
468
|
const command = {
|
|
666
469
|
context: ["compose"],
|
|
667
470
|
type: "query",
|
|
@@ -670,117 +473,535 @@ class Utils {
|
|
|
670
473
|
parameters: parameters,
|
|
671
474
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
672
475
|
};
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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 "";
|
|
680
488
|
}
|
|
681
|
-
|
|
489
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
490
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
491
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
492
|
+
}
|
|
493
|
+
return safeRegistrationIdEnvName;
|
|
682
494
|
}
|
|
683
|
-
static
|
|
684
|
-
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;
|
|
685
514
|
const result = {};
|
|
686
515
|
for (const path in paths) {
|
|
687
516
|
const methods = paths[path];
|
|
688
517
|
for (const method in methods) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
+
};
|
|
692
526
|
}
|
|
693
527
|
}
|
|
694
528
|
}
|
|
529
|
+
this.apiMap = result;
|
|
695
530
|
return result;
|
|
696
531
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
// Server validation
|
|
707
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
708
|
-
errors.push(...serverErrors);
|
|
709
|
-
// Remote reference not supported
|
|
710
|
-
const refPaths = parser.$refs.paths();
|
|
711
|
-
// refPaths [0] is the current spec file path
|
|
712
|
-
if (refPaths.length > 1) {
|
|
713
|
-
errors.push({
|
|
714
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
715
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
716
|
-
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,
|
|
717
539
|
});
|
|
718
540
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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({
|
|
723
561
|
type: ErrorType.NoSupportedApi,
|
|
724
562
|
content: ConstantString.NoSupportedApi,
|
|
563
|
+
data,
|
|
725
564
|
});
|
|
726
565
|
}
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
validateSpecOperationId() {
|
|
569
|
+
const result = { errors: [], warnings: [] };
|
|
570
|
+
const apiMap = this.listAPIs();
|
|
727
571
|
// OperationId missing
|
|
728
572
|
const apisMissingOperationId = [];
|
|
729
573
|
for (const key in apiMap) {
|
|
730
|
-
const
|
|
731
|
-
if (!
|
|
574
|
+
const { operation } = apiMap[key];
|
|
575
|
+
if (!operation.operationId) {
|
|
732
576
|
apisMissingOperationId.push(key);
|
|
733
577
|
}
|
|
734
578
|
}
|
|
735
579
|
if (apisMissingOperationId.length > 0) {
|
|
736
|
-
warnings.push({
|
|
580
|
+
result.warnings.push({
|
|
737
581
|
type: WarningType.OperationIdMissing,
|
|
738
582
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
739
583
|
data: apisMissingOperationId,
|
|
740
584
|
});
|
|
741
585
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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;
|
|
745
594
|
}
|
|
746
|
-
|
|
747
|
-
|
|
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;
|
|
748
600
|
}
|
|
749
|
-
return
|
|
750
|
-
status,
|
|
751
|
-
warnings,
|
|
752
|
-
errors,
|
|
753
|
-
};
|
|
601
|
+
return result;
|
|
754
602
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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;
|
|
761
616
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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);
|
|
765
623
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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));
|
|
769
628
|
}
|
|
770
|
-
return
|
|
629
|
+
return result;
|
|
771
630
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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;
|
|
780
775
|
}
|
|
781
776
|
}
|
|
782
777
|
}
|
|
783
|
-
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
|
+
}
|
|
784
1005
|
}
|
|
785
1006
|
}
|
|
786
1007
|
|
|
@@ -818,11 +1039,7 @@ class SpecParser {
|
|
|
818
1039
|
try {
|
|
819
1040
|
try {
|
|
820
1041
|
await this.loadSpec();
|
|
821
|
-
await this.parser.validate(this.spec
|
|
822
|
-
validate: {
|
|
823
|
-
schema: false,
|
|
824
|
-
},
|
|
825
|
-
});
|
|
1042
|
+
await this.parser.validate(this.spec);
|
|
826
1043
|
}
|
|
827
1044
|
catch (e) {
|
|
828
1045
|
return {
|
|
@@ -831,16 +1048,46 @@ class SpecParser {
|
|
|
831
1048
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
832
1049
|
};
|
|
833
1050
|
}
|
|
1051
|
+
const errors = [];
|
|
1052
|
+
const warnings = [];
|
|
834
1053
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
835
1054
|
return {
|
|
836
1055
|
status: ValidationStatus.Error,
|
|
837
1056
|
warnings: [],
|
|
838
1057
|
errors: [
|
|
839
|
-
{
|
|
1058
|
+
{
|
|
1059
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1060
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1061
|
+
},
|
|
840
1062
|
],
|
|
841
1063
|
};
|
|
842
1064
|
}
|
|
843
|
-
|
|
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
|
+
};
|
|
844
1091
|
}
|
|
845
1092
|
catch (err) {
|
|
846
1093
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -849,17 +1096,20 @@ class SpecParser {
|
|
|
849
1096
|
async listSupportedAPIInfo() {
|
|
850
1097
|
try {
|
|
851
1098
|
await this.loadSpec();
|
|
852
|
-
const apiMap = this.
|
|
1099
|
+
const apiMap = this.getAPIs(this.spec);
|
|
853
1100
|
const apiInfos = [];
|
|
854
1101
|
for (const key in apiMap) {
|
|
855
|
-
const
|
|
1102
|
+
const { operation, isValid } = apiMap[key];
|
|
1103
|
+
if (!isValid) {
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
856
1106
|
const [method, path] = key.split(" ");
|
|
857
|
-
const operationId =
|
|
1107
|
+
const operationId = operation.operationId;
|
|
858
1108
|
// In Browser environment, this api is by default not support api without operationId
|
|
859
1109
|
if (!operationId) {
|
|
860
1110
|
continue;
|
|
861
1111
|
}
|
|
862
|
-
const
|
|
1112
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
863
1113
|
const apiInfo = {
|
|
864
1114
|
method: method,
|
|
865
1115
|
path: path,
|
|
@@ -868,9 +1118,6 @@ class SpecParser {
|
|
|
868
1118
|
parameters: command.parameters,
|
|
869
1119
|
description: command.description,
|
|
870
1120
|
};
|
|
871
|
-
if (warning) {
|
|
872
|
-
apiInfo.warning = warning;
|
|
873
|
-
}
|
|
874
1121
|
apiInfos.push(apiInfo);
|
|
875
1122
|
}
|
|
876
1123
|
return apiInfos;
|
|
@@ -929,13 +1176,22 @@ class SpecParser {
|
|
|
929
1176
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
930
1177
|
}
|
|
931
1178
|
}
|
|
932
|
-
|
|
1179
|
+
getAPIs(spec) {
|
|
933
1180
|
if (this.apiMap !== undefined) {
|
|
934
1181
|
return this.apiMap;
|
|
935
1182
|
}
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
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;
|
|
939
1195
|
}
|
|
940
1196
|
}
|
|
941
1197
|
|
|
@@ -943,7 +1199,7 @@ class SpecParser {
|
|
|
943
1199
|
class AdaptiveCardGenerator {
|
|
944
1200
|
static generateAdaptiveCard(operationItem) {
|
|
945
1201
|
try {
|
|
946
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1202
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
947
1203
|
let cardBody = [];
|
|
948
1204
|
let schema = json.schema;
|
|
949
1205
|
let jsonPath = "$";
|