@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.b54a7ba8f.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.
@@ -55,6 +55,21 @@ var ErrorType;
55
55
  ErrorType["GenerateFailed"] = "generate-failed";
56
56
  ErrorType["ValidateFailed"] = "validate-failed";
57
57
  ErrorType["GetSpecFailed"] = "get-spec-failed";
58
+ ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
59
+ ErrorType["MissingOperationId"] = "missing-operation-id";
60
+ ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
61
+ ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
62
+ ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
63
+ ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
64
+ ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
65
+ ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
66
+ ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
67
+ ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
68
+ ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
69
+ ErrorType["NoParameter"] = "no-parameter";
70
+ ErrorType["NoAPIInfo"] = "no-api-info";
71
+ ErrorType["MethodNotAllowed"] = "method-not-allowed";
72
+ ErrorType["UrlPathNotExist"] = "url-path-not-exist";
58
73
  ErrorType["Cancelled"] = "cancelled";
59
74
  ErrorType["Unknown"] = "unknown";
60
75
  })(ErrorType || (ErrorType = {}));
@@ -205,6 +220,7 @@ class Utils {
205
220
  requiredNum: 0,
206
221
  optionalNum: 0,
207
222
  isValid: true,
223
+ reason: [],
208
224
  };
209
225
  if (!paramObject) {
210
226
  return paramResult;
@@ -214,6 +230,7 @@ class Utils {
214
230
  const schema = param.schema;
215
231
  if (isCopilot && this.hasNestedObjectInSchema(schema)) {
216
232
  paramResult.isValid = false;
233
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
217
234
  continue;
218
235
  }
219
236
  const isRequiredWithoutDefault = param.required && schema.default === undefined;
@@ -229,6 +246,7 @@ class Utils {
229
246
  if (param.in === "header" || param.in === "cookie") {
230
247
  if (isRequiredWithoutDefault) {
231
248
  paramResult.isValid = false;
249
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
232
250
  }
233
251
  continue;
234
252
  }
@@ -238,6 +256,7 @@ class Utils {
238
256
  schema.type !== "integer") {
239
257
  if (isRequiredWithoutDefault) {
240
258
  paramResult.isValid = false;
259
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
241
260
  }
242
261
  continue;
243
262
  }
@@ -258,6 +277,7 @@ class Utils {
258
277
  requiredNum: 0,
259
278
  optionalNum: 0,
260
279
  isValid: true,
280
+ reason: [],
261
281
  };
262
282
  if (Object.keys(schema).length === 0) {
263
283
  return paramResult;
@@ -265,6 +285,7 @@ class Utils {
265
285
  const isRequiredWithoutDefault = isRequired && schema.default === undefined;
266
286
  if (isCopilot && this.hasNestedObjectInSchema(schema)) {
267
287
  paramResult.isValid = false;
288
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
268
289
  return paramResult;
269
290
  }
270
291
  if (schema.type === "string" ||
@@ -289,11 +310,13 @@ class Utils {
289
310
  paramResult.requiredNum += result.requiredNum;
290
311
  paramResult.optionalNum += result.optionalNum;
291
312
  paramResult.isValid = paramResult.isValid && result.isValid;
313
+ paramResult.reason.push(...result.reason);
292
314
  }
293
315
  }
294
316
  else {
295
317
  if (isRequiredWithoutDefault && !isCopilot) {
296
318
  paramResult.isValid = false;
319
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
297
320
  }
298
321
  }
299
322
  return paramResult;
@@ -317,103 +340,123 @@ class Utils {
317
340
  */
318
341
  static isSupportedApi(method, path, spec, options) {
319
342
  var _a;
320
- const pathObj = spec.paths[path];
343
+ const result = { isValid: true, reason: [] };
321
344
  method = method.toLocaleLowerCase();
322
- if (pathObj) {
323
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
324
- const securities = pathObj[method].security;
325
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
326
- const isCopilot = options.projectType === ProjectType.Copilot;
327
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
328
- if (!isTeamsAi) {
329
- const authArray = Utils.getAuthArray(securities, spec);
330
- if (!Utils.isSupportedAuth(authArray, options)) {
331
- return false;
332
- }
333
- }
334
- const operationObject = pathObj[method];
335
- if (!options.allowMissingId && !operationObject.operationId) {
336
- return false;
337
- }
338
- const paramObject = operationObject.parameters;
339
- const requestBody = operationObject.requestBody;
340
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
341
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
342
- return false;
343
- }
344
- const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
345
- if (Object.keys(responseJson).length === 0) {
346
- return false;
347
- }
348
- // Teams AI project doesn't care about request parameters/body
349
- if (isTeamsAi) {
350
- return true;
351
- }
352
- let requestBodyParamResult = {
353
- requiredNum: 0,
354
- optionalNum: 0,
355
- isValid: true,
356
- };
357
- if (requestJsonBody) {
358
- const requestBodySchema = requestJsonBody.schema;
359
- if (isCopilot && requestBodySchema.type !== "object") {
360
- return false;
361
- }
362
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
363
- }
364
- if (!requestBodyParamResult.isValid) {
365
- return false;
366
- }
367
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
368
- if (!paramResult.isValid) {
369
- return false;
345
+ if (options.allowMethods && !options.allowMethods.includes(method)) {
346
+ result.isValid = false;
347
+ result.reason.push(ErrorType.MethodNotAllowed);
348
+ return result;
349
+ }
350
+ const pathObj = spec.paths[path];
351
+ if (!pathObj || !pathObj[method]) {
352
+ result.isValid = false;
353
+ result.reason.push(ErrorType.UrlPathNotExist);
354
+ return result;
355
+ }
356
+ const securities = pathObj[method].security;
357
+ const isTeamsAi = options.projectType === ProjectType.TeamsAi;
358
+ const isCopilot = options.projectType === ProjectType.Copilot;
359
+ // Teams AI project doesn't care about auth, it will use authProvider for user to implement
360
+ if (!isTeamsAi) {
361
+ const authArray = Utils.getAuthArray(securities, spec);
362
+ const authCheckResult = Utils.isSupportedAuth(authArray, options);
363
+ if (!authCheckResult.isValid) {
364
+ result.reason.push(...authCheckResult.reason);
365
+ }
366
+ }
367
+ const operationObject = pathObj[method];
368
+ if (!options.allowMissingId && !operationObject.operationId) {
369
+ result.reason.push(ErrorType.MissingOperationId);
370
+ }
371
+ const rootServer = spec.servers && spec.servers[0];
372
+ const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
373
+ const operationServer = operationObject.servers && operationObject.servers[0];
374
+ const serverUrl = operationServer || methodServer || rootServer;
375
+ if (!serverUrl) {
376
+ result.reason.push(ErrorType.NoServerInformation);
377
+ }
378
+ else {
379
+ const serverValidateResult = Utils.checkServerUrl([serverUrl]);
380
+ result.reason.push(...serverValidateResult.map((item) => item.type));
381
+ }
382
+ const paramObject = operationObject.parameters;
383
+ const requestBody = operationObject.requestBody;
384
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
385
+ if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
386
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
387
+ }
388
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
389
+ if (multipleMediaType && !isTeamsAi) {
390
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
391
+ }
392
+ else if (Object.keys(json).length === 0) {
393
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
394
+ }
395
+ // Teams AI project doesn't care about request parameters/body
396
+ if (!isTeamsAi) {
397
+ let requestBodyParamResult = {
398
+ requiredNum: 0,
399
+ optionalNum: 0,
400
+ isValid: true,
401
+ reason: [],
402
+ };
403
+ if (requestJsonBody) {
404
+ const requestBodySchema = requestJsonBody.schema;
405
+ if (isCopilot && requestBodySchema.type !== "object") {
406
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
370
407
  }
371
- // Copilot support arbitrary parameters
372
- if (isCopilot) {
373
- return true;
408
+ requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
409
+ if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
410
+ result.reason.push(...requestBodyParamResult.reason);
374
411
  }
375
- if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
376
- if (options.allowMultipleParameters &&
377
- requestBodyParamResult.requiredNum + paramResult.requiredNum <=
378
- ConstantString.SMERequiredParamsMaxNum) {
379
- return true;
412
+ }
413
+ const paramResult = Utils.checkParameters(paramObject, isCopilot);
414
+ if (!paramResult.isValid && paramResult.reason) {
415
+ result.reason.push(...paramResult.reason);
416
+ }
417
+ // Copilot support arbitrary parameters
418
+ if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
419
+ const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
420
+ const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
421
+ if (totalRequiredParams > 1) {
422
+ if (!options.allowMultipleParameters ||
423
+ totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
424
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
380
425
  }
381
- return false;
382
- }
383
- else if (requestBodyParamResult.requiredNum +
384
- requestBodyParamResult.optionalNum +
385
- paramResult.requiredNum +
386
- paramResult.optionalNum ===
387
- 0) {
388
- return false;
389
426
  }
390
- else {
391
- return true;
427
+ else if (totalParams === 0) {
428
+ result.reason.push(ErrorType.NoParameter);
392
429
  }
393
430
  }
394
431
  }
395
- return false;
432
+ if (result.reason.length > 0) {
433
+ result.isValid = false;
434
+ }
435
+ return result;
396
436
  }
397
437
  static isSupportedAuth(authSchemeArray, options) {
398
438
  if (authSchemeArray.length === 0) {
399
- return true;
439
+ return { isValid: true, reason: [] };
400
440
  }
401
441
  if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
402
442
  // Currently we don't support multiple auth in one operation
403
443
  if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
404
- return false;
444
+ return {
445
+ isValid: false,
446
+ reason: [ErrorType.MultipleAuthNotSupported],
447
+ };
405
448
  }
406
449
  for (const auths of authSchemeArray) {
407
450
  if (auths.length === 1) {
408
451
  if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
409
452
  (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
410
453
  (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
411
- return true;
454
+ return { isValid: true, reason: [] };
412
455
  }
413
456
  }
414
457
  }
415
458
  }
416
- return false;
459
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
417
460
  }
418
461
  static isBearerTokenAuth(authScheme) {
419
462
  return authScheme.type === "http" && authScheme.scheme === "bearer";
@@ -456,11 +499,17 @@ class Utils {
456
499
  static getResponseJson(operationObject, isTeamsAiProject = false) {
457
500
  var _a, _b;
458
501
  let json = {};
502
+ let multipleMediaType = false;
459
503
  for (const code of ConstantString.ResponseCodeFor20X) {
460
504
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
461
505
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
506
+ multipleMediaType = false;
462
507
  json = responseObject.content["application/json"];
463
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
508
+ if (Utils.containMultipleMediaTypes(responseObject)) {
509
+ multipleMediaType = true;
510
+ if (isTeamsAiProject) {
511
+ break;
512
+ }
464
513
  json = {};
465
514
  }
466
515
  else {
@@ -468,7 +517,7 @@ class Utils {
468
517
  }
469
518
  }
470
519
  }
471
- return json;
520
+ return { json, multipleMediaType };
472
521
  }
473
522
  static convertPathToCamelCase(path) {
474
523
  const pathSegments = path.split(/[./{]/);
@@ -488,10 +537,10 @@ class Utils {
488
537
  return undefined;
489
538
  }
490
539
  }
491
- static resolveServerUrl(url) {
540
+ static resolveEnv(str) {
492
541
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
493
- let matches = placeHolderReg.exec(url);
494
- let newUrl = url;
542
+ let matches = placeHolderReg.exec(str);
543
+ let newStr = str;
495
544
  while (matches != null) {
496
545
  const envVar = matches[1];
497
546
  const envVal = process.env[envVar];
@@ -499,17 +548,17 @@ class Utils {
499
548
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
500
549
  }
501
550
  else {
502
- newUrl = newUrl.replace(matches[0], envVal);
551
+ newStr = newStr.replace(matches[0], envVal);
503
552
  }
504
- matches = placeHolderReg.exec(url);
553
+ matches = placeHolderReg.exec(str);
505
554
  }
506
- return newUrl;
555
+ return newStr;
507
556
  }
508
557
  static checkServerUrl(servers) {
509
558
  const errors = [];
510
559
  let serverUrl;
511
560
  try {
512
- serverUrl = Utils.resolveServerUrl(servers[0].url);
561
+ serverUrl = Utils.resolveEnv(servers[0].url);
513
562
  }
514
563
  catch (err) {
515
564
  errors.push({
@@ -540,6 +589,7 @@ class Utils {
540
589
  return errors;
541
590
  }
542
591
  static validateServer(spec, options) {
592
+ var _a;
543
593
  const errors = [];
544
594
  let hasTopLevelServers = false;
545
595
  let hasPathLevelServers = false;
@@ -560,7 +610,7 @@ class Utils {
560
610
  }
561
611
  for (const method in methods) {
562
612
  const operationObject = methods[method];
563
- if (Utils.isSupportedApi(method, path, spec, options)) {
613
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
564
614
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
565
615
  hasOperationLevelServers = true;
566
616
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -712,15 +762,21 @@ class Utils {
712
762
  }
713
763
  return [command, warning];
714
764
  }
715
- static listSupportedAPIs(spec, options) {
765
+ static listAPIs(spec, options) {
766
+ var _a;
716
767
  const paths = spec.paths;
717
768
  const result = {};
718
769
  for (const path in paths) {
719
770
  const methods = paths[path];
720
771
  for (const method in methods) {
721
- if (Utils.isSupportedApi(method, path, spec, options)) {
722
- const operationObject = methods[method];
723
- result[`${method.toUpperCase()} ${path}`] = operationObject;
772
+ const operationObject = methods[method];
773
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
774
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
775
+ result[`${method.toUpperCase()} ${path}`] = {
776
+ operation: operationObject,
777
+ isValid: validateResult.isValid,
778
+ reason: validateResult.reason,
779
+ };
724
780
  }
725
781
  }
726
782
  }
@@ -729,13 +785,13 @@ class Utils {
729
785
  static validateSpec(spec, parser, isSwaggerFile, options) {
730
786
  const errors = [];
731
787
  const warnings = [];
788
+ const apiMap = Utils.listAPIs(spec, options);
732
789
  if (isSwaggerFile) {
733
790
  warnings.push({
734
791
  type: WarningType.ConvertSwaggerToOpenAPI,
735
792
  content: ConstantString.ConvertSwaggerToOpenAPI,
736
793
  });
737
794
  }
738
- // Server validation
739
795
  const serverErrors = Utils.validateServer(spec, options);
740
796
  errors.push(...serverErrors);
741
797
  // Remote reference not supported
@@ -749,8 +805,8 @@ class Utils {
749
805
  });
750
806
  }
751
807
  // No supported API
752
- const apiMap = Utils.listSupportedAPIs(spec, options);
753
- if (Object.keys(apiMap).length === 0) {
808
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
809
+ if (validAPIs.length === 0) {
754
810
  errors.push({
755
811
  type: ErrorType.NoSupportedApi,
756
812
  content: ConstantString.NoSupportedApi,
@@ -759,8 +815,8 @@ class Utils {
759
815
  // OperationId missing
760
816
  const apisMissingOperationId = [];
761
817
  for (const key in apiMap) {
762
- const pathObjectItem = apiMap[key];
763
- if (!pathObjectItem.operationId) {
818
+ const { operation } = apiMap[key];
819
+ if (!operation.operationId) {
764
820
  apisMissingOperationId.push(key);
765
821
  }
766
822
  }
@@ -979,17 +1035,35 @@ class SpecParser {
979
1035
  if (this.apiMap !== undefined) {
980
1036
  return this.apiMap;
981
1037
  }
982
- const result = Utils.listSupportedAPIs(spec, this.options);
1038
+ const result = this.listSupportedAPIs(spec, this.options);
983
1039
  this.apiMap = result;
984
1040
  return result;
985
1041
  }
1042
+ listSupportedAPIs(spec, options) {
1043
+ var _a;
1044
+ const paths = spec.paths;
1045
+ const result = {};
1046
+ for (const path in paths) {
1047
+ const methods = paths[path];
1048
+ for (const method in methods) {
1049
+ const operationObject = methods[method];
1050
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
1051
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
1052
+ if (validateResult.isValid) {
1053
+ result[`${method.toUpperCase()} ${path}`] = operationObject;
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ return result;
1059
+ }
986
1060
  }
987
1061
 
988
1062
  // Copyright (c) Microsoft Corporation.
989
1063
  class AdaptiveCardGenerator {
990
1064
  static generateAdaptiveCard(operationItem) {
991
1065
  try {
992
- const json = Utils.getResponseJson(operationItem);
1066
+ const { json } = Utils.getResponseJson(operationItem);
993
1067
  let cardBody = [];
994
1068
  let schema = json.schema;
995
1069
  let jsonPath = "$";