@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.
@@ -25,6 +25,21 @@ var ErrorType;
25
25
  ErrorType["GenerateFailed"] = "generate-failed";
26
26
  ErrorType["ValidateFailed"] = "validate-failed";
27
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";
28
43
  ErrorType["Cancelled"] = "cancelled";
29
44
  ErrorType["Unknown"] = "unknown";
30
45
  })(ErrorType || (ErrorType = {}));
@@ -175,6 +190,7 @@ class Utils {
175
190
  requiredNum: 0,
176
191
  optionalNum: 0,
177
192
  isValid: true,
193
+ reason: [],
178
194
  };
179
195
  if (!paramObject) {
180
196
  return paramResult;
@@ -184,6 +200,7 @@ class Utils {
184
200
  const schema = param.schema;
185
201
  if (isCopilot && this.hasNestedObjectInSchema(schema)) {
186
202
  paramResult.isValid = false;
203
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
187
204
  continue;
188
205
  }
189
206
  const isRequiredWithoutDefault = param.required && schema.default === undefined;
@@ -199,6 +216,7 @@ class Utils {
199
216
  if (param.in === "header" || param.in === "cookie") {
200
217
  if (isRequiredWithoutDefault) {
201
218
  paramResult.isValid = false;
219
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
202
220
  }
203
221
  continue;
204
222
  }
@@ -208,6 +226,7 @@ class Utils {
208
226
  schema.type !== "integer") {
209
227
  if (isRequiredWithoutDefault) {
210
228
  paramResult.isValid = false;
229
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
211
230
  }
212
231
  continue;
213
232
  }
@@ -228,6 +247,7 @@ class Utils {
228
247
  requiredNum: 0,
229
248
  optionalNum: 0,
230
249
  isValid: true,
250
+ reason: [],
231
251
  };
232
252
  if (Object.keys(schema).length === 0) {
233
253
  return paramResult;
@@ -235,6 +255,7 @@ class Utils {
235
255
  const isRequiredWithoutDefault = isRequired && schema.default === undefined;
236
256
  if (isCopilot && this.hasNestedObjectInSchema(schema)) {
237
257
  paramResult.isValid = false;
258
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
238
259
  return paramResult;
239
260
  }
240
261
  if (schema.type === "string" ||
@@ -259,11 +280,13 @@ class Utils {
259
280
  paramResult.requiredNum += result.requiredNum;
260
281
  paramResult.optionalNum += result.optionalNum;
261
282
  paramResult.isValid = paramResult.isValid && result.isValid;
283
+ paramResult.reason.push(...result.reason);
262
284
  }
263
285
  }
264
286
  else {
265
287
  if (isRequiredWithoutDefault && !isCopilot) {
266
288
  paramResult.isValid = false;
289
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
267
290
  }
268
291
  }
269
292
  return paramResult;
@@ -287,103 +310,123 @@ class Utils {
287
310
  */
288
311
  static isSupportedApi(method, path, spec, options) {
289
312
  var _a;
290
- const pathObj = spec.paths[path];
313
+ const result = { isValid: true, reason: [] };
291
314
  method = method.toLocaleLowerCase();
292
- if (pathObj) {
293
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
294
- const securities = pathObj[method].security;
295
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
296
- const isCopilot = options.projectType === ProjectType.Copilot;
297
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
298
- if (!isTeamsAi) {
299
- const authArray = Utils.getAuthArray(securities, spec);
300
- if (!Utils.isSupportedAuth(authArray, options)) {
301
- return false;
302
- }
303
- }
304
- const operationObject = pathObj[method];
305
- if (!options.allowMissingId && !operationObject.operationId) {
306
- return false;
307
- }
308
- const paramObject = operationObject.parameters;
309
- const requestBody = operationObject.requestBody;
310
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
311
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
312
- return false;
313
- }
314
- const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
315
- if (Object.keys(responseJson).length === 0) {
316
- return false;
317
- }
318
- // Teams AI project doesn't care about request parameters/body
319
- if (isTeamsAi) {
320
- return true;
321
- }
322
- let requestBodyParamResult = {
323
- requiredNum: 0,
324
- optionalNum: 0,
325
- isValid: true,
326
- };
327
- if (requestJsonBody) {
328
- const requestBodySchema = requestJsonBody.schema;
329
- if (isCopilot && requestBodySchema.type !== "object") {
330
- return false;
331
- }
332
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
333
- }
334
- if (!requestBodyParamResult.isValid) {
335
- return false;
336
- }
337
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
338
- if (!paramResult.isValid) {
339
- return false;
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);
340
377
  }
341
- // Copilot support arbitrary parameters
342
- if (isCopilot) {
343
- return true;
378
+ requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
379
+ if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
380
+ result.reason.push(...requestBodyParamResult.reason);
344
381
  }
345
- if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
346
- if (options.allowMultipleParameters &&
347
- requestBodyParamResult.requiredNum + paramResult.requiredNum <=
348
- ConstantString.SMERequiredParamsMaxNum) {
349
- return true;
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);
350
395
  }
351
- return false;
352
396
  }
353
- else if (requestBodyParamResult.requiredNum +
354
- requestBodyParamResult.optionalNum +
355
- paramResult.requiredNum +
356
- paramResult.optionalNum ===
357
- 0) {
358
- return false;
359
- }
360
- else {
361
- return true;
397
+ else if (totalParams === 0) {
398
+ result.reason.push(ErrorType.NoParameter);
362
399
  }
363
400
  }
364
401
  }
365
- return false;
402
+ if (result.reason.length > 0) {
403
+ result.isValid = false;
404
+ }
405
+ return result;
366
406
  }
367
407
  static isSupportedAuth(authSchemeArray, options) {
368
408
  if (authSchemeArray.length === 0) {
369
- return true;
409
+ return { isValid: true, reason: [] };
370
410
  }
371
411
  if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
372
412
  // Currently we don't support multiple auth in one operation
373
413
  if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
374
- return false;
414
+ return {
415
+ isValid: false,
416
+ reason: [ErrorType.MultipleAuthNotSupported],
417
+ };
375
418
  }
376
419
  for (const auths of authSchemeArray) {
377
420
  if (auths.length === 1) {
378
421
  if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
379
422
  (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
380
423
  (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
381
- return true;
424
+ return { isValid: true, reason: [] };
382
425
  }
383
426
  }
384
427
  }
385
428
  }
386
- return false;
429
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
387
430
  }
388
431
  static isBearerTokenAuth(authScheme) {
389
432
  return authScheme.type === "http" && authScheme.scheme === "bearer";
@@ -426,11 +469,17 @@ class Utils {
426
469
  static getResponseJson(operationObject, isTeamsAiProject = false) {
427
470
  var _a, _b;
428
471
  let json = {};
472
+ let multipleMediaType = false;
429
473
  for (const code of ConstantString.ResponseCodeFor20X) {
430
474
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
431
475
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
476
+ multipleMediaType = false;
432
477
  json = responseObject.content["application/json"];
433
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
478
+ if (Utils.containMultipleMediaTypes(responseObject)) {
479
+ multipleMediaType = true;
480
+ if (isTeamsAiProject) {
481
+ break;
482
+ }
434
483
  json = {};
435
484
  }
436
485
  else {
@@ -438,7 +487,7 @@ class Utils {
438
487
  }
439
488
  }
440
489
  }
441
- return json;
490
+ return { json, multipleMediaType };
442
491
  }
443
492
  static convertPathToCamelCase(path) {
444
493
  const pathSegments = path.split(/[./{]/);
@@ -458,10 +507,10 @@ class Utils {
458
507
  return undefined;
459
508
  }
460
509
  }
461
- static resolveServerUrl(url) {
510
+ static resolveEnv(str) {
462
511
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
463
- let matches = placeHolderReg.exec(url);
464
- let newUrl = url;
512
+ let matches = placeHolderReg.exec(str);
513
+ let newStr = str;
465
514
  while (matches != null) {
466
515
  const envVar = matches[1];
467
516
  const envVal = process.env[envVar];
@@ -469,17 +518,17 @@ class Utils {
469
518
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
470
519
  }
471
520
  else {
472
- newUrl = newUrl.replace(matches[0], envVal);
521
+ newStr = newStr.replace(matches[0], envVal);
473
522
  }
474
- matches = placeHolderReg.exec(url);
523
+ matches = placeHolderReg.exec(str);
475
524
  }
476
- return newUrl;
525
+ return newStr;
477
526
  }
478
527
  static checkServerUrl(servers) {
479
528
  const errors = [];
480
529
  let serverUrl;
481
530
  try {
482
- serverUrl = Utils.resolveServerUrl(servers[0].url);
531
+ serverUrl = Utils.resolveEnv(servers[0].url);
483
532
  }
484
533
  catch (err) {
485
534
  errors.push({
@@ -510,6 +559,7 @@ class Utils {
510
559
  return errors;
511
560
  }
512
561
  static validateServer(spec, options) {
562
+ var _a;
513
563
  const errors = [];
514
564
  let hasTopLevelServers = false;
515
565
  let hasPathLevelServers = false;
@@ -530,7 +580,7 @@ class Utils {
530
580
  }
531
581
  for (const method in methods) {
532
582
  const operationObject = methods[method];
533
- if (Utils.isSupportedApi(method, path, spec, options)) {
583
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
534
584
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
535
585
  hasOperationLevelServers = true;
536
586
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -657,13 +707,7 @@ class Utils {
657
707
  }
658
708
  }
659
709
  const operationId = operationItem.operationId;
660
- const parameters = [];
661
- if (requiredParams.length !== 0) {
662
- parameters.push(...requiredParams);
663
- }
664
- else {
665
- parameters.push(optionalParams[0]);
666
- }
710
+ const parameters = [...requiredParams, ...optionalParams];
667
711
  const command = {
668
712
  context: ["compose"],
669
713
  type: "query",
@@ -672,25 +716,23 @@ class Utils {
672
716
  parameters: parameters,
673
717
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
674
718
  };
675
- let warning = undefined;
676
- if (requiredParams.length === 0 && optionalParams.length > 1) {
677
- warning = {
678
- type: WarningType.OperationOnlyContainsOptionalParam,
679
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
680
- data: operationId,
681
- };
682
- }
683
- return [command, warning];
719
+ return command;
684
720
  }
685
- static listSupportedAPIs(spec, options) {
721
+ static listAPIs(spec, options) {
722
+ var _a;
686
723
  const paths = spec.paths;
687
724
  const result = {};
688
725
  for (const path in paths) {
689
726
  const methods = paths[path];
690
727
  for (const method in methods) {
691
- if (Utils.isSupportedApi(method, path, spec, options)) {
692
- const operationObject = methods[method];
693
- result[`${method.toUpperCase()} ${path}`] = operationObject;
728
+ const operationObject = methods[method];
729
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
730
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
731
+ result[`${method.toUpperCase()} ${path}`] = {
732
+ operation: operationObject,
733
+ isValid: validateResult.isValid,
734
+ reason: validateResult.reason,
735
+ };
694
736
  }
695
737
  }
696
738
  }
@@ -699,13 +741,13 @@ class Utils {
699
741
  static validateSpec(spec, parser, isSwaggerFile, options) {
700
742
  const errors = [];
701
743
  const warnings = [];
744
+ const apiMap = Utils.listAPIs(spec, options);
702
745
  if (isSwaggerFile) {
703
746
  warnings.push({
704
747
  type: WarningType.ConvertSwaggerToOpenAPI,
705
748
  content: ConstantString.ConvertSwaggerToOpenAPI,
706
749
  });
707
750
  }
708
- // Server validation
709
751
  const serverErrors = Utils.validateServer(spec, options);
710
752
  errors.push(...serverErrors);
711
753
  // Remote reference not supported
@@ -719,8 +761,8 @@ class Utils {
719
761
  });
720
762
  }
721
763
  // No supported API
722
- const apiMap = Utils.listSupportedAPIs(spec, options);
723
- if (Object.keys(apiMap).length === 0) {
764
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
765
+ if (validAPIs.length === 0) {
724
766
  errors.push({
725
767
  type: ErrorType.NoSupportedApi,
726
768
  content: ConstantString.NoSupportedApi,
@@ -729,8 +771,8 @@ class Utils {
729
771
  // OperationId missing
730
772
  const apisMissingOperationId = [];
731
773
  for (const key in apiMap) {
732
- const pathObjectItem = apiMap[key];
733
- if (!pathObjectItem.operationId) {
774
+ const { operation } = apiMap[key];
775
+ if (!operation.operationId) {
734
776
  apisMissingOperationId.push(key);
735
777
  }
736
778
  }
@@ -861,7 +903,7 @@ class SpecParser {
861
903
  if (!operationId) {
862
904
  continue;
863
905
  }
864
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
906
+ const command = Utils.parseApiInfo(pathObjectItem, this.options);
865
907
  const apiInfo = {
866
908
  method: method,
867
909
  path: path,
@@ -870,9 +912,6 @@ class SpecParser {
870
912
  parameters: command.parameters,
871
913
  description: command.description,
872
914
  };
873
- if (warning) {
874
- apiInfo.warning = warning;
875
- }
876
915
  apiInfos.push(apiInfo);
877
916
  }
878
917
  return apiInfos;
@@ -935,17 +974,35 @@ class SpecParser {
935
974
  if (this.apiMap !== undefined) {
936
975
  return this.apiMap;
937
976
  }
938
- const result = Utils.listSupportedAPIs(spec, this.options);
977
+ const result = this.listSupportedAPIs(spec, this.options);
939
978
  this.apiMap = result;
940
979
  return result;
941
980
  }
981
+ listSupportedAPIs(spec, options) {
982
+ var _a;
983
+ const paths = spec.paths;
984
+ const result = {};
985
+ for (const path in paths) {
986
+ const methods = paths[path];
987
+ for (const method in methods) {
988
+ const operationObject = methods[method];
989
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
990
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
991
+ if (validateResult.isValid) {
992
+ result[`${method.toUpperCase()} ${path}`] = operationObject;
993
+ }
994
+ }
995
+ }
996
+ }
997
+ return result;
998
+ }
942
999
  }
943
1000
 
944
1001
  // Copyright (c) Microsoft Corporation.
945
1002
  class AdaptiveCardGenerator {
946
1003
  static generateAdaptiveCard(operationItem) {
947
1004
  try {
948
- const json = Utils.getResponseJson(operationItem);
1005
+ const { json } = Utils.getResponseJson(operationItem);
949
1006
  let cardBody = [];
950
1007
  let schema = json.schema;
951
1008
  let jsonPath = "$";