@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.
@@ -166,7 +166,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
166
166
  ConstantString.ParameterDescriptionMaxLens = 128;
167
167
  ConstantString.CommandTitleMaxLens = 32;
168
168
  ConstantString.ParameterTitleMaxLens = 32;
169
- ConstantString.SMERequiredParamsMaxNum = 5;
169
+ ConstantString.SMERequiredParamsMaxNum = 5;
170
+ ConstantString.DefaultPluginId = "plugin_1";
170
171
 
171
172
  // Copyright (c) Microsoft Corporation.
172
173
  class SpecParserError extends Error {
@@ -189,249 +190,9 @@ class Utils {
189
190
  }
190
191
  return false;
191
192
  }
192
- static checkParameters(paramObject, isCopilot) {
193
- const paramResult = {
194
- requiredNum: 0,
195
- optionalNum: 0,
196
- isValid: true,
197
- reason: [],
198
- };
199
- if (!paramObject) {
200
- return paramResult;
201
- }
202
- for (let i = 0; i < paramObject.length; i++) {
203
- const param = paramObject[i];
204
- const schema = param.schema;
205
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
206
- paramResult.isValid = false;
207
- paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
208
- continue;
209
- }
210
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
211
- if (isCopilot) {
212
- if (isRequiredWithoutDefault) {
213
- paramResult.requiredNum = paramResult.requiredNum + 1;
214
- }
215
- else {
216
- paramResult.optionalNum = paramResult.optionalNum + 1;
217
- }
218
- continue;
219
- }
220
- if (param.in === "header" || param.in === "cookie") {
221
- if (isRequiredWithoutDefault) {
222
- paramResult.isValid = false;
223
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
224
- }
225
- continue;
226
- }
227
- if (schema.type !== "boolean" &&
228
- schema.type !== "string" &&
229
- schema.type !== "number" &&
230
- schema.type !== "integer") {
231
- if (isRequiredWithoutDefault) {
232
- paramResult.isValid = false;
233
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
234
- }
235
- continue;
236
- }
237
- if (param.in === "query" || param.in === "path") {
238
- if (isRequiredWithoutDefault) {
239
- paramResult.requiredNum = paramResult.requiredNum + 1;
240
- }
241
- else {
242
- paramResult.optionalNum = paramResult.optionalNum + 1;
243
- }
244
- }
245
- }
246
- return paramResult;
247
- }
248
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
249
- var _a;
250
- const paramResult = {
251
- requiredNum: 0,
252
- optionalNum: 0,
253
- isValid: true,
254
- reason: [],
255
- };
256
- if (Object.keys(schema).length === 0) {
257
- return paramResult;
258
- }
259
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
260
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
261
- paramResult.isValid = false;
262
- paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
263
- return paramResult;
264
- }
265
- if (schema.type === "string" ||
266
- schema.type === "integer" ||
267
- schema.type === "boolean" ||
268
- schema.type === "number") {
269
- if (isRequiredWithoutDefault) {
270
- paramResult.requiredNum = paramResult.requiredNum + 1;
271
- }
272
- else {
273
- paramResult.optionalNum = paramResult.optionalNum + 1;
274
- }
275
- }
276
- else if (schema.type === "object") {
277
- const { properties } = schema;
278
- for (const property in properties) {
279
- let isRequired = false;
280
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
281
- isRequired = true;
282
- }
283
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
284
- paramResult.requiredNum += result.requiredNum;
285
- paramResult.optionalNum += result.optionalNum;
286
- paramResult.isValid = paramResult.isValid && result.isValid;
287
- paramResult.reason.push(...result.reason);
288
- }
289
- }
290
- else {
291
- if (isRequiredWithoutDefault && !isCopilot) {
292
- paramResult.isValid = false;
293
- paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
294
- }
295
- }
296
- return paramResult;
297
- }
298
193
  static containMultipleMediaTypes(bodyObject) {
299
194
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
300
195
  }
301
- /**
302
- * Checks if the given API is supported.
303
- * @param {string} method - The HTTP method of the API.
304
- * @param {string} path - The path of the API.
305
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
306
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
307
- * @description The following APIs are supported:
308
- * 1. only support Get/Post operation without auth property
309
- * 2. parameter inside query or path only support string, number, boolean and integer
310
- * 3. parameter inside post body only support string, number, boolean, integer and object
311
- * 4. request body + required parameters <= 1
312
- * 5. response body should be “application/json” and not empty, and response code should be 20X
313
- * 6. only support request body with “application/json” content type
314
- */
315
- static isSupportedApi(method, path, spec, options) {
316
- var _a;
317
- const result = { isValid: true, reason: [] };
318
- method = method.toLocaleLowerCase();
319
- if (options.allowMethods && !options.allowMethods.includes(method)) {
320
- result.isValid = false;
321
- result.reason.push(ErrorType.MethodNotAllowed);
322
- return result;
323
- }
324
- const pathObj = spec.paths[path];
325
- if (!pathObj || !pathObj[method]) {
326
- result.isValid = false;
327
- result.reason.push(ErrorType.UrlPathNotExist);
328
- return result;
329
- }
330
- const securities = pathObj[method].security;
331
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
332
- const isCopilot = options.projectType === ProjectType.Copilot;
333
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
334
- if (!isTeamsAi) {
335
- const authArray = Utils.getAuthArray(securities, spec);
336
- const authCheckResult = Utils.isSupportedAuth(authArray, options);
337
- if (!authCheckResult.isValid) {
338
- result.reason.push(...authCheckResult.reason);
339
- }
340
- }
341
- const operationObject = pathObj[method];
342
- if (!options.allowMissingId && !operationObject.operationId) {
343
- result.reason.push(ErrorType.MissingOperationId);
344
- }
345
- const rootServer = spec.servers && spec.servers[0];
346
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
347
- const operationServer = operationObject.servers && operationObject.servers[0];
348
- const serverUrl = operationServer || methodServer || rootServer;
349
- if (!serverUrl) {
350
- result.reason.push(ErrorType.NoServerInformation);
351
- }
352
- else {
353
- const serverValidateResult = Utils.checkServerUrl([serverUrl]);
354
- result.reason.push(...serverValidateResult.map((item) => item.type));
355
- }
356
- const paramObject = operationObject.parameters;
357
- const requestBody = operationObject.requestBody;
358
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
359
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
360
- result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
361
- }
362
- const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
363
- if (multipleMediaType && !isTeamsAi) {
364
- result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
365
- }
366
- else if (Object.keys(json).length === 0) {
367
- result.reason.push(ErrorType.ResponseJsonIsEmpty);
368
- }
369
- // Teams AI project doesn't care about request parameters/body
370
- if (!isTeamsAi) {
371
- let requestBodyParamResult = {
372
- requiredNum: 0,
373
- optionalNum: 0,
374
- isValid: true,
375
- reason: [],
376
- };
377
- if (requestJsonBody) {
378
- const requestBodySchema = requestJsonBody.schema;
379
- if (isCopilot && requestBodySchema.type !== "object") {
380
- result.reason.push(ErrorType.PostBodySchemaIsNotJson);
381
- }
382
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
383
- if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
384
- result.reason.push(...requestBodyParamResult.reason);
385
- }
386
- }
387
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
388
- if (!paramResult.isValid && paramResult.reason) {
389
- result.reason.push(...paramResult.reason);
390
- }
391
- // Copilot support arbitrary parameters
392
- if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
393
- const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
394
- const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
395
- if (totalRequiredParams > 1) {
396
- if (!options.allowMultipleParameters ||
397
- totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
398
- result.reason.push(ErrorType.ExceededRequiredParamsLimit);
399
- }
400
- }
401
- else if (totalParams === 0) {
402
- result.reason.push(ErrorType.NoParameter);
403
- }
404
- }
405
- }
406
- if (result.reason.length > 0) {
407
- result.isValid = false;
408
- }
409
- return result;
410
- }
411
- static isSupportedAuth(authSchemeArray, options) {
412
- if (authSchemeArray.length === 0) {
413
- return { isValid: true, reason: [] };
414
- }
415
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
416
- // Currently we don't support multiple auth in one operation
417
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
418
- return {
419
- isValid: false,
420
- reason: [ErrorType.MultipleAuthNotSupported],
421
- };
422
- }
423
- for (const auths of authSchemeArray) {
424
- if (auths.length === 1) {
425
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
426
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
427
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
428
- return { isValid: true, reason: [] };
429
- }
430
- }
431
- }
432
- }
433
- return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
434
- }
435
196
  static isBearerTokenAuth(authScheme) {
436
197
  return authScheme.type === "http" && authScheme.scheme === "bearer";
437
198
  }
@@ -439,10 +200,9 @@ class Utils {
439
200
  return authScheme.type === "apiKey";
440
201
  }
441
202
  static isOAuthWithAuthCodeFlow(authScheme) {
442
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
443
- return true;
444
- }
445
- return false;
203
+ return !!(authScheme.type === "oauth2" &&
204
+ authScheme.flows &&
205
+ authScheme.flows.authorizationCode);
446
206
  }
447
207
  static getAuthArray(securities, spec) {
448
208
  var _a;
@@ -470,7 +230,7 @@ class Utils {
470
230
  static updateFirstLetter(str) {
471
231
  return str.charAt(0).toUpperCase() + str.slice(1);
472
232
  }
473
- static getResponseJson(operationObject, isTeamsAiProject = false) {
233
+ static getResponseJson(operationObject) {
474
234
  var _a, _b;
475
235
  let json = {};
476
236
  let multipleMediaType = false;
@@ -481,9 +241,6 @@ class Utils {
481
241
  json = responseObject.content["application/json"];
482
242
  if (Utils.containMultipleMediaTypes(responseObject)) {
483
243
  multipleMediaType = true;
484
- if (isTeamsAiProject) {
485
- break;
486
- }
487
244
  json = {};
488
245
  }
489
246
  else {
@@ -722,16 +479,49 @@ class Utils {
722
479
  };
723
480
  return command;
724
481
  }
725
- static listAPIs(spec, options) {
482
+ static format(str, ...args) {
483
+ let index = 0;
484
+ return str.replace(/%s/g, () => {
485
+ const arg = args[index++];
486
+ return arg !== undefined ? arg : "";
487
+ });
488
+ }
489
+ static getSafeRegistrationIdEnvName(authName) {
490
+ if (!authName) {
491
+ return "";
492
+ }
493
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
494
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
495
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
496
+ }
497
+ return safeRegistrationIdEnvName;
498
+ }
499
+ static getServerObject(spec, method, path) {
500
+ const pathObj = spec.paths[path];
501
+ const operationObject = pathObj[method];
502
+ const rootServer = spec.servers && spec.servers[0];
503
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
504
+ const operationServer = operationObject.servers && operationObject.servers[0];
505
+ const serverUrl = operationServer || methodServer || rootServer;
506
+ return serverUrl;
507
+ }
508
+ }
509
+
510
+ // Copyright (c) Microsoft Corporation.
511
+ class Validator {
512
+ listAPIs() {
726
513
  var _a;
727
- const paths = spec.paths;
514
+ if (this.apiMap) {
515
+ return this.apiMap;
516
+ }
517
+ const paths = this.spec.paths;
728
518
  const result = {};
729
519
  for (const path in paths) {
730
520
  const methods = paths[path];
731
521
  for (const method in methods) {
732
522
  const operationObject = methods[method];
733
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
734
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
523
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
524
+ const validateResult = this.validateAPI(method, path);
735
525
  result[`${method.toUpperCase()} ${path}`] = {
736
526
  operation: operationObject,
737
527
  isValid: validateResult.isValid,
@@ -740,38 +530,48 @@ class Utils {
740
530
  }
741
531
  }
742
532
  }
533
+ this.apiMap = result;
743
534
  return result;
744
535
  }
745
- static validateSpec(spec, parser, isSwaggerFile, options) {
746
- const errors = [];
747
- const warnings = [];
748
- const apiMap = Utils.listAPIs(spec, options);
749
- if (isSwaggerFile) {
750
- warnings.push({
751
- type: WarningType.ConvertSwaggerToOpenAPI,
752
- content: ConstantString.ConvertSwaggerToOpenAPI,
753
- });
754
- }
755
- const serverErrors = Utils.validateServer(spec, options);
756
- errors.push(...serverErrors);
757
- // Remote reference not supported
758
- const refPaths = parser.$refs.paths();
759
- // refPaths [0] is the current spec file path
760
- if (refPaths.length > 1) {
761
- errors.push({
762
- type: ErrorType.RemoteRefNotSupported,
763
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
764
- data: refPaths,
536
+ validateSpecVersion() {
537
+ const result = { errors: [], warnings: [] };
538
+ if (this.spec.openapi >= "3.1.0") {
539
+ result.errors.push({
540
+ type: ErrorType.SpecVersionNotSupported,
541
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
542
+ data: this.spec.openapi,
765
543
  });
766
544
  }
767
- // No supported API
545
+ return result;
546
+ }
547
+ validateSpecServer() {
548
+ const result = { errors: [], warnings: [] };
549
+ const serverErrors = Utils.validateServer(this.spec, this.options);
550
+ result.errors.push(...serverErrors);
551
+ return result;
552
+ }
553
+ validateSpecNoSupportAPI() {
554
+ const result = { errors: [], warnings: [] };
555
+ const apiMap = this.listAPIs();
768
556
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
769
557
  if (validAPIs.length === 0) {
770
- errors.push({
558
+ const data = [];
559
+ for (const key in apiMap) {
560
+ const { reason } = apiMap[key];
561
+ const apiInvalidReason = { api: key, reason: reason };
562
+ data.push(apiInvalidReason);
563
+ }
564
+ result.errors.push({
771
565
  type: ErrorType.NoSupportedApi,
772
566
  content: ConstantString.NoSupportedApi,
567
+ data,
773
568
  });
774
569
  }
570
+ return result;
571
+ }
572
+ validateSpecOperationId() {
573
+ const result = { errors: [], warnings: [] };
574
+ const apiMap = this.listAPIs();
775
575
  // OperationId missing
776
576
  const apisMissingOperationId = [];
777
577
  for (const key in apiMap) {
@@ -781,54 +581,431 @@ class Utils {
781
581
  }
782
582
  }
783
583
  if (apisMissingOperationId.length > 0) {
784
- warnings.push({
584
+ result.warnings.push({
785
585
  type: WarningType.OperationIdMissing,
786
586
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
787
587
  data: apisMissingOperationId,
788
588
  });
789
589
  }
790
- let status = ValidationStatus.Valid;
791
- if (warnings.length > 0 && errors.length === 0) {
792
- status = ValidationStatus.Warning;
590
+ return result;
591
+ }
592
+ validateMethodAndPath(method, path) {
593
+ const result = { isValid: true, reason: [] };
594
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
595
+ result.isValid = false;
596
+ result.reason.push(ErrorType.MethodNotAllowed);
597
+ return result;
793
598
  }
794
- else if (errors.length > 0) {
795
- status = ValidationStatus.Error;
599
+ const pathObj = this.spec.paths[path];
600
+ if (!pathObj || !pathObj[method]) {
601
+ result.isValid = false;
602
+ result.reason.push(ErrorType.UrlPathNotExist);
603
+ return result;
796
604
  }
797
- return {
798
- status,
799
- warnings,
800
- errors,
801
- };
605
+ return result;
802
606
  }
803
- static format(str, ...args) {
804
- let index = 0;
805
- return str.replace(/%s/g, () => {
806
- const arg = args[index++];
807
- return arg !== undefined ? arg : "";
808
- });
607
+ validateResponse(method, path) {
608
+ const result = { isValid: true, reason: [] };
609
+ const operationObject = this.spec.paths[path][method];
610
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
611
+ // only support response body only contains “application/json” content type
612
+ if (multipleMediaType) {
613
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
614
+ }
615
+ else if (Object.keys(json).length === 0) {
616
+ // response body should not be empty
617
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
618
+ }
619
+ return result;
809
620
  }
810
- static getSafeRegistrationIdEnvName(authName) {
811
- if (!authName) {
812
- return "";
621
+ validateServer(method, path) {
622
+ const result = { isValid: true, reason: [] };
623
+ const serverObj = Utils.getServerObject(this.spec, method, path);
624
+ if (!serverObj) {
625
+ // should contain server URL
626
+ result.reason.push(ErrorType.NoServerInformation);
813
627
  }
814
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
815
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
816
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
628
+ else {
629
+ // server url should be absolute url with https protocol
630
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
631
+ result.reason.push(...serverValidateResult.map((item) => item.type));
817
632
  }
818
- return safeRegistrationIdEnvName;
633
+ return result;
819
634
  }
820
- static getAllAPICount(spec) {
821
- let count = 0;
822
- const paths = spec.paths;
823
- for (const path in paths) {
824
- const methods = paths[path];
825
- for (const method in methods) {
826
- if (ConstantString.AllOperationMethods.includes(method)) {
827
- count++;
635
+ validateAuth(method, path) {
636
+ const pathObj = this.spec.paths[path];
637
+ const operationObject = pathObj[method];
638
+ const securities = operationObject.security;
639
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
640
+ if (authSchemeArray.length === 0) {
641
+ return { isValid: true, reason: [] };
642
+ }
643
+ if (this.options.allowAPIKeyAuth ||
644
+ this.options.allowOauth2 ||
645
+ this.options.allowBearerTokenAuth) {
646
+ // Currently we don't support multiple auth in one operation
647
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
648
+ return {
649
+ isValid: false,
650
+ reason: [ErrorType.MultipleAuthNotSupported],
651
+ };
652
+ }
653
+ for (const auths of authSchemeArray) {
654
+ if (auths.length === 1) {
655
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
656
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
657
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
658
+ return { isValid: true, reason: [] };
659
+ }
660
+ }
661
+ }
662
+ }
663
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
664
+ }
665
+ checkPostBodySchema(schema, isRequired = false) {
666
+ var _a;
667
+ const paramResult = {
668
+ requiredNum: 0,
669
+ optionalNum: 0,
670
+ isValid: true,
671
+ reason: [],
672
+ };
673
+ if (Object.keys(schema).length === 0) {
674
+ return paramResult;
675
+ }
676
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
677
+ const isCopilot = this.projectType === ProjectType.Copilot;
678
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
679
+ paramResult.isValid = false;
680
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
681
+ return paramResult;
682
+ }
683
+ if (schema.type === "string" ||
684
+ schema.type === "integer" ||
685
+ schema.type === "boolean" ||
686
+ schema.type === "number") {
687
+ if (isRequiredWithoutDefault) {
688
+ paramResult.requiredNum = paramResult.requiredNum + 1;
689
+ }
690
+ else {
691
+ paramResult.optionalNum = paramResult.optionalNum + 1;
692
+ }
693
+ }
694
+ else if (schema.type === "object") {
695
+ const { properties } = schema;
696
+ for (const property in properties) {
697
+ let isRequired = false;
698
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
699
+ isRequired = true;
700
+ }
701
+ const result = this.checkPostBodySchema(properties[property], isRequired);
702
+ paramResult.requiredNum += result.requiredNum;
703
+ paramResult.optionalNum += result.optionalNum;
704
+ paramResult.isValid = paramResult.isValid && result.isValid;
705
+ paramResult.reason.push(...result.reason);
706
+ }
707
+ }
708
+ else {
709
+ if (isRequiredWithoutDefault && !isCopilot) {
710
+ paramResult.isValid = false;
711
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
712
+ }
713
+ }
714
+ return paramResult;
715
+ }
716
+ checkParamSchema(paramObject) {
717
+ const paramResult = {
718
+ requiredNum: 0,
719
+ optionalNum: 0,
720
+ isValid: true,
721
+ reason: [],
722
+ };
723
+ if (!paramObject) {
724
+ return paramResult;
725
+ }
726
+ const isCopilot = this.projectType === ProjectType.Copilot;
727
+ for (let i = 0; i < paramObject.length; i++) {
728
+ const param = paramObject[i];
729
+ const schema = param.schema;
730
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
731
+ paramResult.isValid = false;
732
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
733
+ continue;
734
+ }
735
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
736
+ if (isCopilot) {
737
+ if (isRequiredWithoutDefault) {
738
+ paramResult.requiredNum = paramResult.requiredNum + 1;
739
+ }
740
+ else {
741
+ paramResult.optionalNum = paramResult.optionalNum + 1;
742
+ }
743
+ continue;
744
+ }
745
+ if (param.in === "header" || param.in === "cookie") {
746
+ if (isRequiredWithoutDefault) {
747
+ paramResult.isValid = false;
748
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
749
+ }
750
+ continue;
751
+ }
752
+ if (schema.type !== "boolean" &&
753
+ schema.type !== "string" &&
754
+ schema.type !== "number" &&
755
+ schema.type !== "integer") {
756
+ if (isRequiredWithoutDefault) {
757
+ paramResult.isValid = false;
758
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
759
+ }
760
+ continue;
761
+ }
762
+ if (param.in === "query" || param.in === "path") {
763
+ if (isRequiredWithoutDefault) {
764
+ paramResult.requiredNum = paramResult.requiredNum + 1;
765
+ }
766
+ else {
767
+ paramResult.optionalNum = paramResult.optionalNum + 1;
768
+ }
769
+ }
770
+ }
771
+ return paramResult;
772
+ }
773
+ hasNestedObjectInSchema(schema) {
774
+ if (schema.type === "object") {
775
+ for (const property in schema.properties) {
776
+ const nestedSchema = schema.properties[property];
777
+ if (nestedSchema.type === "object") {
778
+ return true;
828
779
  }
829
780
  }
830
781
  }
831
- return count;
782
+ return false;
783
+ }
784
+ }
785
+
786
+ // Copyright (c) Microsoft Corporation.
787
+ class CopilotValidator extends Validator {
788
+ constructor(spec, options) {
789
+ super();
790
+ this.projectType = ProjectType.Copilot;
791
+ this.options = options;
792
+ this.spec = spec;
793
+ }
794
+ validateSpec() {
795
+ const result = { errors: [], warnings: [] };
796
+ // validate spec version
797
+ let validationResult = this.validateSpecVersion();
798
+ result.errors.push(...validationResult.errors);
799
+ // validate spec server
800
+ validationResult = this.validateSpecServer();
801
+ result.errors.push(...validationResult.errors);
802
+ // validate no supported API
803
+ validationResult = this.validateSpecNoSupportAPI();
804
+ result.errors.push(...validationResult.errors);
805
+ // validate operationId missing
806
+ validationResult = this.validateSpecOperationId();
807
+ result.warnings.push(...validationResult.warnings);
808
+ return result;
809
+ }
810
+ validateAPI(method, path) {
811
+ const result = { isValid: true, reason: [] };
812
+ method = method.toLocaleLowerCase();
813
+ // validate method and path
814
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
815
+ if (!methodAndPathResult.isValid) {
816
+ return methodAndPathResult;
817
+ }
818
+ const operationObject = this.spec.paths[path][method];
819
+ // validate auth
820
+ const authCheckResult = this.validateAuth(method, path);
821
+ result.reason.push(...authCheckResult.reason);
822
+ // validate operationId
823
+ if (!this.options.allowMissingId && !operationObject.operationId) {
824
+ result.reason.push(ErrorType.MissingOperationId);
825
+ }
826
+ // validate server
827
+ const validateServerResult = this.validateServer(method, path);
828
+ result.reason.push(...validateServerResult.reason);
829
+ // validate response
830
+ const validateResponseResult = this.validateResponse(method, path);
831
+ result.reason.push(...validateResponseResult.reason);
832
+ // validate requestBody
833
+ const requestBody = operationObject.requestBody;
834
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
835
+ if (Utils.containMultipleMediaTypes(requestBody)) {
836
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
837
+ }
838
+ if (requestJsonBody) {
839
+ const requestBodySchema = requestJsonBody.schema;
840
+ if (requestBodySchema.type !== "object") {
841
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
842
+ }
843
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
844
+ result.reason.push(...requestBodyParamResult.reason);
845
+ }
846
+ // validate parameters
847
+ const paramObject = operationObject.parameters;
848
+ const paramResult = this.checkParamSchema(paramObject);
849
+ result.reason.push(...paramResult.reason);
850
+ if (result.reason.length > 0) {
851
+ result.isValid = false;
852
+ }
853
+ return result;
854
+ }
855
+ }
856
+
857
+ // Copyright (c) Microsoft Corporation.
858
+ class SMEValidator extends Validator {
859
+ constructor(spec, options) {
860
+ super();
861
+ this.projectType = ProjectType.SME;
862
+ this.options = options;
863
+ this.spec = spec;
864
+ }
865
+ validateSpec() {
866
+ const result = { errors: [], warnings: [] };
867
+ // validate spec version
868
+ let validationResult = this.validateSpecVersion();
869
+ result.errors.push(...validationResult.errors);
870
+ // validate spec server
871
+ validationResult = this.validateSpecServer();
872
+ result.errors.push(...validationResult.errors);
873
+ // validate no supported API
874
+ validationResult = this.validateSpecNoSupportAPI();
875
+ result.errors.push(...validationResult.errors);
876
+ // validate operationId missing
877
+ if (this.options.allowMissingId) {
878
+ validationResult = this.validateSpecOperationId();
879
+ result.warnings.push(...validationResult.warnings);
880
+ }
881
+ return result;
882
+ }
883
+ validateAPI(method, path) {
884
+ const result = { isValid: true, reason: [] };
885
+ method = method.toLocaleLowerCase();
886
+ // validate method and path
887
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
888
+ if (!methodAndPathResult.isValid) {
889
+ return methodAndPathResult;
890
+ }
891
+ const operationObject = this.spec.paths[path][method];
892
+ // validate auth
893
+ const authCheckResult = this.validateAuth(method, path);
894
+ result.reason.push(...authCheckResult.reason);
895
+ // validate operationId
896
+ if (!this.options.allowMissingId && !operationObject.operationId) {
897
+ result.reason.push(ErrorType.MissingOperationId);
898
+ }
899
+ // validate server
900
+ const validateServerResult = this.validateServer(method, path);
901
+ result.reason.push(...validateServerResult.reason);
902
+ // validate response
903
+ const validateResponseResult = this.validateResponse(method, path);
904
+ result.reason.push(...validateResponseResult.reason);
905
+ let postBodyResult = {
906
+ requiredNum: 0,
907
+ optionalNum: 0,
908
+ isValid: true,
909
+ reason: [],
910
+ };
911
+ // validate requestBody
912
+ const requestBody = operationObject.requestBody;
913
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
914
+ if (Utils.containMultipleMediaTypes(requestBody)) {
915
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
916
+ }
917
+ if (requestJsonBody) {
918
+ const requestBodySchema = requestJsonBody.schema;
919
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
920
+ result.reason.push(...postBodyResult.reason);
921
+ }
922
+ // validate parameters
923
+ const paramObject = operationObject.parameters;
924
+ const paramResult = this.checkParamSchema(paramObject);
925
+ result.reason.push(...paramResult.reason);
926
+ // validate total parameters count
927
+ if (paramResult.isValid && postBodyResult.isValid) {
928
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
929
+ result.reason.push(...paramCountResult.reason);
930
+ }
931
+ if (result.reason.length > 0) {
932
+ result.isValid = false;
933
+ }
934
+ return result;
935
+ }
936
+ validateParamCount(postBodyResult, paramResult) {
937
+ const result = { isValid: true, reason: [] };
938
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
939
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
940
+ if (totalRequiredParams > 1) {
941
+ if (!this.options.allowMultipleParameters ||
942
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
943
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
944
+ }
945
+ }
946
+ else if (totalParams === 0) {
947
+ result.reason.push(ErrorType.NoParameter);
948
+ }
949
+ return result;
950
+ }
951
+ }
952
+ SMEValidator.SMERequiredParamsMaxNum = 5;
953
+
954
+ // Copyright (c) Microsoft Corporation.
955
+ class TeamsAIValidator extends Validator {
956
+ constructor(spec, options) {
957
+ super();
958
+ this.projectType = ProjectType.TeamsAi;
959
+ this.options = options;
960
+ this.spec = spec;
961
+ }
962
+ validateSpec() {
963
+ const result = { errors: [], warnings: [] };
964
+ // validate spec server
965
+ let validationResult = this.validateSpecServer();
966
+ result.errors.push(...validationResult.errors);
967
+ // validate no supported API
968
+ validationResult = this.validateSpecNoSupportAPI();
969
+ result.errors.push(...validationResult.errors);
970
+ return result;
971
+ }
972
+ validateAPI(method, path) {
973
+ const result = { isValid: true, reason: [] };
974
+ method = method.toLocaleLowerCase();
975
+ // validate method and path
976
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
977
+ if (!methodAndPathResult.isValid) {
978
+ return methodAndPathResult;
979
+ }
980
+ const operationObject = this.spec.paths[path][method];
981
+ // validate operationId
982
+ if (!this.options.allowMissingId && !operationObject.operationId) {
983
+ result.reason.push(ErrorType.MissingOperationId);
984
+ }
985
+ // validate server
986
+ const validateServerResult = this.validateServer(method, path);
987
+ result.reason.push(...validateServerResult.reason);
988
+ if (result.reason.length > 0) {
989
+ result.isValid = false;
990
+ }
991
+ return result;
992
+ }
993
+ }
994
+
995
+ class ValidatorFactory {
996
+ static create(spec, options) {
997
+ var _a;
998
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
999
+ switch (type) {
1000
+ case ProjectType.SME:
1001
+ return new SMEValidator(spec, options);
1002
+ case ProjectType.Copilot:
1003
+ return new CopilotValidator(spec, options);
1004
+ case ProjectType.TeamsAi:
1005
+ return new TeamsAIValidator(spec, options);
1006
+ default:
1007
+ throw new Error(`Invalid project type: ${type}`);
1008
+ }
832
1009
  }
833
1010
  }
834
1011
 
@@ -846,7 +1023,8 @@ class SpecFilter {
846
1023
  if (ConstantString.AllOperationMethods.includes(methodName) &&
847
1024
  pathObj &&
848
1025
  pathObj[methodName]) {
849
- const validateResult = Utils.isSupportedApi(methodName, path, resolvedSpec, options);
1026
+ const validator = ValidatorFactory.create(resolvedSpec, options);
1027
+ const validateResult = validator.validateAPI(methodName, path);
850
1028
  if (!validateResult.isValid) {
851
1029
  continue;
852
1030
  }
@@ -879,13 +1057,14 @@ class ManifestUpdater {
879
1057
  const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
880
1058
  manifest.plugins = [
881
1059
  {
882
- pluginFile: apiPluginRelativePath,
1060
+ file: apiPluginRelativePath,
1061
+ id: ConstantString.DefaultPluginId,
883
1062
  },
884
1063
  ];
885
1064
  const appName = this.removeEnvs(manifest.name.short);
886
1065
  ManifestUpdater.updateManifestDescription(manifest, spec);
887
1066
  const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
888
- const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
1067
+ const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
889
1068
  return [manifest, apiPlugin];
890
1069
  }
891
1070
  static updateManifestDescription(manifest, spec) {
@@ -909,8 +1088,8 @@ class ManifestUpdater {
909
1088
  }
910
1089
  return parameter;
911
1090
  }
912
- static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
913
- var _a, _b, _c;
1091
+ static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
1092
+ var _a, _b, _c, _d;
914
1093
  const functions = [];
915
1094
  const functionNames = [];
916
1095
  const paths = spec.paths;
@@ -972,24 +1151,53 @@ class ManifestUpdater {
972
1151
  }
973
1152
  }
974
1153
  }
975
- const apiPlugin = {
976
- schema_version: "v2",
977
- name_for_human: appName,
978
- description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
979
- functions: functions,
980
- runtimes: [
981
- {
982
- type: "OpenApi",
983
- auth: {
984
- type: "none", // TODO, support auth in the future
985
- },
986
- spec: {
987
- url: specRelativePath,
988
- },
989
- run_for_functions: functionNames,
1154
+ let apiPlugin;
1155
+ if (await fs.pathExists(apiPluginFilePath)) {
1156
+ apiPlugin = await fs.readJSON(apiPluginFilePath);
1157
+ }
1158
+ else {
1159
+ apiPlugin = {
1160
+ schema_version: "v2",
1161
+ name_for_human: "",
1162
+ description_for_human: "",
1163
+ functions: [],
1164
+ runtimes: [],
1165
+ };
1166
+ }
1167
+ apiPlugin.functions = apiPlugin.functions || [];
1168
+ for (const func of functions) {
1169
+ const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
1170
+ if (index === -1) {
1171
+ apiPlugin.functions.push(func);
1172
+ }
1173
+ else {
1174
+ apiPlugin.functions[index] = func;
1175
+ }
1176
+ }
1177
+ apiPlugin.runtimes = apiPlugin.runtimes || [];
1178
+ const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath);
1179
+ if (index === -1) {
1180
+ apiPlugin.runtimes.push({
1181
+ type: "OpenApi",
1182
+ auth: {
1183
+ type: "none",
990
1184
  },
991
- ],
992
- };
1185
+ spec: {
1186
+ url: specRelativePath,
1187
+ },
1188
+ run_for_functions: functionNames,
1189
+ });
1190
+ }
1191
+ else {
1192
+ apiPlugin.runtimes[index].run_for_functions = functionNames;
1193
+ }
1194
+ if (!apiPlugin.name_for_human) {
1195
+ apiPlugin.name_for_human = appName;
1196
+ }
1197
+ if (!apiPlugin.description_for_human) {
1198
+ apiPlugin.description_for_human =
1199
+ (_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
1200
+ }
993
1201
  return apiPlugin;
994
1202
  }
995
1203
  static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
@@ -1394,6 +1602,8 @@ class SpecParser {
1394
1602
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
1395
1603
  };
1396
1604
  }
1605
+ const errors = [];
1606
+ const warnings = [];
1397
1607
  if (!this.options.allowSwagger && this.isSwaggerFile) {
1398
1608
  return {
1399
1609
  status: ValidationStatus.Error,
@@ -1403,23 +1613,38 @@ class SpecParser {
1403
1613
  ],
1404
1614
  };
1405
1615
  }
1406
- if (this.options.projectType === ProjectType.SME ||
1407
- this.options.projectType === ProjectType.Copilot) {
1408
- if (this.spec.openapi >= "3.1.0") {
1409
- return {
1410
- status: ValidationStatus.Error,
1411
- warnings: [],
1412
- errors: [
1413
- {
1414
- type: ErrorType.SpecVersionNotSupported,
1415
- content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
1416
- data: this.spec.openapi,
1417
- },
1418
- ],
1419
- };
1420
- }
1616
+ // Remote reference not supported
1617
+ const refPaths = this.parser.$refs.paths();
1618
+ // refPaths [0] is the current spec file path
1619
+ if (refPaths.length > 1) {
1620
+ errors.push({
1621
+ type: ErrorType.RemoteRefNotSupported,
1622
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1623
+ data: refPaths,
1624
+ });
1625
+ }
1626
+ if (!!this.isSwaggerFile && this.options.allowSwagger) {
1627
+ warnings.push({
1628
+ type: WarningType.ConvertSwaggerToOpenAPI,
1629
+ content: ConstantString.ConvertSwaggerToOpenAPI,
1630
+ });
1421
1631
  }
1422
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1632
+ const validator = this.getValidator(this.spec);
1633
+ const validationResult = validator.validateSpec();
1634
+ warnings.push(...validationResult.warnings);
1635
+ errors.push(...validationResult.errors);
1636
+ let status = ValidationStatus.Valid;
1637
+ if (warnings.length > 0 && errors.length === 0) {
1638
+ status = ValidationStatus.Warning;
1639
+ }
1640
+ else if (errors.length > 0) {
1641
+ status = ValidationStatus.Error;
1642
+ }
1643
+ return {
1644
+ status: status,
1645
+ warnings: warnings,
1646
+ errors: errors,
1647
+ };
1423
1648
  }
1424
1649
  catch (err) {
1425
1650
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -1448,34 +1673,27 @@ class SpecParser {
1448
1673
  for (const apiKey in apiMap) {
1449
1674
  const { operation, isValid, reason } = apiMap[apiKey];
1450
1675
  const [method, path] = apiKey.split(" ");
1676
+ const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1451
1677
  const apiResult = {
1452
- api: "",
1678
+ api: apiKey,
1453
1679
  server: "",
1454
- operationId: "",
1680
+ operationId: operationId,
1455
1681
  isValid: isValid,
1456
1682
  reason: reason,
1457
1683
  };
1458
- const rootServer = spec.servers && spec.servers[0];
1459
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
1460
- const operationServer = operation.servers && operation.servers[0];
1461
- const serverUrl = operationServer || methodServer || rootServer;
1462
- if (!serverUrl) {
1463
- throw new SpecParserError(ConstantString.NoServerInformation, ErrorType.NoServerInformation);
1464
- }
1465
- apiResult.server = Utils.resolveEnv(serverUrl.url);
1466
- let operationId = operation.operationId;
1467
- if (!operationId) {
1468
- operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1469
- }
1470
- apiResult.operationId = operationId;
1471
- const authArray = Utils.getAuthArray(operation.security, spec);
1472
- for (const auths of authArray) {
1473
- if (auths.length === 1) {
1474
- apiResult.auth = auths[0];
1475
- break;
1684
+ if (isValid) {
1685
+ const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
1686
+ if (serverObj) {
1687
+ apiResult.server = Utils.resolveEnv(serverObj.url);
1688
+ }
1689
+ const authArray = Utils.getAuthArray(operation.security, spec);
1690
+ for (const auths of authArray) {
1691
+ if (auths.length === 1) {
1692
+ apiResult.auth = auths[0];
1693
+ break;
1694
+ }
1476
1695
  }
1477
1696
  }
1478
- apiResult.api = apiKey;
1479
1697
  result.APIs.push(apiResult);
1480
1698
  }
1481
1699
  result.allAPICount = result.APIs.length;
@@ -1655,12 +1873,18 @@ class SpecParser {
1655
1873
  }
1656
1874
  }
1657
1875
  getAPIs(spec) {
1658
- if (this.apiMap !== undefined) {
1659
- return this.apiMap;
1876
+ const validator = this.getValidator(spec);
1877
+ const apiMap = validator.listAPIs();
1878
+ this.apiMap = apiMap;
1879
+ return apiMap;
1880
+ }
1881
+ getValidator(spec) {
1882
+ if (this.validator) {
1883
+ return this.validator;
1660
1884
  }
1661
- const result = Utils.listAPIs(spec, this.options);
1662
- this.apiMap = result;
1663
- return result;
1885
+ const validator = ValidatorFactory.create(spec, this.options);
1886
+ this.validator = validator;
1887
+ return validator;
1664
1888
  }
1665
1889
  }
1666
1890