@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.b015b287e.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
426
  }
383
- else if (requestBodyParamResult.requiredNum +
384
- requestBodyParamResult.optionalNum +
385
- paramResult.requiredNum +
386
- paramResult.optionalNum ===
387
- 0) {
388
- return false;
389
- }
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);
@@ -687,13 +737,7 @@ class Utils {
687
737
  }
688
738
  }
689
739
  const operationId = operationItem.operationId;
690
- const parameters = [];
691
- if (requiredParams.length !== 0) {
692
- parameters.push(...requiredParams);
693
- }
694
- else {
695
- parameters.push(optionalParams[0]);
696
- }
740
+ const parameters = [...requiredParams, ...optionalParams];
697
741
  const command = {
698
742
  context: ["compose"],
699
743
  type: "query",
@@ -702,25 +746,23 @@ class Utils {
702
746
  parameters: parameters,
703
747
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
704
748
  };
705
- let warning = undefined;
706
- if (requiredParams.length === 0 && optionalParams.length > 1) {
707
- warning = {
708
- type: WarningType.OperationOnlyContainsOptionalParam,
709
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
710
- data: operationId,
711
- };
712
- }
713
- return [command, warning];
749
+ return command;
714
750
  }
715
- static listSupportedAPIs(spec, options) {
751
+ static listAPIs(spec, options) {
752
+ var _a;
716
753
  const paths = spec.paths;
717
754
  const result = {};
718
755
  for (const path in paths) {
719
756
  const methods = paths[path];
720
757
  for (const method in methods) {
721
- if (Utils.isSupportedApi(method, path, spec, options)) {
722
- const operationObject = methods[method];
723
- result[`${method.toUpperCase()} ${path}`] = operationObject;
758
+ const operationObject = methods[method];
759
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
760
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
761
+ result[`${method.toUpperCase()} ${path}`] = {
762
+ operation: operationObject,
763
+ isValid: validateResult.isValid,
764
+ reason: validateResult.reason,
765
+ };
724
766
  }
725
767
  }
726
768
  }
@@ -729,13 +771,13 @@ class Utils {
729
771
  static validateSpec(spec, parser, isSwaggerFile, options) {
730
772
  const errors = [];
731
773
  const warnings = [];
774
+ const apiMap = Utils.listAPIs(spec, options);
732
775
  if (isSwaggerFile) {
733
776
  warnings.push({
734
777
  type: WarningType.ConvertSwaggerToOpenAPI,
735
778
  content: ConstantString.ConvertSwaggerToOpenAPI,
736
779
  });
737
780
  }
738
- // Server validation
739
781
  const serverErrors = Utils.validateServer(spec, options);
740
782
  errors.push(...serverErrors);
741
783
  // Remote reference not supported
@@ -749,8 +791,8 @@ class Utils {
749
791
  });
750
792
  }
751
793
  // No supported API
752
- const apiMap = Utils.listSupportedAPIs(spec, options);
753
- if (Object.keys(apiMap).length === 0) {
794
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
795
+ if (validAPIs.length === 0) {
754
796
  errors.push({
755
797
  type: ErrorType.NoSupportedApi,
756
798
  content: ConstantString.NoSupportedApi,
@@ -759,8 +801,8 @@ class Utils {
759
801
  // OperationId missing
760
802
  const apisMissingOperationId = [];
761
803
  for (const key in apiMap) {
762
- const pathObjectItem = apiMap[key];
763
- if (!pathObjectItem.operationId) {
804
+ const { operation } = apiMap[key];
805
+ if (!operation.operationId) {
764
806
  apisMissingOperationId.push(key);
765
807
  }
766
808
  }
@@ -894,7 +936,7 @@ class SpecParser {
894
936
  if (!operationId) {
895
937
  continue;
896
938
  }
897
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
939
+ const command = Utils.parseApiInfo(pathObjectItem, this.options);
898
940
  const apiInfo = {
899
941
  method: method,
900
942
  path: path,
@@ -903,9 +945,6 @@ class SpecParser {
903
945
  parameters: command.parameters,
904
946
  description: command.description,
905
947
  };
906
- if (warning) {
907
- apiInfo.warning = warning;
908
- }
909
948
  apiInfos.push(apiInfo);
910
949
  }
911
950
  return apiInfos;
@@ -979,17 +1018,35 @@ class SpecParser {
979
1018
  if (this.apiMap !== undefined) {
980
1019
  return this.apiMap;
981
1020
  }
982
- const result = Utils.listSupportedAPIs(spec, this.options);
1021
+ const result = this.listSupportedAPIs(spec, this.options);
983
1022
  this.apiMap = result;
984
1023
  return result;
985
1024
  }
1025
+ listSupportedAPIs(spec, options) {
1026
+ var _a;
1027
+ const paths = spec.paths;
1028
+ const result = {};
1029
+ for (const path in paths) {
1030
+ const methods = paths[path];
1031
+ for (const method in methods) {
1032
+ const operationObject = methods[method];
1033
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
1034
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
1035
+ if (validateResult.isValid) {
1036
+ result[`${method.toUpperCase()} ${path}`] = operationObject;
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ return result;
1042
+ }
986
1043
  }
987
1044
 
988
1045
  // Copyright (c) Microsoft Corporation.
989
1046
  class AdaptiveCardGenerator {
990
1047
  static generateAdaptiveCard(operationItem) {
991
1048
  try {
992
- const json = Utils.getResponseJson(operationItem);
1049
+ const { json } = Utils.getResponseJson(operationItem);
993
1050
  let cardBody = [];
994
1051
  let schema = json.schema;
995
1052
  let jsonPath = "$";