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