@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.
@@ -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
- }
353
- else if (requestBodyParamResult.requiredNum +
354
- requestBodyParamResult.optionalNum +
355
- paramResult.requiredNum +
356
- paramResult.optionalNum ===
357
- 0) {
358
- return false;
359
396
  }
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);
@@ -682,15 +732,21 @@ class Utils {
682
732
  }
683
733
  return [command, warning];
684
734
  }
685
- static listSupportedAPIs(spec, options) {
735
+ static listAPIs(spec, options) {
736
+ var _a;
686
737
  const paths = spec.paths;
687
738
  const result = {};
688
739
  for (const path in paths) {
689
740
  const methods = paths[path];
690
741
  for (const method in methods) {
691
- if (Utils.isSupportedApi(method, path, spec, options)) {
692
- const operationObject = methods[method];
693
- result[`${method.toUpperCase()} ${path}`] = operationObject;
742
+ const operationObject = methods[method];
743
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
744
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
745
+ result[`${method.toUpperCase()} ${path}`] = {
746
+ operation: operationObject,
747
+ isValid: validateResult.isValid,
748
+ reason: validateResult.reason,
749
+ };
694
750
  }
695
751
  }
696
752
  }
@@ -699,13 +755,13 @@ class Utils {
699
755
  static validateSpec(spec, parser, isSwaggerFile, options) {
700
756
  const errors = [];
701
757
  const warnings = [];
758
+ const apiMap = Utils.listAPIs(spec, options);
702
759
  if (isSwaggerFile) {
703
760
  warnings.push({
704
761
  type: WarningType.ConvertSwaggerToOpenAPI,
705
762
  content: ConstantString.ConvertSwaggerToOpenAPI,
706
763
  });
707
764
  }
708
- // Server validation
709
765
  const serverErrors = Utils.validateServer(spec, options);
710
766
  errors.push(...serverErrors);
711
767
  // Remote reference not supported
@@ -719,8 +775,8 @@ class Utils {
719
775
  });
720
776
  }
721
777
  // No supported API
722
- const apiMap = Utils.listSupportedAPIs(spec, options);
723
- if (Object.keys(apiMap).length === 0) {
778
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
779
+ if (validAPIs.length === 0) {
724
780
  errors.push({
725
781
  type: ErrorType.NoSupportedApi,
726
782
  content: ConstantString.NoSupportedApi,
@@ -729,8 +785,8 @@ class Utils {
729
785
  // OperationId missing
730
786
  const apisMissingOperationId = [];
731
787
  for (const key in apiMap) {
732
- const pathObjectItem = apiMap[key];
733
- if (!pathObjectItem.operationId) {
788
+ const { operation } = apiMap[key];
789
+ if (!operation.operationId) {
734
790
  apisMissingOperationId.push(key);
735
791
  }
736
792
  }
@@ -935,17 +991,35 @@ class SpecParser {
935
991
  if (this.apiMap !== undefined) {
936
992
  return this.apiMap;
937
993
  }
938
- const result = Utils.listSupportedAPIs(spec, this.options);
994
+ const result = this.listSupportedAPIs(spec, this.options);
939
995
  this.apiMap = result;
940
996
  return result;
941
997
  }
998
+ listSupportedAPIs(spec, options) {
999
+ var _a;
1000
+ const paths = spec.paths;
1001
+ const result = {};
1002
+ for (const path in paths) {
1003
+ const methods = paths[path];
1004
+ for (const method in methods) {
1005
+ const operationObject = methods[method];
1006
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
1007
+ const validateResult = Utils.isSupportedApi(method, path, spec, options);
1008
+ if (validateResult.isValid) {
1009
+ result[`${method.toUpperCase()} ${path}`] = operationObject;
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+ return result;
1015
+ }
942
1016
  }
943
1017
 
944
1018
  // Copyright (c) Microsoft Corporation.
945
1019
  class AdaptiveCardGenerator {
946
1020
  static generateAdaptiveCard(operationItem) {
947
1021
  try {
948
- const json = Utils.getResponseJson(operationItem);
1022
+ const { json } = Utils.getResponseJson(operationItem);
949
1023
  let cardBody = [];
950
1024
  let schema = json.schema;
951
1025
  let jsonPath = "$";