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