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