@microsoft/m365-spec-parser 0.1.1-alpha.4f2290daa.0 → 0.1.1-alpha.5fc8ceacd.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.
@@ -170,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
170
170
  ConstantString.ParameterDescriptionMaxLens = 128;
171
171
  ConstantString.CommandTitleMaxLens = 32;
172
172
  ConstantString.ParameterTitleMaxLens = 32;
173
- ConstantString.SMERequiredParamsMaxNum = 5;
173
+ ConstantString.SMERequiredParamsMaxNum = 5;
174
+ ConstantString.DefaultPluginId = "plugin_1";
174
175
 
175
176
  // Copyright (c) Microsoft Corporation.
176
177
  class Utils {
@@ -185,249 +186,9 @@ class Utils {
185
186
  }
186
187
  return false;
187
188
  }
188
- static checkParameters(paramObject, isCopilot) {
189
- const paramResult = {
190
- requiredNum: 0,
191
- optionalNum: 0,
192
- isValid: true,
193
- reason: [],
194
- };
195
- if (!paramObject) {
196
- return paramResult;
197
- }
198
- for (let i = 0; i < paramObject.length; i++) {
199
- const param = paramObject[i];
200
- const schema = param.schema;
201
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
202
- paramResult.isValid = false;
203
- paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
204
- continue;
205
- }
206
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
207
- if (isCopilot) {
208
- if (isRequiredWithoutDefault) {
209
- paramResult.requiredNum = paramResult.requiredNum + 1;
210
- }
211
- else {
212
- paramResult.optionalNum = paramResult.optionalNum + 1;
213
- }
214
- continue;
215
- }
216
- if (param.in === "header" || param.in === "cookie") {
217
- if (isRequiredWithoutDefault) {
218
- paramResult.isValid = false;
219
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
220
- }
221
- continue;
222
- }
223
- if (schema.type !== "boolean" &&
224
- schema.type !== "string" &&
225
- schema.type !== "number" &&
226
- schema.type !== "integer") {
227
- if (isRequiredWithoutDefault) {
228
- paramResult.isValid = false;
229
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
230
- }
231
- continue;
232
- }
233
- if (param.in === "query" || param.in === "path") {
234
- if (isRequiredWithoutDefault) {
235
- paramResult.requiredNum = paramResult.requiredNum + 1;
236
- }
237
- else {
238
- paramResult.optionalNum = paramResult.optionalNum + 1;
239
- }
240
- }
241
- }
242
- return paramResult;
243
- }
244
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
245
- var _a;
246
- const paramResult = {
247
- requiredNum: 0,
248
- optionalNum: 0,
249
- isValid: true,
250
- reason: [],
251
- };
252
- if (Object.keys(schema).length === 0) {
253
- return paramResult;
254
- }
255
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
256
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
257
- paramResult.isValid = false;
258
- paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
259
- return paramResult;
260
- }
261
- if (schema.type === "string" ||
262
- schema.type === "integer" ||
263
- schema.type === "boolean" ||
264
- schema.type === "number") {
265
- if (isRequiredWithoutDefault) {
266
- paramResult.requiredNum = paramResult.requiredNum + 1;
267
- }
268
- else {
269
- paramResult.optionalNum = paramResult.optionalNum + 1;
270
- }
271
- }
272
- else if (schema.type === "object") {
273
- const { properties } = schema;
274
- for (const property in properties) {
275
- let isRequired = false;
276
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
277
- isRequired = true;
278
- }
279
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
280
- paramResult.requiredNum += result.requiredNum;
281
- paramResult.optionalNum += result.optionalNum;
282
- paramResult.isValid = paramResult.isValid && result.isValid;
283
- paramResult.reason.push(...result.reason);
284
- }
285
- }
286
- else {
287
- if (isRequiredWithoutDefault && !isCopilot) {
288
- paramResult.isValid = false;
289
- paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
290
- }
291
- }
292
- return paramResult;
293
- }
294
189
  static containMultipleMediaTypes(bodyObject) {
295
190
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
296
191
  }
297
- /**
298
- * Checks if the given API is supported.
299
- * @param {string} method - The HTTP method of the API.
300
- * @param {string} path - The path of the API.
301
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
302
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
303
- * @description The following APIs are supported:
304
- * 1. only support Get/Post operation without auth property
305
- * 2. parameter inside query or path only support string, number, boolean and integer
306
- * 3. parameter inside post body only support string, number, boolean, integer and object
307
- * 4. request body + required parameters <= 1
308
- * 5. response body should be “application/json” and not empty, and response code should be 20X
309
- * 6. only support request body with “application/json” content type
310
- */
311
- static isSupportedApi(method, path, spec, options) {
312
- var _a;
313
- const result = { isValid: true, reason: [] };
314
- method = method.toLocaleLowerCase();
315
- if (options.allowMethods && !options.allowMethods.includes(method)) {
316
- result.isValid = false;
317
- result.reason.push(ErrorType.MethodNotAllowed);
318
- return result;
319
- }
320
- const pathObj = spec.paths[path];
321
- if (!pathObj || !pathObj[method]) {
322
- result.isValid = false;
323
- result.reason.push(ErrorType.UrlPathNotExist);
324
- return result;
325
- }
326
- const securities = pathObj[method].security;
327
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
328
- const isCopilot = options.projectType === ProjectType.Copilot;
329
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
330
- if (!isTeamsAi) {
331
- const authArray = Utils.getAuthArray(securities, spec);
332
- const authCheckResult = Utils.isSupportedAuth(authArray, options);
333
- if (!authCheckResult.isValid) {
334
- result.reason.push(...authCheckResult.reason);
335
- }
336
- }
337
- const operationObject = pathObj[method];
338
- if (!options.allowMissingId && !operationObject.operationId) {
339
- result.reason.push(ErrorType.MissingOperationId);
340
- }
341
- const rootServer = spec.servers && spec.servers[0];
342
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
343
- const operationServer = operationObject.servers && operationObject.servers[0];
344
- const serverUrl = operationServer || methodServer || rootServer;
345
- if (!serverUrl) {
346
- result.reason.push(ErrorType.NoServerInformation);
347
- }
348
- else {
349
- const serverValidateResult = Utils.checkServerUrl([serverUrl]);
350
- result.reason.push(...serverValidateResult.map((item) => item.type));
351
- }
352
- const paramObject = operationObject.parameters;
353
- const requestBody = operationObject.requestBody;
354
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
355
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
356
- result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
357
- }
358
- const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
359
- if (multipleMediaType && !isTeamsAi) {
360
- result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
361
- }
362
- else if (Object.keys(json).length === 0) {
363
- result.reason.push(ErrorType.ResponseJsonIsEmpty);
364
- }
365
- // Teams AI project doesn't care about request parameters/body
366
- if (!isTeamsAi) {
367
- let requestBodyParamResult = {
368
- requiredNum: 0,
369
- optionalNum: 0,
370
- isValid: true,
371
- reason: [],
372
- };
373
- if (requestJsonBody) {
374
- const requestBodySchema = requestJsonBody.schema;
375
- if (isCopilot && requestBodySchema.type !== "object") {
376
- result.reason.push(ErrorType.PostBodySchemaIsNotJson);
377
- }
378
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
379
- if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
380
- result.reason.push(...requestBodyParamResult.reason);
381
- }
382
- }
383
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
384
- if (!paramResult.isValid && paramResult.reason) {
385
- result.reason.push(...paramResult.reason);
386
- }
387
- // Copilot support arbitrary parameters
388
- if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
389
- const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
390
- const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
391
- if (totalRequiredParams > 1) {
392
- if (!options.allowMultipleParameters ||
393
- totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
394
- result.reason.push(ErrorType.ExceededRequiredParamsLimit);
395
- }
396
- }
397
- else if (totalParams === 0) {
398
- result.reason.push(ErrorType.NoParameter);
399
- }
400
- }
401
- }
402
- if (result.reason.length > 0) {
403
- result.isValid = false;
404
- }
405
- return result;
406
- }
407
- static isSupportedAuth(authSchemeArray, options) {
408
- if (authSchemeArray.length === 0) {
409
- return { isValid: true, reason: [] };
410
- }
411
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
412
- // Currently we don't support multiple auth in one operation
413
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
414
- return {
415
- isValid: false,
416
- reason: [ErrorType.MultipleAuthNotSupported],
417
- };
418
- }
419
- for (const auths of authSchemeArray) {
420
- if (auths.length === 1) {
421
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
422
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
423
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
424
- return { isValid: true, reason: [] };
425
- }
426
- }
427
- }
428
- }
429
- return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
430
- }
431
192
  static isBearerTokenAuth(authScheme) {
432
193
  return authScheme.type === "http" && authScheme.scheme === "bearer";
433
194
  }
@@ -435,10 +196,9 @@ class Utils {
435
196
  return authScheme.type === "apiKey";
436
197
  }
437
198
  static isOAuthWithAuthCodeFlow(authScheme) {
438
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
439
- return true;
440
- }
441
- return false;
199
+ return !!(authScheme.type === "oauth2" &&
200
+ authScheme.flows &&
201
+ authScheme.flows.authorizationCode);
442
202
  }
443
203
  static getAuthArray(securities, spec) {
444
204
  var _a;
@@ -466,7 +226,7 @@ class Utils {
466
226
  static updateFirstLetter(str) {
467
227
  return str.charAt(0).toUpperCase() + str.slice(1);
468
228
  }
469
- static getResponseJson(operationObject, isTeamsAiProject = false) {
229
+ static getResponseJson(operationObject) {
470
230
  var _a, _b;
471
231
  let json = {};
472
232
  let multipleMediaType = false;
@@ -477,9 +237,6 @@ class Utils {
477
237
  json = responseObject.content["application/json"];
478
238
  if (Utils.containMultipleMediaTypes(responseObject)) {
479
239
  multipleMediaType = true;
480
- if (isTeamsAiProject) {
481
- break;
482
- }
483
240
  json = {};
484
241
  }
485
242
  else {
@@ -718,16 +475,49 @@ class Utils {
718
475
  };
719
476
  return command;
720
477
  }
721
- static listAPIs(spec, options) {
478
+ static format(str, ...args) {
479
+ let index = 0;
480
+ return str.replace(/%s/g, () => {
481
+ const arg = args[index++];
482
+ return arg !== undefined ? arg : "";
483
+ });
484
+ }
485
+ static getSafeRegistrationIdEnvName(authName) {
486
+ if (!authName) {
487
+ return "";
488
+ }
489
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
490
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
491
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
492
+ }
493
+ return safeRegistrationIdEnvName;
494
+ }
495
+ static getServerObject(spec, method, path) {
496
+ const pathObj = spec.paths[path];
497
+ const operationObject = pathObj[method];
498
+ const rootServer = spec.servers && spec.servers[0];
499
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
500
+ const operationServer = operationObject.servers && operationObject.servers[0];
501
+ const serverUrl = operationServer || methodServer || rootServer;
502
+ return serverUrl;
503
+ }
504
+ }
505
+
506
+ // Copyright (c) Microsoft Corporation.
507
+ class Validator {
508
+ listAPIs() {
722
509
  var _a;
723
- const paths = spec.paths;
510
+ if (this.apiMap) {
511
+ return this.apiMap;
512
+ }
513
+ const paths = this.spec.paths;
724
514
  const result = {};
725
515
  for (const path in paths) {
726
516
  const methods = paths[path];
727
517
  for (const method in methods) {
728
518
  const operationObject = methods[method];
729
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
730
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
519
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
520
+ const validateResult = this.validateAPI(method, path);
731
521
  result[`${method.toUpperCase()} ${path}`] = {
732
522
  operation: operationObject,
733
523
  isValid: validateResult.isValid,
@@ -736,38 +526,48 @@ class Utils {
736
526
  }
737
527
  }
738
528
  }
529
+ this.apiMap = result;
739
530
  return result;
740
531
  }
741
- static validateSpec(spec, parser, isSwaggerFile, options) {
742
- const errors = [];
743
- const warnings = [];
744
- const apiMap = Utils.listAPIs(spec, options);
745
- if (isSwaggerFile) {
746
- warnings.push({
747
- type: WarningType.ConvertSwaggerToOpenAPI,
748
- content: ConstantString.ConvertSwaggerToOpenAPI,
532
+ validateSpecVersion() {
533
+ const result = { errors: [], warnings: [] };
534
+ if (this.spec.openapi >= "3.1.0") {
535
+ result.errors.push({
536
+ type: ErrorType.SpecVersionNotSupported,
537
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
538
+ data: this.spec.openapi,
749
539
  });
750
540
  }
751
- const serverErrors = Utils.validateServer(spec, options);
752
- errors.push(...serverErrors);
753
- // Remote reference not supported
754
- const refPaths = parser.$refs.paths();
755
- // refPaths [0] is the current spec file path
756
- if (refPaths.length > 1) {
757
- errors.push({
758
- type: ErrorType.RemoteRefNotSupported,
759
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
760
- data: refPaths,
761
- });
762
- }
763
- // No supported API
541
+ return result;
542
+ }
543
+ validateSpecServer() {
544
+ const result = { errors: [], warnings: [] };
545
+ const serverErrors = Utils.validateServer(this.spec, this.options);
546
+ result.errors.push(...serverErrors);
547
+ return result;
548
+ }
549
+ validateSpecNoSupportAPI() {
550
+ const result = { errors: [], warnings: [] };
551
+ const apiMap = this.listAPIs();
764
552
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
765
553
  if (validAPIs.length === 0) {
766
- errors.push({
554
+ const data = [];
555
+ for (const key in apiMap) {
556
+ const { reason } = apiMap[key];
557
+ const apiInvalidReason = { api: key, reason: reason };
558
+ data.push(apiInvalidReason);
559
+ }
560
+ result.errors.push({
767
561
  type: ErrorType.NoSupportedApi,
768
562
  content: ConstantString.NoSupportedApi,
563
+ data,
769
564
  });
770
565
  }
566
+ return result;
567
+ }
568
+ validateSpecOperationId() {
569
+ const result = { errors: [], warnings: [] };
570
+ const apiMap = this.listAPIs();
771
571
  // OperationId missing
772
572
  const apisMissingOperationId = [];
773
573
  for (const key in apiMap) {
@@ -777,54 +577,431 @@ class Utils {
777
577
  }
778
578
  }
779
579
  if (apisMissingOperationId.length > 0) {
780
- warnings.push({
580
+ result.warnings.push({
781
581
  type: WarningType.OperationIdMissing,
782
582
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
783
583
  data: apisMissingOperationId,
784
584
  });
785
585
  }
786
- let status = ValidationStatus.Valid;
787
- if (warnings.length > 0 && errors.length === 0) {
788
- status = ValidationStatus.Warning;
586
+ return result;
587
+ }
588
+ validateMethodAndPath(method, path) {
589
+ const result = { isValid: true, reason: [] };
590
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
591
+ result.isValid = false;
592
+ result.reason.push(ErrorType.MethodNotAllowed);
593
+ return result;
789
594
  }
790
- else if (errors.length > 0) {
791
- status = ValidationStatus.Error;
595
+ const pathObj = this.spec.paths[path];
596
+ if (!pathObj || !pathObj[method]) {
597
+ result.isValid = false;
598
+ result.reason.push(ErrorType.UrlPathNotExist);
599
+ return result;
792
600
  }
793
- return {
794
- status,
795
- warnings,
796
- errors,
797
- };
601
+ return result;
798
602
  }
799
- static format(str, ...args) {
800
- let index = 0;
801
- return str.replace(/%s/g, () => {
802
- const arg = args[index++];
803
- return arg !== undefined ? arg : "";
804
- });
603
+ validateResponse(method, path) {
604
+ const result = { isValid: true, reason: [] };
605
+ const operationObject = this.spec.paths[path][method];
606
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
607
+ // only support response body only contains “application/json” content type
608
+ if (multipleMediaType) {
609
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
610
+ }
611
+ else if (Object.keys(json).length === 0) {
612
+ // response body should not be empty
613
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
614
+ }
615
+ return result;
805
616
  }
806
- static getSafeRegistrationIdEnvName(authName) {
807
- if (!authName) {
808
- return "";
617
+ validateServer(method, path) {
618
+ const result = { isValid: true, reason: [] };
619
+ const serverObj = Utils.getServerObject(this.spec, method, path);
620
+ if (!serverObj) {
621
+ // should contain server URL
622
+ result.reason.push(ErrorType.NoServerInformation);
809
623
  }
810
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
811
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
812
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
624
+ else {
625
+ // server url should be absolute url with https protocol
626
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
627
+ result.reason.push(...serverValidateResult.map((item) => item.type));
813
628
  }
814
- return safeRegistrationIdEnvName;
629
+ return result;
815
630
  }
816
- static getAllAPICount(spec) {
817
- let count = 0;
818
- const paths = spec.paths;
819
- for (const path in paths) {
820
- const methods = paths[path];
821
- for (const method in methods) {
822
- if (ConstantString.AllOperationMethods.includes(method)) {
823
- count++;
631
+ validateAuth(method, path) {
632
+ const pathObj = this.spec.paths[path];
633
+ const operationObject = pathObj[method];
634
+ const securities = operationObject.security;
635
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
636
+ if (authSchemeArray.length === 0) {
637
+ return { isValid: true, reason: [] };
638
+ }
639
+ if (this.options.allowAPIKeyAuth ||
640
+ this.options.allowOauth2 ||
641
+ this.options.allowBearerTokenAuth) {
642
+ // Currently we don't support multiple auth in one operation
643
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
644
+ return {
645
+ isValid: false,
646
+ reason: [ErrorType.MultipleAuthNotSupported],
647
+ };
648
+ }
649
+ for (const auths of authSchemeArray) {
650
+ if (auths.length === 1) {
651
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
652
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
653
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
654
+ return { isValid: true, reason: [] };
655
+ }
656
+ }
657
+ }
658
+ }
659
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
660
+ }
661
+ checkPostBodySchema(schema, isRequired = false) {
662
+ var _a;
663
+ const paramResult = {
664
+ requiredNum: 0,
665
+ optionalNum: 0,
666
+ isValid: true,
667
+ reason: [],
668
+ };
669
+ if (Object.keys(schema).length === 0) {
670
+ return paramResult;
671
+ }
672
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
673
+ const isCopilot = this.projectType === ProjectType.Copilot;
674
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
675
+ paramResult.isValid = false;
676
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
677
+ return paramResult;
678
+ }
679
+ if (schema.type === "string" ||
680
+ schema.type === "integer" ||
681
+ schema.type === "boolean" ||
682
+ schema.type === "number") {
683
+ if (isRequiredWithoutDefault) {
684
+ paramResult.requiredNum = paramResult.requiredNum + 1;
685
+ }
686
+ else {
687
+ paramResult.optionalNum = paramResult.optionalNum + 1;
688
+ }
689
+ }
690
+ else if (schema.type === "object") {
691
+ const { properties } = schema;
692
+ for (const property in properties) {
693
+ let isRequired = false;
694
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
695
+ isRequired = true;
696
+ }
697
+ const result = this.checkPostBodySchema(properties[property], isRequired);
698
+ paramResult.requiredNum += result.requiredNum;
699
+ paramResult.optionalNum += result.optionalNum;
700
+ paramResult.isValid = paramResult.isValid && result.isValid;
701
+ paramResult.reason.push(...result.reason);
702
+ }
703
+ }
704
+ else {
705
+ if (isRequiredWithoutDefault && !isCopilot) {
706
+ paramResult.isValid = false;
707
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
708
+ }
709
+ }
710
+ return paramResult;
711
+ }
712
+ checkParamSchema(paramObject) {
713
+ const paramResult = {
714
+ requiredNum: 0,
715
+ optionalNum: 0,
716
+ isValid: true,
717
+ reason: [],
718
+ };
719
+ if (!paramObject) {
720
+ return paramResult;
721
+ }
722
+ const isCopilot = this.projectType === ProjectType.Copilot;
723
+ for (let i = 0; i < paramObject.length; i++) {
724
+ const param = paramObject[i];
725
+ const schema = param.schema;
726
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
727
+ paramResult.isValid = false;
728
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
729
+ continue;
730
+ }
731
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
732
+ if (isCopilot) {
733
+ if (isRequiredWithoutDefault) {
734
+ paramResult.requiredNum = paramResult.requiredNum + 1;
735
+ }
736
+ else {
737
+ paramResult.optionalNum = paramResult.optionalNum + 1;
738
+ }
739
+ continue;
740
+ }
741
+ if (param.in === "header" || param.in === "cookie") {
742
+ if (isRequiredWithoutDefault) {
743
+ paramResult.isValid = false;
744
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
745
+ }
746
+ continue;
747
+ }
748
+ if (schema.type !== "boolean" &&
749
+ schema.type !== "string" &&
750
+ schema.type !== "number" &&
751
+ schema.type !== "integer") {
752
+ if (isRequiredWithoutDefault) {
753
+ paramResult.isValid = false;
754
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
755
+ }
756
+ continue;
757
+ }
758
+ if (param.in === "query" || param.in === "path") {
759
+ if (isRequiredWithoutDefault) {
760
+ paramResult.requiredNum = paramResult.requiredNum + 1;
824
761
  }
762
+ else {
763
+ paramResult.optionalNum = paramResult.optionalNum + 1;
764
+ }
765
+ }
766
+ }
767
+ return paramResult;
768
+ }
769
+ hasNestedObjectInSchema(schema) {
770
+ if (schema.type === "object") {
771
+ for (const property in schema.properties) {
772
+ const nestedSchema = schema.properties[property];
773
+ if (nestedSchema.type === "object") {
774
+ return true;
775
+ }
776
+ }
777
+ }
778
+ return false;
779
+ }
780
+ }
781
+
782
+ // Copyright (c) Microsoft Corporation.
783
+ class CopilotValidator extends Validator {
784
+ constructor(spec, options) {
785
+ super();
786
+ this.projectType = ProjectType.Copilot;
787
+ this.options = options;
788
+ this.spec = spec;
789
+ }
790
+ validateSpec() {
791
+ const result = { errors: [], warnings: [] };
792
+ // validate spec version
793
+ let validationResult = this.validateSpecVersion();
794
+ result.errors.push(...validationResult.errors);
795
+ // validate spec server
796
+ validationResult = this.validateSpecServer();
797
+ result.errors.push(...validationResult.errors);
798
+ // validate no supported API
799
+ validationResult = this.validateSpecNoSupportAPI();
800
+ result.errors.push(...validationResult.errors);
801
+ // validate operationId missing
802
+ validationResult = this.validateSpecOperationId();
803
+ result.warnings.push(...validationResult.warnings);
804
+ return result;
805
+ }
806
+ validateAPI(method, path) {
807
+ const result = { isValid: true, reason: [] };
808
+ method = method.toLocaleLowerCase();
809
+ // validate method and path
810
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
811
+ if (!methodAndPathResult.isValid) {
812
+ return methodAndPathResult;
813
+ }
814
+ const operationObject = this.spec.paths[path][method];
815
+ // validate auth
816
+ const authCheckResult = this.validateAuth(method, path);
817
+ result.reason.push(...authCheckResult.reason);
818
+ // validate operationId
819
+ if (!this.options.allowMissingId && !operationObject.operationId) {
820
+ result.reason.push(ErrorType.MissingOperationId);
821
+ }
822
+ // validate server
823
+ const validateServerResult = this.validateServer(method, path);
824
+ result.reason.push(...validateServerResult.reason);
825
+ // validate response
826
+ const validateResponseResult = this.validateResponse(method, path);
827
+ result.reason.push(...validateResponseResult.reason);
828
+ // validate requestBody
829
+ const requestBody = operationObject.requestBody;
830
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
831
+ if (Utils.containMultipleMediaTypes(requestBody)) {
832
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
833
+ }
834
+ if (requestJsonBody) {
835
+ const requestBodySchema = requestJsonBody.schema;
836
+ if (requestBodySchema.type !== "object") {
837
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
825
838
  }
839
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
840
+ result.reason.push(...requestBodyParamResult.reason);
841
+ }
842
+ // validate parameters
843
+ const paramObject = operationObject.parameters;
844
+ const paramResult = this.checkParamSchema(paramObject);
845
+ result.reason.push(...paramResult.reason);
846
+ if (result.reason.length > 0) {
847
+ result.isValid = false;
848
+ }
849
+ return result;
850
+ }
851
+ }
852
+
853
+ // Copyright (c) Microsoft Corporation.
854
+ class SMEValidator extends Validator {
855
+ constructor(spec, options) {
856
+ super();
857
+ this.projectType = ProjectType.SME;
858
+ this.options = options;
859
+ this.spec = spec;
860
+ }
861
+ validateSpec() {
862
+ const result = { errors: [], warnings: [] };
863
+ // validate spec version
864
+ let validationResult = this.validateSpecVersion();
865
+ result.errors.push(...validationResult.errors);
866
+ // validate spec server
867
+ validationResult = this.validateSpecServer();
868
+ result.errors.push(...validationResult.errors);
869
+ // validate no supported API
870
+ validationResult = this.validateSpecNoSupportAPI();
871
+ result.errors.push(...validationResult.errors);
872
+ // validate operationId missing
873
+ if (this.options.allowMissingId) {
874
+ validationResult = this.validateSpecOperationId();
875
+ result.warnings.push(...validationResult.warnings);
876
+ }
877
+ return result;
878
+ }
879
+ validateAPI(method, path) {
880
+ const result = { isValid: true, reason: [] };
881
+ method = method.toLocaleLowerCase();
882
+ // validate method and path
883
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
884
+ if (!methodAndPathResult.isValid) {
885
+ return methodAndPathResult;
886
+ }
887
+ const operationObject = this.spec.paths[path][method];
888
+ // validate auth
889
+ const authCheckResult = this.validateAuth(method, path);
890
+ result.reason.push(...authCheckResult.reason);
891
+ // validate operationId
892
+ if (!this.options.allowMissingId && !operationObject.operationId) {
893
+ result.reason.push(ErrorType.MissingOperationId);
894
+ }
895
+ // validate server
896
+ const validateServerResult = this.validateServer(method, path);
897
+ result.reason.push(...validateServerResult.reason);
898
+ // validate response
899
+ const validateResponseResult = this.validateResponse(method, path);
900
+ result.reason.push(...validateResponseResult.reason);
901
+ let postBodyResult = {
902
+ requiredNum: 0,
903
+ optionalNum: 0,
904
+ isValid: true,
905
+ reason: [],
906
+ };
907
+ // validate requestBody
908
+ const requestBody = operationObject.requestBody;
909
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
910
+ if (Utils.containMultipleMediaTypes(requestBody)) {
911
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
912
+ }
913
+ if (requestJsonBody) {
914
+ const requestBodySchema = requestJsonBody.schema;
915
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
916
+ result.reason.push(...postBodyResult.reason);
917
+ }
918
+ // validate parameters
919
+ const paramObject = operationObject.parameters;
920
+ const paramResult = this.checkParamSchema(paramObject);
921
+ result.reason.push(...paramResult.reason);
922
+ // validate total parameters count
923
+ if (paramResult.isValid && postBodyResult.isValid) {
924
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
925
+ result.reason.push(...paramCountResult.reason);
926
+ }
927
+ if (result.reason.length > 0) {
928
+ result.isValid = false;
929
+ }
930
+ return result;
931
+ }
932
+ validateParamCount(postBodyResult, paramResult) {
933
+ const result = { isValid: true, reason: [] };
934
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
935
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
936
+ if (totalRequiredParams > 1) {
937
+ if (!this.options.allowMultipleParameters ||
938
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
939
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
940
+ }
941
+ }
942
+ else if (totalParams === 0) {
943
+ result.reason.push(ErrorType.NoParameter);
944
+ }
945
+ return result;
946
+ }
947
+ }
948
+ SMEValidator.SMERequiredParamsMaxNum = 5;
949
+
950
+ // Copyright (c) Microsoft Corporation.
951
+ class TeamsAIValidator extends Validator {
952
+ constructor(spec, options) {
953
+ super();
954
+ this.projectType = ProjectType.TeamsAi;
955
+ this.options = options;
956
+ this.spec = spec;
957
+ }
958
+ validateSpec() {
959
+ const result = { errors: [], warnings: [] };
960
+ // validate spec server
961
+ let validationResult = this.validateSpecServer();
962
+ result.errors.push(...validationResult.errors);
963
+ // validate no supported API
964
+ validationResult = this.validateSpecNoSupportAPI();
965
+ result.errors.push(...validationResult.errors);
966
+ return result;
967
+ }
968
+ validateAPI(method, path) {
969
+ const result = { isValid: true, reason: [] };
970
+ method = method.toLocaleLowerCase();
971
+ // validate method and path
972
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
973
+ if (!methodAndPathResult.isValid) {
974
+ return methodAndPathResult;
975
+ }
976
+ const operationObject = this.spec.paths[path][method];
977
+ // validate operationId
978
+ if (!this.options.allowMissingId && !operationObject.operationId) {
979
+ result.reason.push(ErrorType.MissingOperationId);
980
+ }
981
+ // validate server
982
+ const validateServerResult = this.validateServer(method, path);
983
+ result.reason.push(...validateServerResult.reason);
984
+ if (result.reason.length > 0) {
985
+ result.isValid = false;
986
+ }
987
+ return result;
988
+ }
989
+ }
990
+
991
+ class ValidatorFactory {
992
+ static create(spec, options) {
993
+ var _a;
994
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
995
+ switch (type) {
996
+ case ProjectType.SME:
997
+ return new SMEValidator(spec, options);
998
+ case ProjectType.Copilot:
999
+ return new CopilotValidator(spec, options);
1000
+ case ProjectType.TeamsAi:
1001
+ return new TeamsAIValidator(spec, options);
1002
+ default:
1003
+ throw new Error(`Invalid project type: ${type}`);
826
1004
  }
827
- return count;
828
1005
  }
829
1006
  }
830
1007
 
@@ -862,11 +1039,7 @@ class SpecParser {
862
1039
  try {
863
1040
  try {
864
1041
  await this.loadSpec();
865
- await this.parser.validate(this.spec, {
866
- validate: {
867
- schema: false,
868
- },
869
- });
1042
+ await this.parser.validate(this.spec);
870
1043
  }
871
1044
  catch (e) {
872
1045
  return {
@@ -875,16 +1048,46 @@ class SpecParser {
875
1048
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
876
1049
  };
877
1050
  }
1051
+ const errors = [];
1052
+ const warnings = [];
878
1053
  if (!this.options.allowSwagger && this.isSwaggerFile) {
879
1054
  return {
880
1055
  status: ValidationStatus.Error,
881
1056
  warnings: [],
882
1057
  errors: [
883
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1058
+ {
1059
+ type: ErrorType.SwaggerNotSupported,
1060
+ content: ConstantString.SwaggerNotSupported,
1061
+ },
884
1062
  ],
885
1063
  };
886
1064
  }
887
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1065
+ // Remote reference not supported
1066
+ const refPaths = this.parser.$refs.paths();
1067
+ // refPaths [0] is the current spec file path
1068
+ if (refPaths.length > 1) {
1069
+ errors.push({
1070
+ type: ErrorType.RemoteRefNotSupported,
1071
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1072
+ data: refPaths,
1073
+ });
1074
+ }
1075
+ const validator = this.getValidator(this.spec);
1076
+ const validationResult = validator.validateSpec();
1077
+ warnings.push(...validationResult.warnings);
1078
+ errors.push(...validationResult.errors);
1079
+ let status = ValidationStatus.Valid;
1080
+ if (warnings.length > 0 && errors.length === 0) {
1081
+ status = ValidationStatus.Warning;
1082
+ }
1083
+ else if (errors.length > 0) {
1084
+ status = ValidationStatus.Error;
1085
+ }
1086
+ return {
1087
+ status: status,
1088
+ warnings: warnings,
1089
+ errors: errors,
1090
+ };
888
1091
  }
889
1092
  catch (err) {
890
1093
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -893,17 +1096,20 @@ class SpecParser {
893
1096
  async listSupportedAPIInfo() {
894
1097
  try {
895
1098
  await this.loadSpec();
896
- const apiMap = this.getAllSupportedAPIs(this.spec);
1099
+ const apiMap = this.getAPIs(this.spec);
897
1100
  const apiInfos = [];
898
1101
  for (const key in apiMap) {
899
- const pathObjectItem = apiMap[key];
1102
+ const { operation, isValid } = apiMap[key];
1103
+ if (!isValid) {
1104
+ continue;
1105
+ }
900
1106
  const [method, path] = key.split(" ");
901
- const operationId = pathObjectItem.operationId;
1107
+ const operationId = operation.operationId;
902
1108
  // In Browser environment, this api is by default not support api without operationId
903
1109
  if (!operationId) {
904
1110
  continue;
905
1111
  }
906
- const command = Utils.parseApiInfo(pathObjectItem, this.options);
1112
+ const command = Utils.parseApiInfo(operation, this.options);
907
1113
  const apiInfo = {
908
1114
  method: method,
909
1115
  path: path,
@@ -970,31 +1176,22 @@ class SpecParser {
970
1176
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
971
1177
  }
972
1178
  }
973
- getAllSupportedAPIs(spec) {
1179
+ getAPIs(spec) {
974
1180
  if (this.apiMap !== undefined) {
975
1181
  return this.apiMap;
976
1182
  }
977
- const result = this.listSupportedAPIs(spec, this.options);
978
- this.apiMap = result;
979
- return result;
1183
+ const validator = this.getValidator(spec);
1184
+ const apiMap = validator.listAPIs();
1185
+ this.apiMap = apiMap;
1186
+ return apiMap;
980
1187
  }
981
- listSupportedAPIs(spec, options) {
982
- var _a;
983
- const paths = spec.paths;
984
- const result = {};
985
- for (const path in paths) {
986
- const methods = paths[path];
987
- for (const method in methods) {
988
- const operationObject = methods[method];
989
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
990
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
991
- if (validateResult.isValid) {
992
- result[`${method.toUpperCase()} ${path}`] = operationObject;
993
- }
994
- }
995
- }
1188
+ getValidator(spec) {
1189
+ if (this.validator) {
1190
+ return this.validator;
996
1191
  }
997
- return result;
1192
+ const validator = ValidatorFactory.create(spec, this.options);
1193
+ this.validator = validator;
1194
+ return validator;
998
1195
  }
999
1196
  }
1000
1197