@microsoft/m365-spec-parser 0.1.1-alpha.4cb5c08a8.0 → 0.1.1-alpha.4e708f092.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.
@@ -137,6 +137,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
137
137
  ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
138
138
  ConstantString.AdaptiveCardType = "AdaptiveCard";
139
139
  ConstantString.TextBlockType = "TextBlock";
140
+ ConstantString.ImageType = "Image";
140
141
  ConstantString.ContainerType = "Container";
141
142
  ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
142
143
  ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
@@ -200,7 +201,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
200
201
  ConstantString.ParameterDescriptionMaxLens = 128;
201
202
  ConstantString.CommandTitleMaxLens = 32;
202
203
  ConstantString.ParameterTitleMaxLens = 32;
203
- ConstantString.SMERequiredParamsMaxNum = 5;
204
+ ConstantString.SMERequiredParamsMaxNum = 5;
205
+ ConstantString.DefaultPluginId = "plugin_1";
204
206
 
205
207
  // Copyright (c) Microsoft Corporation.
206
208
  class Utils {
@@ -215,249 +217,9 @@ class Utils {
215
217
  }
216
218
  return false;
217
219
  }
218
- static checkParameters(paramObject, isCopilot) {
219
- const paramResult = {
220
- requiredNum: 0,
221
- optionalNum: 0,
222
- isValid: true,
223
- reason: [],
224
- };
225
- if (!paramObject) {
226
- return paramResult;
227
- }
228
- for (let i = 0; i < paramObject.length; i++) {
229
- const param = paramObject[i];
230
- const schema = param.schema;
231
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
232
- paramResult.isValid = false;
233
- paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
234
- continue;
235
- }
236
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
237
- if (isCopilot) {
238
- if (isRequiredWithoutDefault) {
239
- paramResult.requiredNum = paramResult.requiredNum + 1;
240
- }
241
- else {
242
- paramResult.optionalNum = paramResult.optionalNum + 1;
243
- }
244
- continue;
245
- }
246
- if (param.in === "header" || param.in === "cookie") {
247
- if (isRequiredWithoutDefault) {
248
- paramResult.isValid = false;
249
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
250
- }
251
- continue;
252
- }
253
- if (schema.type !== "boolean" &&
254
- schema.type !== "string" &&
255
- schema.type !== "number" &&
256
- schema.type !== "integer") {
257
- if (isRequiredWithoutDefault) {
258
- paramResult.isValid = false;
259
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
260
- }
261
- continue;
262
- }
263
- if (param.in === "query" || param.in === "path") {
264
- if (isRequiredWithoutDefault) {
265
- paramResult.requiredNum = paramResult.requiredNum + 1;
266
- }
267
- else {
268
- paramResult.optionalNum = paramResult.optionalNum + 1;
269
- }
270
- }
271
- }
272
- return paramResult;
273
- }
274
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
275
- var _a;
276
- const paramResult = {
277
- requiredNum: 0,
278
- optionalNum: 0,
279
- isValid: true,
280
- reason: [],
281
- };
282
- if (Object.keys(schema).length === 0) {
283
- return paramResult;
284
- }
285
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
286
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
287
- paramResult.isValid = false;
288
- paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
289
- return paramResult;
290
- }
291
- if (schema.type === "string" ||
292
- schema.type === "integer" ||
293
- schema.type === "boolean" ||
294
- schema.type === "number") {
295
- if (isRequiredWithoutDefault) {
296
- paramResult.requiredNum = paramResult.requiredNum + 1;
297
- }
298
- else {
299
- paramResult.optionalNum = paramResult.optionalNum + 1;
300
- }
301
- }
302
- else if (schema.type === "object") {
303
- const { properties } = schema;
304
- for (const property in properties) {
305
- let isRequired = false;
306
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
307
- isRequired = true;
308
- }
309
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
310
- paramResult.requiredNum += result.requiredNum;
311
- paramResult.optionalNum += result.optionalNum;
312
- paramResult.isValid = paramResult.isValid && result.isValid;
313
- paramResult.reason.push(...result.reason);
314
- }
315
- }
316
- else {
317
- if (isRequiredWithoutDefault && !isCopilot) {
318
- paramResult.isValid = false;
319
- paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
320
- }
321
- }
322
- return paramResult;
323
- }
324
220
  static containMultipleMediaTypes(bodyObject) {
325
221
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
326
222
  }
327
- /**
328
- * Checks if the given API is supported.
329
- * @param {string} method - The HTTP method of the API.
330
- * @param {string} path - The path of the API.
331
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
332
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
333
- * @description The following APIs are supported:
334
- * 1. only support Get/Post operation without auth property
335
- * 2. parameter inside query or path only support string, number, boolean and integer
336
- * 3. parameter inside post body only support string, number, boolean, integer and object
337
- * 4. request body + required parameters <= 1
338
- * 5. response body should be “application/json” and not empty, and response code should be 20X
339
- * 6. only support request body with “application/json” content type
340
- */
341
- static isSupportedApi(method, path, spec, options) {
342
- var _a;
343
- const result = { isValid: true, reason: [] };
344
- method = method.toLocaleLowerCase();
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);
407
- }
408
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
409
- if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
410
- result.reason.push(...requestBodyParamResult.reason);
411
- }
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);
425
- }
426
- }
427
- else if (totalParams === 0) {
428
- result.reason.push(ErrorType.NoParameter);
429
- }
430
- }
431
- }
432
- if (result.reason.length > 0) {
433
- result.isValid = false;
434
- }
435
- return result;
436
- }
437
- static isSupportedAuth(authSchemeArray, options) {
438
- if (authSchemeArray.length === 0) {
439
- return { isValid: true, reason: [] };
440
- }
441
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
442
- // Currently we don't support multiple auth in one operation
443
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
444
- return {
445
- isValid: false,
446
- reason: [ErrorType.MultipleAuthNotSupported],
447
- };
448
- }
449
- for (const auths of authSchemeArray) {
450
- if (auths.length === 1) {
451
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
452
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
453
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
454
- return { isValid: true, reason: [] };
455
- }
456
- }
457
- }
458
- }
459
- return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
460
- }
461
223
  static isBearerTokenAuth(authScheme) {
462
224
  return authScheme.type === "http" && authScheme.scheme === "bearer";
463
225
  }
@@ -465,10 +227,9 @@ class Utils {
465
227
  return authScheme.type === "apiKey";
466
228
  }
467
229
  static isOAuthWithAuthCodeFlow(authScheme) {
468
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
469
- return true;
470
- }
471
- return false;
230
+ return !!(authScheme.type === "oauth2" &&
231
+ authScheme.flows &&
232
+ authScheme.flows.authorizationCode);
472
233
  }
473
234
  static getAuthArray(securities, spec) {
474
235
  var _a;
@@ -496,7 +257,7 @@ class Utils {
496
257
  static updateFirstLetter(str) {
497
258
  return str.charAt(0).toUpperCase() + str.slice(1);
498
259
  }
499
- static getResponseJson(operationObject, isTeamsAiProject = false) {
260
+ static getResponseJson(operationObject) {
500
261
  var _a, _b;
501
262
  let json = {};
502
263
  let multipleMediaType = false;
@@ -507,9 +268,6 @@ class Utils {
507
268
  json = responseObject.content["application/json"];
508
269
  if (Utils.containMultipleMediaTypes(responseObject)) {
509
270
  multipleMediaType = true;
510
- if (isTeamsAiProject) {
511
- break;
512
- }
513
271
  json = {};
514
272
  }
515
273
  else {
@@ -737,13 +495,7 @@ class Utils {
737
495
  }
738
496
  }
739
497
  const operationId = operationItem.operationId;
740
- const parameters = [];
741
- if (requiredParams.length !== 0) {
742
- parameters.push(...requiredParams);
743
- }
744
- else {
745
- parameters.push(optionalParams[0]);
746
- }
498
+ const parameters = [...requiredParams, ...optionalParams];
747
499
  const command = {
748
500
  context: ["compose"],
749
501
  type: "query",
@@ -752,26 +504,51 @@ class Utils {
752
504
  parameters: parameters,
753
505
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
754
506
  };
755
- let warning = undefined;
756
- if (requiredParams.length === 0 && optionalParams.length > 1) {
757
- warning = {
758
- type: WarningType.OperationOnlyContainsOptionalParam,
759
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
760
- data: operationId,
761
- };
507
+ return command;
508
+ }
509
+ static format(str, ...args) {
510
+ let index = 0;
511
+ return str.replace(/%s/g, () => {
512
+ const arg = args[index++];
513
+ return arg !== undefined ? arg : "";
514
+ });
515
+ }
516
+ static getSafeRegistrationIdEnvName(authName) {
517
+ if (!authName) {
518
+ return "";
519
+ }
520
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
521
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
522
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
762
523
  }
763
- return [command, warning];
524
+ return safeRegistrationIdEnvName;
764
525
  }
765
- static listAPIs(spec, options) {
526
+ static getServerObject(spec, method, path) {
527
+ const pathObj = spec.paths[path];
528
+ const operationObject = pathObj[method];
529
+ const rootServer = spec.servers && spec.servers[0];
530
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
531
+ const operationServer = operationObject.servers && operationObject.servers[0];
532
+ const serverUrl = operationServer || methodServer || rootServer;
533
+ return serverUrl;
534
+ }
535
+ }
536
+
537
+ // Copyright (c) Microsoft Corporation.
538
+ class Validator {
539
+ listAPIs() {
766
540
  var _a;
767
- const paths = spec.paths;
541
+ if (this.apiMap) {
542
+ return this.apiMap;
543
+ }
544
+ const paths = this.spec.paths;
768
545
  const result = {};
769
546
  for (const path in paths) {
770
547
  const methods = paths[path];
771
548
  for (const method in methods) {
772
549
  const operationObject = methods[method];
773
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
774
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
550
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
551
+ const validateResult = this.validateAPI(method, path);
775
552
  result[`${method.toUpperCase()} ${path}`] = {
776
553
  operation: operationObject,
777
554
  isValid: validateResult.isValid,
@@ -780,38 +557,48 @@ class Utils {
780
557
  }
781
558
  }
782
559
  }
560
+ this.apiMap = result;
783
561
  return result;
784
562
  }
785
- static validateSpec(spec, parser, isSwaggerFile, options) {
786
- const errors = [];
787
- const warnings = [];
788
- const apiMap = Utils.listAPIs(spec, options);
789
- if (isSwaggerFile) {
790
- warnings.push({
791
- type: WarningType.ConvertSwaggerToOpenAPI,
792
- content: ConstantString.ConvertSwaggerToOpenAPI,
563
+ validateSpecVersion() {
564
+ const result = { errors: [], warnings: [] };
565
+ if (this.spec.openapi >= "3.1.0") {
566
+ result.errors.push({
567
+ type: ErrorType.SpecVersionNotSupported,
568
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
569
+ data: this.spec.openapi,
793
570
  });
794
571
  }
795
- const serverErrors = Utils.validateServer(spec, options);
796
- errors.push(...serverErrors);
797
- // Remote reference not supported
798
- const refPaths = parser.$refs.paths();
799
- // refPaths [0] is the current spec file path
800
- if (refPaths.length > 1) {
801
- errors.push({
802
- type: ErrorType.RemoteRefNotSupported,
803
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
804
- data: refPaths,
805
- });
806
- }
807
- // No supported API
572
+ return result;
573
+ }
574
+ validateSpecServer() {
575
+ const result = { errors: [], warnings: [] };
576
+ const serverErrors = Utils.validateServer(this.spec, this.options);
577
+ result.errors.push(...serverErrors);
578
+ return result;
579
+ }
580
+ validateSpecNoSupportAPI() {
581
+ const result = { errors: [], warnings: [] };
582
+ const apiMap = this.listAPIs();
808
583
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
809
584
  if (validAPIs.length === 0) {
810
- errors.push({
585
+ const data = [];
586
+ for (const key in apiMap) {
587
+ const { reason } = apiMap[key];
588
+ const apiInvalidReason = { api: key, reason: reason };
589
+ data.push(apiInvalidReason);
590
+ }
591
+ result.errors.push({
811
592
  type: ErrorType.NoSupportedApi,
812
593
  content: ConstantString.NoSupportedApi,
594
+ data,
813
595
  });
814
596
  }
597
+ return result;
598
+ }
599
+ validateSpecOperationId() {
600
+ const result = { errors: [], warnings: [] };
601
+ const apiMap = this.listAPIs();
815
602
  // OperationId missing
816
603
  const apisMissingOperationId = [];
817
604
  for (const key in apiMap) {
@@ -821,54 +608,431 @@ class Utils {
821
608
  }
822
609
  }
823
610
  if (apisMissingOperationId.length > 0) {
824
- warnings.push({
611
+ result.warnings.push({
825
612
  type: WarningType.OperationIdMissing,
826
613
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
827
614
  data: apisMissingOperationId,
828
615
  });
829
616
  }
830
- let status = ValidationStatus.Valid;
831
- if (warnings.length > 0 && errors.length === 0) {
832
- status = ValidationStatus.Warning;
617
+ return result;
618
+ }
619
+ validateMethodAndPath(method, path) {
620
+ const result = { isValid: true, reason: [] };
621
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
622
+ result.isValid = false;
623
+ result.reason.push(ErrorType.MethodNotAllowed);
624
+ return result;
833
625
  }
834
- else if (errors.length > 0) {
835
- status = ValidationStatus.Error;
626
+ const pathObj = this.spec.paths[path];
627
+ if (!pathObj || !pathObj[method]) {
628
+ result.isValid = false;
629
+ result.reason.push(ErrorType.UrlPathNotExist);
630
+ return result;
836
631
  }
837
- return {
838
- status,
839
- warnings,
840
- errors,
841
- };
632
+ return result;
842
633
  }
843
- static format(str, ...args) {
844
- let index = 0;
845
- return str.replace(/%s/g, () => {
846
- const arg = args[index++];
847
- return arg !== undefined ? arg : "";
848
- });
634
+ validateResponse(method, path) {
635
+ const result = { isValid: true, reason: [] };
636
+ const operationObject = this.spec.paths[path][method];
637
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
638
+ // only support response body only contains “application/json” content type
639
+ if (multipleMediaType) {
640
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
641
+ }
642
+ else if (Object.keys(json).length === 0) {
643
+ // response body should not be empty
644
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
645
+ }
646
+ return result;
849
647
  }
850
- static getSafeRegistrationIdEnvName(authName) {
851
- if (!authName) {
852
- return "";
648
+ validateServer(method, path) {
649
+ const result = { isValid: true, reason: [] };
650
+ const serverObj = Utils.getServerObject(this.spec, method, path);
651
+ if (!serverObj) {
652
+ // should contain server URL
653
+ result.reason.push(ErrorType.NoServerInformation);
853
654
  }
854
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
855
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
856
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
655
+ else {
656
+ // server url should be absolute url with https protocol
657
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
658
+ result.reason.push(...serverValidateResult.map((item) => item.type));
857
659
  }
858
- return safeRegistrationIdEnvName;
660
+ return result;
859
661
  }
860
- static getAllAPICount(spec) {
861
- let count = 0;
862
- const paths = spec.paths;
863
- for (const path in paths) {
864
- const methods = paths[path];
865
- for (const method in methods) {
866
- if (ConstantString.AllOperationMethods.includes(method)) {
867
- count++;
662
+ validateAuth(method, path) {
663
+ const pathObj = this.spec.paths[path];
664
+ const operationObject = pathObj[method];
665
+ const securities = operationObject.security;
666
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
667
+ if (authSchemeArray.length === 0) {
668
+ return { isValid: true, reason: [] };
669
+ }
670
+ if (this.options.allowAPIKeyAuth ||
671
+ this.options.allowOauth2 ||
672
+ this.options.allowBearerTokenAuth) {
673
+ // Currently we don't support multiple auth in one operation
674
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
675
+ return {
676
+ isValid: false,
677
+ reason: [ErrorType.MultipleAuthNotSupported],
678
+ };
679
+ }
680
+ for (const auths of authSchemeArray) {
681
+ if (auths.length === 1) {
682
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
683
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
684
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
685
+ return { isValid: true, reason: [] };
686
+ }
687
+ }
688
+ }
689
+ }
690
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
691
+ }
692
+ checkPostBodySchema(schema, isRequired = false) {
693
+ var _a;
694
+ const paramResult = {
695
+ requiredNum: 0,
696
+ optionalNum: 0,
697
+ isValid: true,
698
+ reason: [],
699
+ };
700
+ if (Object.keys(schema).length === 0) {
701
+ return paramResult;
702
+ }
703
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
704
+ const isCopilot = this.projectType === ProjectType.Copilot;
705
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
706
+ paramResult.isValid = false;
707
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
708
+ return paramResult;
709
+ }
710
+ if (schema.type === "string" ||
711
+ schema.type === "integer" ||
712
+ schema.type === "boolean" ||
713
+ schema.type === "number") {
714
+ if (isRequiredWithoutDefault) {
715
+ paramResult.requiredNum = paramResult.requiredNum + 1;
716
+ }
717
+ else {
718
+ paramResult.optionalNum = paramResult.optionalNum + 1;
719
+ }
720
+ }
721
+ else if (schema.type === "object") {
722
+ const { properties } = schema;
723
+ for (const property in properties) {
724
+ let isRequired = false;
725
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
726
+ isRequired = true;
727
+ }
728
+ const result = this.checkPostBodySchema(properties[property], isRequired);
729
+ paramResult.requiredNum += result.requiredNum;
730
+ paramResult.optionalNum += result.optionalNum;
731
+ paramResult.isValid = paramResult.isValid && result.isValid;
732
+ paramResult.reason.push(...result.reason);
733
+ }
734
+ }
735
+ else {
736
+ if (isRequiredWithoutDefault && !isCopilot) {
737
+ paramResult.isValid = false;
738
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
739
+ }
740
+ }
741
+ return paramResult;
742
+ }
743
+ checkParamSchema(paramObject) {
744
+ const paramResult = {
745
+ requiredNum: 0,
746
+ optionalNum: 0,
747
+ isValid: true,
748
+ reason: [],
749
+ };
750
+ if (!paramObject) {
751
+ return paramResult;
752
+ }
753
+ const isCopilot = this.projectType === ProjectType.Copilot;
754
+ for (let i = 0; i < paramObject.length; i++) {
755
+ const param = paramObject[i];
756
+ const schema = param.schema;
757
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
758
+ paramResult.isValid = false;
759
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
760
+ continue;
761
+ }
762
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
763
+ if (isCopilot) {
764
+ if (isRequiredWithoutDefault) {
765
+ paramResult.requiredNum = paramResult.requiredNum + 1;
766
+ }
767
+ else {
768
+ paramResult.optionalNum = paramResult.optionalNum + 1;
769
+ }
770
+ continue;
771
+ }
772
+ if (param.in === "header" || param.in === "cookie") {
773
+ if (isRequiredWithoutDefault) {
774
+ paramResult.isValid = false;
775
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
776
+ }
777
+ continue;
778
+ }
779
+ if (schema.type !== "boolean" &&
780
+ schema.type !== "string" &&
781
+ schema.type !== "number" &&
782
+ schema.type !== "integer") {
783
+ if (isRequiredWithoutDefault) {
784
+ paramResult.isValid = false;
785
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
786
+ }
787
+ continue;
788
+ }
789
+ if (param.in === "query" || param.in === "path") {
790
+ if (isRequiredWithoutDefault) {
791
+ paramResult.requiredNum = paramResult.requiredNum + 1;
792
+ }
793
+ else {
794
+ paramResult.optionalNum = paramResult.optionalNum + 1;
868
795
  }
869
796
  }
870
797
  }
871
- return count;
798
+ return paramResult;
799
+ }
800
+ hasNestedObjectInSchema(schema) {
801
+ if (schema.type === "object") {
802
+ for (const property in schema.properties) {
803
+ const nestedSchema = schema.properties[property];
804
+ if (nestedSchema.type === "object") {
805
+ return true;
806
+ }
807
+ }
808
+ }
809
+ return false;
810
+ }
811
+ }
812
+
813
+ // Copyright (c) Microsoft Corporation.
814
+ class CopilotValidator extends Validator {
815
+ constructor(spec, options) {
816
+ super();
817
+ this.projectType = ProjectType.Copilot;
818
+ this.options = options;
819
+ this.spec = spec;
820
+ }
821
+ validateSpec() {
822
+ const result = { errors: [], warnings: [] };
823
+ // validate spec version
824
+ let validationResult = this.validateSpecVersion();
825
+ result.errors.push(...validationResult.errors);
826
+ // validate spec server
827
+ validationResult = this.validateSpecServer();
828
+ result.errors.push(...validationResult.errors);
829
+ // validate no supported API
830
+ validationResult = this.validateSpecNoSupportAPI();
831
+ result.errors.push(...validationResult.errors);
832
+ // validate operationId missing
833
+ validationResult = this.validateSpecOperationId();
834
+ result.warnings.push(...validationResult.warnings);
835
+ return result;
836
+ }
837
+ validateAPI(method, path) {
838
+ const result = { isValid: true, reason: [] };
839
+ method = method.toLocaleLowerCase();
840
+ // validate method and path
841
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
842
+ if (!methodAndPathResult.isValid) {
843
+ return methodAndPathResult;
844
+ }
845
+ const operationObject = this.spec.paths[path][method];
846
+ // validate auth
847
+ const authCheckResult = this.validateAuth(method, path);
848
+ result.reason.push(...authCheckResult.reason);
849
+ // validate operationId
850
+ if (!this.options.allowMissingId && !operationObject.operationId) {
851
+ result.reason.push(ErrorType.MissingOperationId);
852
+ }
853
+ // validate server
854
+ const validateServerResult = this.validateServer(method, path);
855
+ result.reason.push(...validateServerResult.reason);
856
+ // validate response
857
+ const validateResponseResult = this.validateResponse(method, path);
858
+ result.reason.push(...validateResponseResult.reason);
859
+ // validate requestBody
860
+ const requestBody = operationObject.requestBody;
861
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
862
+ if (Utils.containMultipleMediaTypes(requestBody)) {
863
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
864
+ }
865
+ if (requestJsonBody) {
866
+ const requestBodySchema = requestJsonBody.schema;
867
+ if (requestBodySchema.type !== "object") {
868
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
869
+ }
870
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
871
+ result.reason.push(...requestBodyParamResult.reason);
872
+ }
873
+ // validate parameters
874
+ const paramObject = operationObject.parameters;
875
+ const paramResult = this.checkParamSchema(paramObject);
876
+ result.reason.push(...paramResult.reason);
877
+ if (result.reason.length > 0) {
878
+ result.isValid = false;
879
+ }
880
+ return result;
881
+ }
882
+ }
883
+
884
+ // Copyright (c) Microsoft Corporation.
885
+ class SMEValidator extends Validator {
886
+ constructor(spec, options) {
887
+ super();
888
+ this.projectType = ProjectType.SME;
889
+ this.options = options;
890
+ this.spec = spec;
891
+ }
892
+ validateSpec() {
893
+ const result = { errors: [], warnings: [] };
894
+ // validate spec version
895
+ let validationResult = this.validateSpecVersion();
896
+ result.errors.push(...validationResult.errors);
897
+ // validate spec server
898
+ validationResult = this.validateSpecServer();
899
+ result.errors.push(...validationResult.errors);
900
+ // validate no supported API
901
+ validationResult = this.validateSpecNoSupportAPI();
902
+ result.errors.push(...validationResult.errors);
903
+ // validate operationId missing
904
+ if (this.options.allowMissingId) {
905
+ validationResult = this.validateSpecOperationId();
906
+ result.warnings.push(...validationResult.warnings);
907
+ }
908
+ return result;
909
+ }
910
+ validateAPI(method, path) {
911
+ const result = { isValid: true, reason: [] };
912
+ method = method.toLocaleLowerCase();
913
+ // validate method and path
914
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
915
+ if (!methodAndPathResult.isValid) {
916
+ return methodAndPathResult;
917
+ }
918
+ const operationObject = this.spec.paths[path][method];
919
+ // validate auth
920
+ const authCheckResult = this.validateAuth(method, path);
921
+ result.reason.push(...authCheckResult.reason);
922
+ // validate operationId
923
+ if (!this.options.allowMissingId && !operationObject.operationId) {
924
+ result.reason.push(ErrorType.MissingOperationId);
925
+ }
926
+ // validate server
927
+ const validateServerResult = this.validateServer(method, path);
928
+ result.reason.push(...validateServerResult.reason);
929
+ // validate response
930
+ const validateResponseResult = this.validateResponse(method, path);
931
+ result.reason.push(...validateResponseResult.reason);
932
+ let postBodyResult = {
933
+ requiredNum: 0,
934
+ optionalNum: 0,
935
+ isValid: true,
936
+ reason: [],
937
+ };
938
+ // validate requestBody
939
+ const requestBody = operationObject.requestBody;
940
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
941
+ if (Utils.containMultipleMediaTypes(requestBody)) {
942
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
943
+ }
944
+ if (requestJsonBody) {
945
+ const requestBodySchema = requestJsonBody.schema;
946
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
947
+ result.reason.push(...postBodyResult.reason);
948
+ }
949
+ // validate parameters
950
+ const paramObject = operationObject.parameters;
951
+ const paramResult = this.checkParamSchema(paramObject);
952
+ result.reason.push(...paramResult.reason);
953
+ // validate total parameters count
954
+ if (paramResult.isValid && postBodyResult.isValid) {
955
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
956
+ result.reason.push(...paramCountResult.reason);
957
+ }
958
+ if (result.reason.length > 0) {
959
+ result.isValid = false;
960
+ }
961
+ return result;
962
+ }
963
+ validateParamCount(postBodyResult, paramResult) {
964
+ const result = { isValid: true, reason: [] };
965
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
966
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
967
+ if (totalRequiredParams > 1) {
968
+ if (!this.options.allowMultipleParameters ||
969
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
970
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
971
+ }
972
+ }
973
+ else if (totalParams === 0) {
974
+ result.reason.push(ErrorType.NoParameter);
975
+ }
976
+ return result;
977
+ }
978
+ }
979
+ SMEValidator.SMERequiredParamsMaxNum = 5;
980
+
981
+ // Copyright (c) Microsoft Corporation.
982
+ class TeamsAIValidator extends Validator {
983
+ constructor(spec, options) {
984
+ super();
985
+ this.projectType = ProjectType.TeamsAi;
986
+ this.options = options;
987
+ this.spec = spec;
988
+ }
989
+ validateSpec() {
990
+ const result = { errors: [], warnings: [] };
991
+ // validate spec server
992
+ let validationResult = this.validateSpecServer();
993
+ result.errors.push(...validationResult.errors);
994
+ // validate no supported API
995
+ validationResult = this.validateSpecNoSupportAPI();
996
+ result.errors.push(...validationResult.errors);
997
+ return result;
998
+ }
999
+ validateAPI(method, path) {
1000
+ const result = { isValid: true, reason: [] };
1001
+ method = method.toLocaleLowerCase();
1002
+ // validate method and path
1003
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
1004
+ if (!methodAndPathResult.isValid) {
1005
+ return methodAndPathResult;
1006
+ }
1007
+ const operationObject = this.spec.paths[path][method];
1008
+ // validate operationId
1009
+ if (!this.options.allowMissingId && !operationObject.operationId) {
1010
+ result.reason.push(ErrorType.MissingOperationId);
1011
+ }
1012
+ // validate server
1013
+ const validateServerResult = this.validateServer(method, path);
1014
+ result.reason.push(...validateServerResult.reason);
1015
+ if (result.reason.length > 0) {
1016
+ result.isValid = false;
1017
+ }
1018
+ return result;
1019
+ }
1020
+ }
1021
+
1022
+ class ValidatorFactory {
1023
+ static create(spec, options) {
1024
+ var _a;
1025
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1026
+ switch (type) {
1027
+ case ProjectType.SME:
1028
+ return new SMEValidator(spec, options);
1029
+ case ProjectType.Copilot:
1030
+ return new CopilotValidator(spec, options);
1031
+ case ProjectType.TeamsAi:
1032
+ return new TeamsAIValidator(spec, options);
1033
+ default:
1034
+ throw new Error(`Invalid project type: ${type}`);
1035
+ }
872
1036
  }
873
1037
  }
874
1038
 
@@ -891,6 +1055,8 @@ class SpecParser {
891
1055
  allowBearerTokenAuth: false,
892
1056
  allowOauth2: false,
893
1057
  allowMethods: ["get", "post"],
1058
+ allowConversationStarters: false,
1059
+ allowResponseSemantics: false,
894
1060
  projectType: ProjectType.SME,
895
1061
  };
896
1062
  this.pathOrSpec = pathOrDoc;
@@ -907,11 +1073,7 @@ class SpecParser {
907
1073
  try {
908
1074
  try {
909
1075
  yield this.loadSpec();
910
- yield this.parser.validate(this.spec, {
911
- validate: {
912
- schema: false,
913
- },
914
- });
1076
+ yield this.parser.validate(this.spec);
915
1077
  }
916
1078
  catch (e) {
917
1079
  return {
@@ -920,16 +1082,46 @@ class SpecParser {
920
1082
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
921
1083
  };
922
1084
  }
1085
+ const errors = [];
1086
+ const warnings = [];
923
1087
  if (!this.options.allowSwagger && this.isSwaggerFile) {
924
1088
  return {
925
1089
  status: ValidationStatus.Error,
926
1090
  warnings: [],
927
1091
  errors: [
928
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1092
+ {
1093
+ type: ErrorType.SwaggerNotSupported,
1094
+ content: ConstantString.SwaggerNotSupported,
1095
+ },
929
1096
  ],
930
1097
  };
931
1098
  }
932
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1099
+ // Remote reference not supported
1100
+ const refPaths = this.parser.$refs.paths();
1101
+ // refPaths [0] is the current spec file path
1102
+ if (refPaths.length > 1) {
1103
+ errors.push({
1104
+ type: ErrorType.RemoteRefNotSupported,
1105
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1106
+ data: refPaths,
1107
+ });
1108
+ }
1109
+ const validator = this.getValidator(this.spec);
1110
+ const validationResult = validator.validateSpec();
1111
+ warnings.push(...validationResult.warnings);
1112
+ errors.push(...validationResult.errors);
1113
+ let status = ValidationStatus.Valid;
1114
+ if (warnings.length > 0 && errors.length === 0) {
1115
+ status = ValidationStatus.Warning;
1116
+ }
1117
+ else if (errors.length > 0) {
1118
+ status = ValidationStatus.Error;
1119
+ }
1120
+ return {
1121
+ status: status,
1122
+ warnings: warnings,
1123
+ errors: errors,
1124
+ };
933
1125
  }
934
1126
  catch (err) {
935
1127
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -940,17 +1132,20 @@ class SpecParser {
940
1132
  return __awaiter(this, void 0, void 0, function* () {
941
1133
  try {
942
1134
  yield this.loadSpec();
943
- const apiMap = this.getAllSupportedAPIs(this.spec);
1135
+ const apiMap = this.getAPIs(this.spec);
944
1136
  const apiInfos = [];
945
1137
  for (const key in apiMap) {
946
- const pathObjectItem = apiMap[key];
1138
+ const { operation, isValid } = apiMap[key];
1139
+ if (!isValid) {
1140
+ continue;
1141
+ }
947
1142
  const [method, path] = key.split(" ");
948
- const operationId = pathObjectItem.operationId;
1143
+ const operationId = operation.operationId;
949
1144
  // In Browser environment, this api is by default not support api without operationId
950
1145
  if (!operationId) {
951
1146
  continue;
952
1147
  }
953
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1148
+ const command = Utils.parseApiInfo(operation, this.options);
954
1149
  const apiInfo = {
955
1150
  method: method,
956
1151
  path: path,
@@ -959,9 +1154,6 @@ class SpecParser {
959
1154
  parameters: command.parameters,
960
1155
  description: command.description,
961
1156
  };
962
- if (warning) {
963
- apiInfo.warning = warning;
964
- }
965
1157
  apiInfos.push(apiInfo);
966
1158
  }
967
1159
  return apiInfos;
@@ -1031,31 +1223,18 @@ class SpecParser {
1031
1223
  }
1032
1224
  });
1033
1225
  }
1034
- getAllSupportedAPIs(spec) {
1035
- if (this.apiMap !== undefined) {
1036
- return this.apiMap;
1037
- }
1038
- const result = this.listSupportedAPIs(spec, this.options);
1039
- this.apiMap = result;
1040
- return result;
1226
+ getAPIs(spec) {
1227
+ const validator = this.getValidator(spec);
1228
+ const apiMap = validator.listAPIs();
1229
+ return apiMap;
1041
1230
  }
1042
- listSupportedAPIs(spec, options) {
1043
- var _a;
1044
- const paths = spec.paths;
1045
- const result = {};
1046
- for (const path in paths) {
1047
- const methods = paths[path];
1048
- for (const method in methods) {
1049
- const operationObject = methods[method];
1050
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
1051
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
1052
- if (validateResult.isValid) {
1053
- result[`${method.toUpperCase()} ${path}`] = operationObject;
1054
- }
1055
- }
1056
- }
1231
+ getValidator(spec) {
1232
+ if (this.validator) {
1233
+ return this.validator;
1057
1234
  }
1058
- return result;
1235
+ const validator = ValidatorFactory.create(spec, this.options);
1236
+ this.validator = validator;
1237
+ return validator;
1059
1238
  }
1060
1239
  }
1061
1240