@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.42af26ca6.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.
@@ -46,6 +46,7 @@ var ErrorType;
46
46
  ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
47
47
  ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
48
48
  ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
49
+ ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
49
50
  ErrorType["ListFailed"] = "list-failed";
50
51
  ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
51
52
  ErrorType["FilterSpecFailed"] = "filter-spec-failed";
@@ -54,6 +55,21 @@ var ErrorType;
54
55
  ErrorType["GenerateFailed"] = "generate-failed";
55
56
  ErrorType["ValidateFailed"] = "validate-failed";
56
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";
57
73
  ErrorType["Cancelled"] = "cancelled";
58
74
  ErrorType["Unknown"] = "unknown";
59
75
  })(ErrorType || (ErrorType = {}));
@@ -109,6 +125,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
109
125
  ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
110
126
  ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
111
127
  ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
128
+ ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
112
129
  ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
113
130
  ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
114
131
  ConstantString.WrappedCardVersion = "devPreview";
@@ -198,221 +215,9 @@ class Utils {
198
215
  }
199
216
  return false;
200
217
  }
201
- static checkParameters(paramObject, isCopilot) {
202
- const paramResult = {
203
- requiredNum: 0,
204
- optionalNum: 0,
205
- isValid: true,
206
- };
207
- if (!paramObject) {
208
- return paramResult;
209
- }
210
- for (let i = 0; i < paramObject.length; i++) {
211
- const param = paramObject[i];
212
- const schema = param.schema;
213
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
214
- paramResult.isValid = false;
215
- continue;
216
- }
217
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
218
- if (isCopilot) {
219
- if (isRequiredWithoutDefault) {
220
- paramResult.requiredNum = paramResult.requiredNum + 1;
221
- }
222
- else {
223
- paramResult.optionalNum = paramResult.optionalNum + 1;
224
- }
225
- continue;
226
- }
227
- if (param.in === "header" || param.in === "cookie") {
228
- if (isRequiredWithoutDefault) {
229
- paramResult.isValid = false;
230
- }
231
- continue;
232
- }
233
- if (schema.type !== "boolean" &&
234
- schema.type !== "string" &&
235
- schema.type !== "number" &&
236
- schema.type !== "integer") {
237
- if (isRequiredWithoutDefault) {
238
- paramResult.isValid = false;
239
- }
240
- continue;
241
- }
242
- if (param.in === "query" || param.in === "path") {
243
- if (isRequiredWithoutDefault) {
244
- paramResult.requiredNum = paramResult.requiredNum + 1;
245
- }
246
- else {
247
- paramResult.optionalNum = paramResult.optionalNum + 1;
248
- }
249
- }
250
- }
251
- return paramResult;
252
- }
253
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
254
- var _a;
255
- const paramResult = {
256
- requiredNum: 0,
257
- optionalNum: 0,
258
- isValid: true,
259
- };
260
- if (Object.keys(schema).length === 0) {
261
- return paramResult;
262
- }
263
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
264
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
265
- paramResult.isValid = false;
266
- return paramResult;
267
- }
268
- if (schema.type === "string" ||
269
- schema.type === "integer" ||
270
- schema.type === "boolean" ||
271
- schema.type === "number") {
272
- if (isRequiredWithoutDefault) {
273
- paramResult.requiredNum = paramResult.requiredNum + 1;
274
- }
275
- else {
276
- paramResult.optionalNum = paramResult.optionalNum + 1;
277
- }
278
- }
279
- else if (schema.type === "object") {
280
- const { properties } = schema;
281
- for (const property in properties) {
282
- let isRequired = false;
283
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
284
- isRequired = true;
285
- }
286
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
287
- paramResult.requiredNum += result.requiredNum;
288
- paramResult.optionalNum += result.optionalNum;
289
- paramResult.isValid = paramResult.isValid && result.isValid;
290
- }
291
- }
292
- else {
293
- if (isRequiredWithoutDefault && !isCopilot) {
294
- paramResult.isValid = false;
295
- }
296
- }
297
- return paramResult;
298
- }
299
218
  static containMultipleMediaTypes(bodyObject) {
300
219
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
301
220
  }
302
- /**
303
- * Checks if the given API is supported.
304
- * @param {string} method - The HTTP method of the API.
305
- * @param {string} path - The path of the API.
306
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
307
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
308
- * @description The following APIs are supported:
309
- * 1. only support Get/Post operation without auth property
310
- * 2. parameter inside query or path only support string, number, boolean and integer
311
- * 3. parameter inside post body only support string, number, boolean, integer and object
312
- * 4. request body + required parameters <= 1
313
- * 5. response body should be “application/json” and not empty, and response code should be 20X
314
- * 6. only support request body with “application/json” content type
315
- */
316
- static isSupportedApi(method, path, spec, options) {
317
- var _a;
318
- const pathObj = spec.paths[path];
319
- method = method.toLocaleLowerCase();
320
- if (pathObj) {
321
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
322
- const securities = pathObj[method].security;
323
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
324
- const isCopilot = options.projectType === ProjectType.Copilot;
325
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
326
- if (!isTeamsAi) {
327
- const authArray = Utils.getAuthArray(securities, spec);
328
- if (!Utils.isSupportedAuth(authArray, options)) {
329
- return false;
330
- }
331
- }
332
- const operationObject = pathObj[method];
333
- if (!options.allowMissingId && !operationObject.operationId) {
334
- return false;
335
- }
336
- const paramObject = operationObject.parameters;
337
- const requestBody = operationObject.requestBody;
338
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
339
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
340
- return false;
341
- }
342
- const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
343
- if (Object.keys(responseJson).length === 0) {
344
- return false;
345
- }
346
- // Teams AI project doesn't care about request parameters/body
347
- if (isTeamsAi) {
348
- return true;
349
- }
350
- let requestBodyParamResult = {
351
- requiredNum: 0,
352
- optionalNum: 0,
353
- isValid: true,
354
- };
355
- if (requestJsonBody) {
356
- const requestBodySchema = requestJsonBody.schema;
357
- if (isCopilot && requestBodySchema.type !== "object") {
358
- return false;
359
- }
360
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
361
- }
362
- if (!requestBodyParamResult.isValid) {
363
- return false;
364
- }
365
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
366
- if (!paramResult.isValid) {
367
- return false;
368
- }
369
- // Copilot support arbitrary parameters
370
- if (isCopilot) {
371
- return true;
372
- }
373
- if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
374
- if (options.allowMultipleParameters &&
375
- requestBodyParamResult.requiredNum + paramResult.requiredNum <=
376
- ConstantString.SMERequiredParamsMaxNum) {
377
- return true;
378
- }
379
- return false;
380
- }
381
- else if (requestBodyParamResult.requiredNum +
382
- requestBodyParamResult.optionalNum +
383
- paramResult.requiredNum +
384
- paramResult.optionalNum ===
385
- 0) {
386
- return false;
387
- }
388
- else {
389
- return true;
390
- }
391
- }
392
- }
393
- return false;
394
- }
395
- static isSupportedAuth(authSchemeArray, options) {
396
- if (authSchemeArray.length === 0) {
397
- return true;
398
- }
399
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
400
- // Currently we don't support multiple auth in one operation
401
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
402
- return false;
403
- }
404
- for (const auths of authSchemeArray) {
405
- if (auths.length === 1) {
406
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
407
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
408
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
409
- return true;
410
- }
411
- }
412
- }
413
- }
414
- return false;
415
- }
416
221
  static isBearerTokenAuth(authScheme) {
417
222
  return authScheme.type === "http" && authScheme.scheme === "bearer";
418
223
  }
@@ -420,10 +225,9 @@ class Utils {
420
225
  return authScheme.type === "apiKey";
421
226
  }
422
227
  static isOAuthWithAuthCodeFlow(authScheme) {
423
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
424
- return true;
425
- }
426
- return false;
228
+ return !!(authScheme.type === "oauth2" &&
229
+ authScheme.flows &&
230
+ authScheme.flows.authorizationCode);
427
231
  }
428
232
  static getAuthArray(securities, spec) {
429
233
  var _a;
@@ -451,14 +255,17 @@ class Utils {
451
255
  static updateFirstLetter(str) {
452
256
  return str.charAt(0).toUpperCase() + str.slice(1);
453
257
  }
454
- static getResponseJson(operationObject, isTeamsAiProject = false) {
258
+ static getResponseJson(operationObject) {
455
259
  var _a, _b;
456
260
  let json = {};
261
+ let multipleMediaType = false;
457
262
  for (const code of ConstantString.ResponseCodeFor20X) {
458
263
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
459
264
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
265
+ multipleMediaType = false;
460
266
  json = responseObject.content["application/json"];
461
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
267
+ if (Utils.containMultipleMediaTypes(responseObject)) {
268
+ multipleMediaType = true;
462
269
  json = {};
463
270
  }
464
271
  else {
@@ -466,7 +273,7 @@ class Utils {
466
273
  }
467
274
  }
468
275
  }
469
- return json;
276
+ return { json, multipleMediaType };
470
277
  }
471
278
  static convertPathToCamelCase(path) {
472
279
  const pathSegments = path.split(/[./{]/);
@@ -486,10 +293,10 @@ class Utils {
486
293
  return undefined;
487
294
  }
488
295
  }
489
- static resolveServerUrl(url) {
296
+ static resolveEnv(str) {
490
297
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
491
- let matches = placeHolderReg.exec(url);
492
- let newUrl = url;
298
+ let matches = placeHolderReg.exec(str);
299
+ let newStr = str;
493
300
  while (matches != null) {
494
301
  const envVar = matches[1];
495
302
  const envVal = process.env[envVar];
@@ -497,17 +304,17 @@ class Utils {
497
304
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
498
305
  }
499
306
  else {
500
- newUrl = newUrl.replace(matches[0], envVal);
307
+ newStr = newStr.replace(matches[0], envVal);
501
308
  }
502
- matches = placeHolderReg.exec(url);
309
+ matches = placeHolderReg.exec(str);
503
310
  }
504
- return newUrl;
311
+ return newStr;
505
312
  }
506
313
  static checkServerUrl(servers) {
507
314
  const errors = [];
508
315
  let serverUrl;
509
316
  try {
510
- serverUrl = Utils.resolveServerUrl(servers[0].url);
317
+ serverUrl = Utils.resolveEnv(servers[0].url);
511
318
  }
512
319
  catch (err) {
513
320
  errors.push({
@@ -538,6 +345,7 @@ class Utils {
538
345
  return errors;
539
346
  }
540
347
  static validateServer(spec, options) {
348
+ var _a;
541
349
  const errors = [];
542
350
  let hasTopLevelServers = false;
543
351
  let hasPathLevelServers = false;
@@ -558,7 +366,7 @@ class Utils {
558
366
  }
559
367
  for (const method in methods) {
560
368
  const operationObject = methods[method];
561
- if (Utils.isSupportedApi(method, path, spec, options)) {
369
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
562
370
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
563
371
  hasOperationLevelServers = true;
564
372
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -685,13 +493,7 @@ class Utils {
685
493
  }
686
494
  }
687
495
  const operationId = operationItem.operationId;
688
- const parameters = [];
689
- if (requiredParams.length !== 0) {
690
- parameters.push(...requiredParams);
691
- }
692
- else {
693
- parameters.push(optionalParams[0]);
694
- }
496
+ const parameters = [...requiredParams, ...optionalParams];
695
497
  const command = {
696
498
  context: ["compose"],
697
499
  type: "query",
@@ -700,117 +502,526 @@ class Utils {
700
502
  parameters: parameters,
701
503
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
702
504
  };
703
- let warning = undefined;
704
- if (requiredParams.length === 0 && optionalParams.length > 1) {
705
- warning = {
706
- type: WarningType.OperationOnlyContainsOptionalParam,
707
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
708
- data: operationId,
709
- };
505
+ return command;
506
+ }
507
+ static format(str, ...args) {
508
+ let index = 0;
509
+ return str.replace(/%s/g, () => {
510
+ const arg = args[index++];
511
+ return arg !== undefined ? arg : "";
512
+ });
513
+ }
514
+ static getSafeRegistrationIdEnvName(authName) {
515
+ if (!authName) {
516
+ return "";
710
517
  }
711
- return [command, warning];
518
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
519
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
520
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
521
+ }
522
+ return safeRegistrationIdEnvName;
712
523
  }
713
- static listSupportedAPIs(spec, options) {
714
- const paths = spec.paths;
524
+ static getServerObject(spec, method, path) {
525
+ const pathObj = spec.paths[path];
526
+ const operationObject = pathObj[method];
527
+ const rootServer = spec.servers && spec.servers[0];
528
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
529
+ const operationServer = operationObject.servers && operationObject.servers[0];
530
+ const serverUrl = operationServer || methodServer || rootServer;
531
+ return serverUrl;
532
+ }
533
+ }
534
+
535
+ // Copyright (c) Microsoft Corporation.
536
+ class Validator {
537
+ listAPIs() {
538
+ var _a;
539
+ if (this.apiMap) {
540
+ return this.apiMap;
541
+ }
542
+ const paths = this.spec.paths;
715
543
  const result = {};
716
544
  for (const path in paths) {
717
545
  const methods = paths[path];
718
546
  for (const method in methods) {
719
- if (Utils.isSupportedApi(method, path, spec, options)) {
720
- const operationObject = methods[method];
721
- result[`${method.toUpperCase()} ${path}`] = operationObject;
547
+ const operationObject = methods[method];
548
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
549
+ const validateResult = this.validateAPI(method, path);
550
+ result[`${method.toUpperCase()} ${path}`] = {
551
+ operation: operationObject,
552
+ isValid: validateResult.isValid,
553
+ reason: validateResult.reason,
554
+ };
722
555
  }
723
556
  }
724
557
  }
558
+ this.apiMap = result;
725
559
  return result;
726
560
  }
727
- static validateSpec(spec, parser, isSwaggerFile, options) {
728
- const errors = [];
729
- const warnings = [];
730
- if (isSwaggerFile) {
731
- warnings.push({
732
- type: WarningType.ConvertSwaggerToOpenAPI,
733
- content: ConstantString.ConvertSwaggerToOpenAPI,
561
+ validateSpecVersion() {
562
+ const result = { errors: [], warnings: [] };
563
+ if (this.spec.openapi >= "3.1.0") {
564
+ result.errors.push({
565
+ type: ErrorType.SpecVersionNotSupported,
566
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
567
+ data: this.spec.openapi,
734
568
  });
735
569
  }
736
- // Server validation
737
- const serverErrors = Utils.validateServer(spec, options);
738
- errors.push(...serverErrors);
739
- // Remote reference not supported
740
- const refPaths = parser.$refs.paths();
741
- // refPaths [0] is the current spec file path
742
- if (refPaths.length > 1) {
743
- errors.push({
744
- type: ErrorType.RemoteRefNotSupported,
745
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
746
- data: refPaths,
747
- });
748
- }
749
- // No supported API
750
- const apiMap = Utils.listSupportedAPIs(spec, options);
751
- if (Object.keys(apiMap).length === 0) {
752
- errors.push({
570
+ return result;
571
+ }
572
+ validateSpecServer() {
573
+ const result = { errors: [], warnings: [] };
574
+ const serverErrors = Utils.validateServer(this.spec, this.options);
575
+ result.errors.push(...serverErrors);
576
+ return result;
577
+ }
578
+ validateSpecNoSupportAPI() {
579
+ const result = { errors: [], warnings: [] };
580
+ const apiMap = this.listAPIs();
581
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
582
+ if (validAPIs.length === 0) {
583
+ result.errors.push({
753
584
  type: ErrorType.NoSupportedApi,
754
585
  content: ConstantString.NoSupportedApi,
755
586
  });
756
587
  }
588
+ return result;
589
+ }
590
+ validateSpecOperationId() {
591
+ const result = { errors: [], warnings: [] };
592
+ const apiMap = this.listAPIs();
757
593
  // OperationId missing
758
594
  const apisMissingOperationId = [];
759
595
  for (const key in apiMap) {
760
- const pathObjectItem = apiMap[key];
761
- if (!pathObjectItem.operationId) {
596
+ const { operation } = apiMap[key];
597
+ if (!operation.operationId) {
762
598
  apisMissingOperationId.push(key);
763
599
  }
764
600
  }
765
601
  if (apisMissingOperationId.length > 0) {
766
- warnings.push({
602
+ result.warnings.push({
767
603
  type: WarningType.OperationIdMissing,
768
604
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
769
605
  data: apisMissingOperationId,
770
606
  });
771
607
  }
772
- let status = ValidationStatus.Valid;
773
- if (warnings.length > 0 && errors.length === 0) {
774
- status = ValidationStatus.Warning;
608
+ return result;
609
+ }
610
+ validateMethodAndPath(method, path) {
611
+ const result = { isValid: true, reason: [] };
612
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
613
+ result.isValid = false;
614
+ result.reason.push(ErrorType.MethodNotAllowed);
615
+ return result;
775
616
  }
776
- else if (errors.length > 0) {
777
- status = ValidationStatus.Error;
617
+ const pathObj = this.spec.paths[path];
618
+ if (!pathObj || !pathObj[method]) {
619
+ result.isValid = false;
620
+ result.reason.push(ErrorType.UrlPathNotExist);
621
+ return result;
778
622
  }
779
- return {
780
- status,
781
- warnings,
782
- errors,
783
- };
623
+ return result;
784
624
  }
785
- static format(str, ...args) {
786
- let index = 0;
787
- return str.replace(/%s/g, () => {
788
- const arg = args[index++];
789
- return arg !== undefined ? arg : "";
790
- });
625
+ validateResponse(method, path) {
626
+ const result = { isValid: true, reason: [] };
627
+ const operationObject = this.spec.paths[path][method];
628
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
629
+ // only support response body only contains “application/json” content type
630
+ if (multipleMediaType) {
631
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
632
+ }
633
+ else if (Object.keys(json).length === 0) {
634
+ // response body should not be empty
635
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
636
+ }
637
+ return result;
791
638
  }
792
- static getSafeRegistrationIdEnvName(authName) {
793
- if (!authName) {
794
- return "";
639
+ validateServer(method, path) {
640
+ const result = { isValid: true, reason: [] };
641
+ const serverObj = Utils.getServerObject(this.spec, method, path);
642
+ if (!serverObj) {
643
+ // should contain server URL
644
+ result.reason.push(ErrorType.NoServerInformation);
795
645
  }
796
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
797
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
798
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
646
+ else {
647
+ // server url should be absolute url with https protocol
648
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
649
+ result.reason.push(...serverValidateResult.map((item) => item.type));
799
650
  }
800
- return safeRegistrationIdEnvName;
651
+ return result;
801
652
  }
802
- static getAllAPICount(spec) {
803
- let count = 0;
804
- const paths = spec.paths;
805
- for (const path in paths) {
806
- const methods = paths[path];
807
- for (const method in methods) {
808
- if (ConstantString.AllOperationMethods.includes(method)) {
809
- count++;
653
+ validateAuth(method, path) {
654
+ const pathObj = this.spec.paths[path];
655
+ const operationObject = pathObj[method];
656
+ const securities = operationObject.security;
657
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
658
+ if (authSchemeArray.length === 0) {
659
+ return { isValid: true, reason: [] };
660
+ }
661
+ if (this.options.allowAPIKeyAuth ||
662
+ this.options.allowOauth2 ||
663
+ this.options.allowBearerTokenAuth) {
664
+ // Currently we don't support multiple auth in one operation
665
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
666
+ return {
667
+ isValid: false,
668
+ reason: [ErrorType.MultipleAuthNotSupported],
669
+ };
670
+ }
671
+ for (const auths of authSchemeArray) {
672
+ if (auths.length === 1) {
673
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
674
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
675
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
676
+ return { isValid: true, reason: [] };
677
+ }
810
678
  }
811
679
  }
812
680
  }
813
- return count;
681
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
682
+ }
683
+ checkPostBodySchema(schema, isRequired = false) {
684
+ var _a;
685
+ const paramResult = {
686
+ requiredNum: 0,
687
+ optionalNum: 0,
688
+ isValid: true,
689
+ reason: [],
690
+ };
691
+ if (Object.keys(schema).length === 0) {
692
+ return paramResult;
693
+ }
694
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
695
+ const isCopilot = this.projectType === ProjectType.Copilot;
696
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
697
+ paramResult.isValid = false;
698
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
699
+ return paramResult;
700
+ }
701
+ if (schema.type === "string" ||
702
+ schema.type === "integer" ||
703
+ schema.type === "boolean" ||
704
+ schema.type === "number") {
705
+ if (isRequiredWithoutDefault) {
706
+ paramResult.requiredNum = paramResult.requiredNum + 1;
707
+ }
708
+ else {
709
+ paramResult.optionalNum = paramResult.optionalNum + 1;
710
+ }
711
+ }
712
+ else if (schema.type === "object") {
713
+ const { properties } = schema;
714
+ for (const property in properties) {
715
+ let isRequired = false;
716
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
717
+ isRequired = true;
718
+ }
719
+ const result = this.checkPostBodySchema(properties[property], isRequired);
720
+ paramResult.requiredNum += result.requiredNum;
721
+ paramResult.optionalNum += result.optionalNum;
722
+ paramResult.isValid = paramResult.isValid && result.isValid;
723
+ paramResult.reason.push(...result.reason);
724
+ }
725
+ }
726
+ else {
727
+ if (isRequiredWithoutDefault && !isCopilot) {
728
+ paramResult.isValid = false;
729
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
730
+ }
731
+ }
732
+ return paramResult;
733
+ }
734
+ checkParamSchema(paramObject) {
735
+ const paramResult = {
736
+ requiredNum: 0,
737
+ optionalNum: 0,
738
+ isValid: true,
739
+ reason: [],
740
+ };
741
+ if (!paramObject) {
742
+ return paramResult;
743
+ }
744
+ const isCopilot = this.projectType === ProjectType.Copilot;
745
+ for (let i = 0; i < paramObject.length; i++) {
746
+ const param = paramObject[i];
747
+ const schema = param.schema;
748
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
749
+ paramResult.isValid = false;
750
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
751
+ continue;
752
+ }
753
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
754
+ if (isCopilot) {
755
+ if (isRequiredWithoutDefault) {
756
+ paramResult.requiredNum = paramResult.requiredNum + 1;
757
+ }
758
+ else {
759
+ paramResult.optionalNum = paramResult.optionalNum + 1;
760
+ }
761
+ continue;
762
+ }
763
+ if (param.in === "header" || param.in === "cookie") {
764
+ if (isRequiredWithoutDefault) {
765
+ paramResult.isValid = false;
766
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
767
+ }
768
+ continue;
769
+ }
770
+ if (schema.type !== "boolean" &&
771
+ schema.type !== "string" &&
772
+ schema.type !== "number" &&
773
+ schema.type !== "integer") {
774
+ if (isRequiredWithoutDefault) {
775
+ paramResult.isValid = false;
776
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
777
+ }
778
+ continue;
779
+ }
780
+ if (param.in === "query" || param.in === "path") {
781
+ if (isRequiredWithoutDefault) {
782
+ paramResult.requiredNum = paramResult.requiredNum + 1;
783
+ }
784
+ else {
785
+ paramResult.optionalNum = paramResult.optionalNum + 1;
786
+ }
787
+ }
788
+ }
789
+ return paramResult;
790
+ }
791
+ hasNestedObjectInSchema(schema) {
792
+ if (schema.type === "object") {
793
+ for (const property in schema.properties) {
794
+ const nestedSchema = schema.properties[property];
795
+ if (nestedSchema.type === "object") {
796
+ return true;
797
+ }
798
+ }
799
+ }
800
+ return false;
801
+ }
802
+ }
803
+
804
+ // Copyright (c) Microsoft Corporation.
805
+ class CopilotValidator extends Validator {
806
+ constructor(spec, options) {
807
+ super();
808
+ this.projectType = ProjectType.Copilot;
809
+ this.options = options;
810
+ this.spec = spec;
811
+ }
812
+ validateSpec() {
813
+ const result = { errors: [], warnings: [] };
814
+ // validate spec version
815
+ let validationResult = this.validateSpecVersion();
816
+ result.errors.push(...validationResult.errors);
817
+ // validate spec server
818
+ validationResult = this.validateSpecServer();
819
+ result.errors.push(...validationResult.errors);
820
+ // validate no supported API
821
+ validationResult = this.validateSpecNoSupportAPI();
822
+ result.errors.push(...validationResult.errors);
823
+ // validate operationId missing
824
+ validationResult = this.validateSpecOperationId();
825
+ result.warnings.push(...validationResult.warnings);
826
+ return result;
827
+ }
828
+ validateAPI(method, path) {
829
+ const result = { isValid: true, reason: [] };
830
+ method = method.toLocaleLowerCase();
831
+ // validate method and path
832
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
833
+ if (!methodAndPathResult.isValid) {
834
+ return methodAndPathResult;
835
+ }
836
+ const operationObject = this.spec.paths[path][method];
837
+ // validate auth
838
+ const authCheckResult = this.validateAuth(method, path);
839
+ result.reason.push(...authCheckResult.reason);
840
+ // validate operationId
841
+ if (!this.options.allowMissingId && !operationObject.operationId) {
842
+ result.reason.push(ErrorType.MissingOperationId);
843
+ }
844
+ // validate server
845
+ const validateServerResult = this.validateServer(method, path);
846
+ result.reason.push(...validateServerResult.reason);
847
+ // validate response
848
+ const validateResponseResult = this.validateResponse(method, path);
849
+ result.reason.push(...validateResponseResult.reason);
850
+ // validate requestBody
851
+ const requestBody = operationObject.requestBody;
852
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
853
+ if (Utils.containMultipleMediaTypes(requestBody)) {
854
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
855
+ }
856
+ if (requestJsonBody) {
857
+ const requestBodySchema = requestJsonBody.schema;
858
+ if (requestBodySchema.type !== "object") {
859
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
860
+ }
861
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
862
+ result.reason.push(...requestBodyParamResult.reason);
863
+ }
864
+ // validate parameters
865
+ const paramObject = operationObject.parameters;
866
+ const paramResult = this.checkParamSchema(paramObject);
867
+ result.reason.push(...paramResult.reason);
868
+ if (result.reason.length > 0) {
869
+ result.isValid = false;
870
+ }
871
+ return result;
872
+ }
873
+ }
874
+
875
+ // Copyright (c) Microsoft Corporation.
876
+ class SMEValidator extends Validator {
877
+ constructor(spec, options) {
878
+ super();
879
+ this.projectType = ProjectType.SME;
880
+ this.options = options;
881
+ this.spec = spec;
882
+ }
883
+ validateSpec() {
884
+ const result = { errors: [], warnings: [] };
885
+ // validate spec version
886
+ let validationResult = this.validateSpecVersion();
887
+ result.errors.push(...validationResult.errors);
888
+ // validate spec server
889
+ validationResult = this.validateSpecServer();
890
+ result.errors.push(...validationResult.errors);
891
+ // validate no supported API
892
+ validationResult = this.validateSpecNoSupportAPI();
893
+ result.errors.push(...validationResult.errors);
894
+ // validate operationId missing
895
+ validationResult = this.validateSpecOperationId();
896
+ result.warnings.push(...validationResult.warnings);
897
+ return result;
898
+ }
899
+ validateAPI(method, path) {
900
+ const result = { isValid: true, reason: [] };
901
+ method = method.toLocaleLowerCase();
902
+ // validate method and path
903
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
904
+ if (!methodAndPathResult.isValid) {
905
+ return methodAndPathResult;
906
+ }
907
+ const operationObject = this.spec.paths[path][method];
908
+ // validate auth
909
+ const authCheckResult = this.validateAuth(method, path);
910
+ result.reason.push(...authCheckResult.reason);
911
+ // validate operationId
912
+ if (!this.options.allowMissingId && !operationObject.operationId) {
913
+ result.reason.push(ErrorType.MissingOperationId);
914
+ }
915
+ // validate server
916
+ const validateServerResult = this.validateServer(method, path);
917
+ result.reason.push(...validateServerResult.reason);
918
+ // validate response
919
+ const validateResponseResult = this.validateResponse(method, path);
920
+ result.reason.push(...validateResponseResult.reason);
921
+ let postBodyResult = {
922
+ requiredNum: 0,
923
+ optionalNum: 0,
924
+ isValid: true,
925
+ reason: [],
926
+ };
927
+ // validate requestBody
928
+ const requestBody = operationObject.requestBody;
929
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
930
+ if (Utils.containMultipleMediaTypes(requestBody)) {
931
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
932
+ }
933
+ if (requestJsonBody) {
934
+ const requestBodySchema = requestJsonBody.schema;
935
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
936
+ result.reason.push(...postBodyResult.reason);
937
+ }
938
+ // validate parameters
939
+ const paramObject = operationObject.parameters;
940
+ const paramResult = this.checkParamSchema(paramObject);
941
+ result.reason.push(...paramResult.reason);
942
+ // validate total parameters count
943
+ if (paramResult.isValid && postBodyResult.isValid) {
944
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
945
+ result.reason.push(...paramCountResult.reason);
946
+ }
947
+ if (result.reason.length > 0) {
948
+ result.isValid = false;
949
+ }
950
+ return result;
951
+ }
952
+ validateParamCount(postBodyResult, paramResult) {
953
+ const result = { isValid: true, reason: [] };
954
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
955
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
956
+ if (totalRequiredParams > 1) {
957
+ if (!this.options.allowMultipleParameters ||
958
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
959
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
960
+ }
961
+ }
962
+ else if (totalParams === 0) {
963
+ result.reason.push(ErrorType.NoParameter);
964
+ }
965
+ return result;
966
+ }
967
+ }
968
+ SMEValidator.SMERequiredParamsMaxNum = 5;
969
+
970
+ // Copyright (c) Microsoft Corporation.
971
+ class TeamsAIValidator extends Validator {
972
+ constructor(spec, options) {
973
+ super();
974
+ this.projectType = ProjectType.TeamsAi;
975
+ this.options = options;
976
+ this.spec = spec;
977
+ }
978
+ validateSpec() {
979
+ const result = { errors: [], warnings: [] };
980
+ // validate spec server
981
+ let validationResult = this.validateSpecServer();
982
+ result.errors.push(...validationResult.errors);
983
+ // validate no supported API
984
+ validationResult = this.validateSpecNoSupportAPI();
985
+ result.errors.push(...validationResult.errors);
986
+ return result;
987
+ }
988
+ validateAPI(method, path) {
989
+ const result = { isValid: true, reason: [] };
990
+ method = method.toLocaleLowerCase();
991
+ // validate method and path
992
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
993
+ if (!methodAndPathResult.isValid) {
994
+ return methodAndPathResult;
995
+ }
996
+ const operationObject = this.spec.paths[path][method];
997
+ // validate operationId
998
+ if (!this.options.allowMissingId && !operationObject.operationId) {
999
+ result.reason.push(ErrorType.MissingOperationId);
1000
+ }
1001
+ // validate server
1002
+ const validateServerResult = this.validateServer(method, path);
1003
+ result.reason.push(...validateServerResult.reason);
1004
+ if (result.reason.length > 0) {
1005
+ result.isValid = false;
1006
+ }
1007
+ return result;
1008
+ }
1009
+ }
1010
+
1011
+ class ValidatorFactory {
1012
+ static create(spec, options) {
1013
+ var _a;
1014
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1015
+ switch (type) {
1016
+ case ProjectType.SME:
1017
+ return new SMEValidator(spec, options);
1018
+ case ProjectType.Copilot:
1019
+ return new CopilotValidator(spec, options);
1020
+ case ProjectType.TeamsAi:
1021
+ return new TeamsAIValidator(spec, options);
1022
+ default:
1023
+ throw new Error(`Invalid project type: ${type}`);
1024
+ }
814
1025
  }
815
1026
  }
816
1027
 
@@ -849,11 +1060,7 @@ class SpecParser {
849
1060
  try {
850
1061
  try {
851
1062
  yield this.loadSpec();
852
- yield this.parser.validate(this.spec, {
853
- validate: {
854
- schema: false,
855
- },
856
- });
1063
+ yield this.parser.validate(this.spec);
857
1064
  }
858
1065
  catch (e) {
859
1066
  return {
@@ -862,16 +1069,46 @@ class SpecParser {
862
1069
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
863
1070
  };
864
1071
  }
1072
+ const errors = [];
1073
+ const warnings = [];
865
1074
  if (!this.options.allowSwagger && this.isSwaggerFile) {
866
1075
  return {
867
1076
  status: ValidationStatus.Error,
868
1077
  warnings: [],
869
1078
  errors: [
870
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1079
+ {
1080
+ type: ErrorType.SwaggerNotSupported,
1081
+ content: ConstantString.SwaggerNotSupported,
1082
+ },
871
1083
  ],
872
1084
  };
873
1085
  }
874
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1086
+ // Remote reference not supported
1087
+ const refPaths = this.parser.$refs.paths();
1088
+ // refPaths [0] is the current spec file path
1089
+ if (refPaths.length > 1) {
1090
+ errors.push({
1091
+ type: ErrorType.RemoteRefNotSupported,
1092
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1093
+ data: refPaths,
1094
+ });
1095
+ }
1096
+ const validator = this.getValidator(this.spec);
1097
+ const validationResult = validator.validateSpec();
1098
+ warnings.push(...validationResult.warnings);
1099
+ errors.push(...validationResult.errors);
1100
+ let status = ValidationStatus.Valid;
1101
+ if (warnings.length > 0 && errors.length === 0) {
1102
+ status = ValidationStatus.Warning;
1103
+ }
1104
+ else if (errors.length > 0) {
1105
+ status = ValidationStatus.Error;
1106
+ }
1107
+ return {
1108
+ status: status,
1109
+ warnings: warnings,
1110
+ errors: errors,
1111
+ };
875
1112
  }
876
1113
  catch (err) {
877
1114
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -882,17 +1119,20 @@ class SpecParser {
882
1119
  return __awaiter(this, void 0, void 0, function* () {
883
1120
  try {
884
1121
  yield this.loadSpec();
885
- const apiMap = this.getAllSupportedAPIs(this.spec);
1122
+ const apiMap = this.getAPIs(this.spec);
886
1123
  const apiInfos = [];
887
1124
  for (const key in apiMap) {
888
- const pathObjectItem = apiMap[key];
1125
+ const { operation, isValid } = apiMap[key];
1126
+ if (!isValid) {
1127
+ continue;
1128
+ }
889
1129
  const [method, path] = key.split(" ");
890
- const operationId = pathObjectItem.operationId;
1130
+ const operationId = operation.operationId;
891
1131
  // In Browser environment, this api is by default not support api without operationId
892
1132
  if (!operationId) {
893
1133
  continue;
894
1134
  }
895
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1135
+ const command = Utils.parseApiInfo(operation, this.options);
896
1136
  const apiInfo = {
897
1137
  method: method,
898
1138
  path: path,
@@ -901,9 +1141,6 @@ class SpecParser {
901
1141
  parameters: command.parameters,
902
1142
  description: command.description,
903
1143
  };
904
- if (warning) {
905
- apiInfo.warning = warning;
906
- }
907
1144
  apiInfos.push(apiInfo);
908
1145
  }
909
1146
  return apiInfos;
@@ -973,13 +1210,22 @@ class SpecParser {
973
1210
  }
974
1211
  });
975
1212
  }
976
- getAllSupportedAPIs(spec) {
1213
+ getAPIs(spec) {
977
1214
  if (this.apiMap !== undefined) {
978
1215
  return this.apiMap;
979
1216
  }
980
- const result = Utils.listSupportedAPIs(spec, this.options);
981
- this.apiMap = result;
982
- return result;
1217
+ const validator = this.getValidator(spec);
1218
+ const apiMap = validator.listAPIs();
1219
+ this.apiMap = apiMap;
1220
+ return apiMap;
1221
+ }
1222
+ getValidator(spec) {
1223
+ if (this.validator) {
1224
+ return this.validator;
1225
+ }
1226
+ const validator = ValidatorFactory.create(spec, this.options);
1227
+ this.validator = validator;
1228
+ return validator;
983
1229
  }
984
1230
  }
985
1231
 
@@ -987,7 +1233,7 @@ class SpecParser {
987
1233
  class AdaptiveCardGenerator {
988
1234
  static generateAdaptiveCard(operationItem) {
989
1235
  try {
990
- const json = Utils.getResponseJson(operationItem);
1236
+ const { json } = Utils.getResponseJson(operationItem);
991
1237
  let cardBody = [];
992
1238
  let schema = json.schema;
993
1239
  let jsonPath = "$";