@microsoft/m365-spec-parser 0.1.1-alpha.4f2290daa.0 → 0.1.1-alpha.5bc137917.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.
@@ -107,6 +107,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
107
107
  ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
108
108
  ConstantString.AdaptiveCardType = "AdaptiveCard";
109
109
  ConstantString.TextBlockType = "TextBlock";
110
+ ConstantString.ImageType = "Image";
110
111
  ConstantString.ContainerType = "Container";
111
112
  ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
112
113
  ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
@@ -170,7 +171,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
170
171
  ConstantString.ParameterDescriptionMaxLens = 128;
171
172
  ConstantString.CommandTitleMaxLens = 32;
172
173
  ConstantString.ParameterTitleMaxLens = 32;
173
- ConstantString.SMERequiredParamsMaxNum = 5;
174
+ ConstantString.SMERequiredParamsMaxNum = 5;
175
+ ConstantString.DefaultPluginId = "plugin_1";
174
176
 
175
177
  // Copyright (c) Microsoft Corporation.
176
178
  class Utils {
@@ -185,249 +187,9 @@ class Utils {
185
187
  }
186
188
  return false;
187
189
  }
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
190
  static containMultipleMediaTypes(bodyObject) {
295
191
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
296
192
  }
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
193
  static isBearerTokenAuth(authScheme) {
432
194
  return authScheme.type === "http" && authScheme.scheme === "bearer";
433
195
  }
@@ -435,10 +197,9 @@ class Utils {
435
197
  return authScheme.type === "apiKey";
436
198
  }
437
199
  static isOAuthWithAuthCodeFlow(authScheme) {
438
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
439
- return true;
440
- }
441
- return false;
200
+ return !!(authScheme.type === "oauth2" &&
201
+ authScheme.flows &&
202
+ authScheme.flows.authorizationCode);
442
203
  }
443
204
  static getAuthArray(securities, spec) {
444
205
  var _a;
@@ -463,10 +224,29 @@ class Utils {
463
224
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
464
225
  return result;
465
226
  }
227
+ static getAuthInfo(spec) {
228
+ let authInfo = undefined;
229
+ for (const url in spec.paths) {
230
+ for (const method in spec.paths[url]) {
231
+ const operation = spec.paths[url][method];
232
+ const authArray = Utils.getAuthArray(operation.security, spec);
233
+ if (authArray && authArray.length > 0) {
234
+ const currentAuth = authArray[0][0];
235
+ if (!authInfo) {
236
+ authInfo = authArray[0][0];
237
+ }
238
+ else if (authInfo.name !== currentAuth.name) {
239
+ throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return authInfo;
245
+ }
466
246
  static updateFirstLetter(str) {
467
247
  return str.charAt(0).toUpperCase() + str.slice(1);
468
248
  }
469
- static getResponseJson(operationObject, isTeamsAiProject = false) {
249
+ static getResponseJson(operationObject) {
470
250
  var _a, _b;
471
251
  let json = {};
472
252
  let multipleMediaType = false;
@@ -477,9 +257,6 @@ class Utils {
477
257
  json = responseObject.content["application/json"];
478
258
  if (Utils.containMultipleMediaTypes(responseObject)) {
479
259
  multipleMediaType = true;
480
- if (isTeamsAiProject) {
481
- break;
482
- }
483
260
  json = {};
484
261
  }
485
262
  else {
@@ -718,16 +495,49 @@ class Utils {
718
495
  };
719
496
  return command;
720
497
  }
721
- static listAPIs(spec, options) {
498
+ static format(str, ...args) {
499
+ let index = 0;
500
+ return str.replace(/%s/g, () => {
501
+ const arg = args[index++];
502
+ return arg !== undefined ? arg : "";
503
+ });
504
+ }
505
+ static getSafeRegistrationIdEnvName(authName) {
506
+ if (!authName) {
507
+ return "";
508
+ }
509
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
510
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
511
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
512
+ }
513
+ return safeRegistrationIdEnvName;
514
+ }
515
+ static getServerObject(spec, method, path) {
516
+ const pathObj = spec.paths[path];
517
+ const operationObject = pathObj[method];
518
+ const rootServer = spec.servers && spec.servers[0];
519
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
520
+ const operationServer = operationObject.servers && operationObject.servers[0];
521
+ const serverUrl = operationServer || methodServer || rootServer;
522
+ return serverUrl;
523
+ }
524
+ }
525
+
526
+ // Copyright (c) Microsoft Corporation.
527
+ class Validator {
528
+ listAPIs() {
722
529
  var _a;
723
- const paths = spec.paths;
530
+ if (this.apiMap) {
531
+ return this.apiMap;
532
+ }
533
+ const paths = this.spec.paths;
724
534
  const result = {};
725
535
  for (const path in paths) {
726
536
  const methods = paths[path];
727
537
  for (const method in methods) {
728
538
  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);
539
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
540
+ const validateResult = this.validateAPI(method, path);
731
541
  result[`${method.toUpperCase()} ${path}`] = {
732
542
  operation: operationObject,
733
543
  isValid: validateResult.isValid,
@@ -736,38 +546,48 @@ class Utils {
736
546
  }
737
547
  }
738
548
  }
549
+ this.apiMap = result;
739
550
  return result;
740
551
  }
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,
552
+ validateSpecVersion() {
553
+ const result = { errors: [], warnings: [] };
554
+ if (this.spec.openapi >= "3.1.0") {
555
+ result.errors.push({
556
+ type: ErrorType.SpecVersionNotSupported,
557
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
558
+ data: this.spec.openapi,
749
559
  });
750
560
  }
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
561
+ return result;
562
+ }
563
+ validateSpecServer() {
564
+ const result = { errors: [], warnings: [] };
565
+ const serverErrors = Utils.validateServer(this.spec, this.options);
566
+ result.errors.push(...serverErrors);
567
+ return result;
568
+ }
569
+ validateSpecNoSupportAPI() {
570
+ const result = { errors: [], warnings: [] };
571
+ const apiMap = this.listAPIs();
764
572
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
765
573
  if (validAPIs.length === 0) {
766
- errors.push({
574
+ const data = [];
575
+ for (const key in apiMap) {
576
+ const { reason } = apiMap[key];
577
+ const apiInvalidReason = { api: key, reason: reason };
578
+ data.push(apiInvalidReason);
579
+ }
580
+ result.errors.push({
767
581
  type: ErrorType.NoSupportedApi,
768
582
  content: ConstantString.NoSupportedApi,
583
+ data,
769
584
  });
770
585
  }
586
+ return result;
587
+ }
588
+ validateSpecOperationId() {
589
+ const result = { errors: [], warnings: [] };
590
+ const apiMap = this.listAPIs();
771
591
  // OperationId missing
772
592
  const apisMissingOperationId = [];
773
593
  for (const key in apiMap) {
@@ -777,54 +597,430 @@ class Utils {
777
597
  }
778
598
  }
779
599
  if (apisMissingOperationId.length > 0) {
780
- warnings.push({
600
+ result.warnings.push({
781
601
  type: WarningType.OperationIdMissing,
782
602
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
783
603
  data: apisMissingOperationId,
784
604
  });
785
605
  }
786
- let status = ValidationStatus.Valid;
787
- if (warnings.length > 0 && errors.length === 0) {
788
- status = ValidationStatus.Warning;
606
+ return result;
607
+ }
608
+ validateMethodAndPath(method, path) {
609
+ const result = { isValid: true, reason: [] };
610
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
611
+ result.isValid = false;
612
+ result.reason.push(ErrorType.MethodNotAllowed);
613
+ return result;
789
614
  }
790
- else if (errors.length > 0) {
791
- status = ValidationStatus.Error;
615
+ const pathObj = this.spec.paths[path];
616
+ if (!pathObj || !pathObj[method]) {
617
+ result.isValid = false;
618
+ result.reason.push(ErrorType.UrlPathNotExist);
619
+ return result;
792
620
  }
793
- return {
794
- status,
795
- warnings,
796
- errors,
797
- };
621
+ return result;
798
622
  }
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
- });
623
+ validateResponse(method, path) {
624
+ const result = { isValid: true, reason: [] };
625
+ const operationObject = this.spec.paths[path][method];
626
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
627
+ if (this.options.projectType === ProjectType.SME) {
628
+ // only support response body only contains “application/json” content type
629
+ if (multipleMediaType) {
630
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
631
+ }
632
+ else if (Object.keys(json).length === 0) {
633
+ // response body should not be empty
634
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
635
+ }
636
+ }
637
+ return result;
805
638
  }
806
- static getSafeRegistrationIdEnvName(authName) {
807
- if (!authName) {
808
- return "";
639
+ validateServer(method, path) {
640
+ const result = { isValid: true, reason: [] };
641
+ const serverObj = Utils.getServerObject(this.spec, method, path);
642
+ if (!serverObj) {
643
+ // should contain server URL
644
+ result.reason.push(ErrorType.NoServerInformation);
809
645
  }
810
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
811
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
812
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
646
+ else {
647
+ // server url should be absolute url with https protocol
648
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
649
+ result.reason.push(...serverValidateResult.map((item) => item.type));
813
650
  }
814
- return safeRegistrationIdEnvName;
651
+ return result;
815
652
  }
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++;
653
+ validateAuth(method, path) {
654
+ const pathObj = this.spec.paths[path];
655
+ const operationObject = pathObj[method];
656
+ const securities = operationObject.security;
657
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
658
+ if (authSchemeArray.length === 0) {
659
+ return { isValid: true, reason: [] };
660
+ }
661
+ if (this.options.allowAPIKeyAuth ||
662
+ this.options.allowOauth2 ||
663
+ this.options.allowBearerTokenAuth) {
664
+ // Currently we don't support multiple auth in one operation
665
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
666
+ return {
667
+ isValid: false,
668
+ reason: [ErrorType.MultipleAuthNotSupported],
669
+ };
670
+ }
671
+ for (const auths of authSchemeArray) {
672
+ if (auths.length === 1) {
673
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
674
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
675
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
676
+ return { isValid: true, reason: [] };
677
+ }
678
+ }
679
+ }
680
+ }
681
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
682
+ }
683
+ checkPostBodySchema(schema, isRequired = false) {
684
+ var _a;
685
+ const paramResult = {
686
+ requiredNum: 0,
687
+ optionalNum: 0,
688
+ isValid: true,
689
+ reason: [],
690
+ };
691
+ if (Object.keys(schema).length === 0) {
692
+ return paramResult;
693
+ }
694
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
695
+ const isCopilot = this.projectType === ProjectType.Copilot;
696
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
697
+ paramResult.isValid = false;
698
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
699
+ return paramResult;
700
+ }
701
+ if (schema.type === "string" ||
702
+ schema.type === "integer" ||
703
+ schema.type === "boolean" ||
704
+ schema.type === "number") {
705
+ if (isRequiredWithoutDefault) {
706
+ paramResult.requiredNum = paramResult.requiredNum + 1;
707
+ }
708
+ else {
709
+ paramResult.optionalNum = paramResult.optionalNum + 1;
710
+ }
711
+ }
712
+ else if (schema.type === "object") {
713
+ const { properties } = schema;
714
+ for (const property in properties) {
715
+ let isRequired = false;
716
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
717
+ isRequired = true;
824
718
  }
719
+ const result = this.checkPostBodySchema(properties[property], isRequired);
720
+ paramResult.requiredNum += result.requiredNum;
721
+ paramResult.optionalNum += result.optionalNum;
722
+ paramResult.isValid = paramResult.isValid && result.isValid;
723
+ paramResult.reason.push(...result.reason);
825
724
  }
826
725
  }
827
- return count;
726
+ else {
727
+ if (isRequiredWithoutDefault && !isCopilot) {
728
+ paramResult.isValid = false;
729
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
730
+ }
731
+ }
732
+ return paramResult;
733
+ }
734
+ checkParamSchema(paramObject) {
735
+ const paramResult = {
736
+ requiredNum: 0,
737
+ optionalNum: 0,
738
+ isValid: true,
739
+ reason: [],
740
+ };
741
+ if (!paramObject) {
742
+ return paramResult;
743
+ }
744
+ const isCopilot = this.projectType === ProjectType.Copilot;
745
+ for (let i = 0; i < paramObject.length; i++) {
746
+ const param = paramObject[i];
747
+ const schema = param.schema;
748
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
749
+ paramResult.isValid = false;
750
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
751
+ continue;
752
+ }
753
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
754
+ if (isCopilot) {
755
+ if (isRequiredWithoutDefault) {
756
+ paramResult.requiredNum = paramResult.requiredNum + 1;
757
+ }
758
+ else {
759
+ paramResult.optionalNum = paramResult.optionalNum + 1;
760
+ }
761
+ continue;
762
+ }
763
+ if (param.in === "header" || param.in === "cookie") {
764
+ if (isRequiredWithoutDefault) {
765
+ paramResult.isValid = false;
766
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
767
+ }
768
+ continue;
769
+ }
770
+ if (schema.type !== "boolean" &&
771
+ schema.type !== "string" &&
772
+ schema.type !== "number" &&
773
+ schema.type !== "integer") {
774
+ if (isRequiredWithoutDefault) {
775
+ paramResult.isValid = false;
776
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
777
+ }
778
+ continue;
779
+ }
780
+ if (param.in === "query" || param.in === "path") {
781
+ if (isRequiredWithoutDefault) {
782
+ paramResult.requiredNum = paramResult.requiredNum + 1;
783
+ }
784
+ else {
785
+ paramResult.optionalNum = paramResult.optionalNum + 1;
786
+ }
787
+ }
788
+ }
789
+ return paramResult;
790
+ }
791
+ hasNestedObjectInSchema(schema) {
792
+ if (schema.type === "object") {
793
+ for (const property in schema.properties) {
794
+ const nestedSchema = schema.properties[property];
795
+ if (nestedSchema.type === "object") {
796
+ return true;
797
+ }
798
+ }
799
+ }
800
+ return false;
801
+ }
802
+ }
803
+
804
+ // Copyright (c) Microsoft Corporation.
805
+ class CopilotValidator extends Validator {
806
+ constructor(spec, options) {
807
+ super();
808
+ this.projectType = ProjectType.Copilot;
809
+ this.options = options;
810
+ this.spec = spec;
811
+ }
812
+ validateSpec() {
813
+ const result = { errors: [], warnings: [] };
814
+ // validate spec version
815
+ let validationResult = this.validateSpecVersion();
816
+ result.errors.push(...validationResult.errors);
817
+ // validate spec server
818
+ validationResult = this.validateSpecServer();
819
+ result.errors.push(...validationResult.errors);
820
+ // validate no supported API
821
+ validationResult = this.validateSpecNoSupportAPI();
822
+ result.errors.push(...validationResult.errors);
823
+ // validate operationId missing
824
+ validationResult = this.validateSpecOperationId();
825
+ result.warnings.push(...validationResult.warnings);
826
+ return result;
827
+ }
828
+ validateAPI(method, path) {
829
+ const result = { isValid: true, reason: [] };
830
+ method = method.toLocaleLowerCase();
831
+ // validate method and path
832
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
833
+ if (!methodAndPathResult.isValid) {
834
+ return methodAndPathResult;
835
+ }
836
+ const operationObject = this.spec.paths[path][method];
837
+ // validate auth
838
+ const authCheckResult = this.validateAuth(method, path);
839
+ result.reason.push(...authCheckResult.reason);
840
+ // validate operationId
841
+ if (!this.options.allowMissingId && !operationObject.operationId) {
842
+ result.reason.push(ErrorType.MissingOperationId);
843
+ }
844
+ // validate server
845
+ const validateServerResult = this.validateServer(method, path);
846
+ result.reason.push(...validateServerResult.reason);
847
+ // validate response
848
+ const validateResponseResult = this.validateResponse(method, path);
849
+ result.reason.push(...validateResponseResult.reason);
850
+ // validate requestBody
851
+ const requestBody = operationObject.requestBody;
852
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
853
+ if (requestJsonBody) {
854
+ const requestBodySchema = requestJsonBody.schema;
855
+ if (requestBodySchema.type !== "object") {
856
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
857
+ }
858
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
859
+ result.reason.push(...requestBodyParamResult.reason);
860
+ }
861
+ // validate parameters
862
+ const paramObject = operationObject.parameters;
863
+ const paramResult = this.checkParamSchema(paramObject);
864
+ result.reason.push(...paramResult.reason);
865
+ if (result.reason.length > 0) {
866
+ result.isValid = false;
867
+ }
868
+ return result;
869
+ }
870
+ }
871
+
872
+ // Copyright (c) Microsoft Corporation.
873
+ class SMEValidator extends Validator {
874
+ constructor(spec, options) {
875
+ super();
876
+ this.projectType = ProjectType.SME;
877
+ this.options = options;
878
+ this.spec = spec;
879
+ }
880
+ validateSpec() {
881
+ const result = { errors: [], warnings: [] };
882
+ // validate spec version
883
+ let validationResult = this.validateSpecVersion();
884
+ result.errors.push(...validationResult.errors);
885
+ // validate spec server
886
+ validationResult = this.validateSpecServer();
887
+ result.errors.push(...validationResult.errors);
888
+ // validate no supported API
889
+ validationResult = this.validateSpecNoSupportAPI();
890
+ result.errors.push(...validationResult.errors);
891
+ // validate operationId missing
892
+ if (this.options.allowMissingId) {
893
+ validationResult = this.validateSpecOperationId();
894
+ result.warnings.push(...validationResult.warnings);
895
+ }
896
+ return result;
897
+ }
898
+ validateAPI(method, path) {
899
+ const result = { isValid: true, reason: [] };
900
+ method = method.toLocaleLowerCase();
901
+ // validate method and path
902
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
903
+ if (!methodAndPathResult.isValid) {
904
+ return methodAndPathResult;
905
+ }
906
+ const operationObject = this.spec.paths[path][method];
907
+ // validate auth
908
+ const authCheckResult = this.validateAuth(method, path);
909
+ result.reason.push(...authCheckResult.reason);
910
+ // validate operationId
911
+ if (!this.options.allowMissingId && !operationObject.operationId) {
912
+ result.reason.push(ErrorType.MissingOperationId);
913
+ }
914
+ // validate server
915
+ const validateServerResult = this.validateServer(method, path);
916
+ result.reason.push(...validateServerResult.reason);
917
+ // validate response
918
+ const validateResponseResult = this.validateResponse(method, path);
919
+ result.reason.push(...validateResponseResult.reason);
920
+ let postBodyResult = {
921
+ requiredNum: 0,
922
+ optionalNum: 0,
923
+ isValid: true,
924
+ reason: [],
925
+ };
926
+ // validate requestBody
927
+ const requestBody = operationObject.requestBody;
928
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
929
+ if (Utils.containMultipleMediaTypes(requestBody)) {
930
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
931
+ }
932
+ if (requestJsonBody) {
933
+ const requestBodySchema = requestJsonBody.schema;
934
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
935
+ result.reason.push(...postBodyResult.reason);
936
+ }
937
+ // validate parameters
938
+ const paramObject = operationObject.parameters;
939
+ const paramResult = this.checkParamSchema(paramObject);
940
+ result.reason.push(...paramResult.reason);
941
+ // validate total parameters count
942
+ if (paramResult.isValid && postBodyResult.isValid) {
943
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
944
+ result.reason.push(...paramCountResult.reason);
945
+ }
946
+ if (result.reason.length > 0) {
947
+ result.isValid = false;
948
+ }
949
+ return result;
950
+ }
951
+ validateParamCount(postBodyResult, paramResult) {
952
+ const result = { isValid: true, reason: [] };
953
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
954
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
955
+ if (totalRequiredParams > 1) {
956
+ if (!this.options.allowMultipleParameters ||
957
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
958
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
959
+ }
960
+ }
961
+ else if (totalParams === 0) {
962
+ result.reason.push(ErrorType.NoParameter);
963
+ }
964
+ return result;
965
+ }
966
+ }
967
+ SMEValidator.SMERequiredParamsMaxNum = 5;
968
+
969
+ // Copyright (c) Microsoft Corporation.
970
+ class TeamsAIValidator extends Validator {
971
+ constructor(spec, options) {
972
+ super();
973
+ this.projectType = ProjectType.TeamsAi;
974
+ this.options = options;
975
+ this.spec = spec;
976
+ }
977
+ validateSpec() {
978
+ const result = { errors: [], warnings: [] };
979
+ // validate spec server
980
+ let validationResult = this.validateSpecServer();
981
+ result.errors.push(...validationResult.errors);
982
+ // validate no supported API
983
+ validationResult = this.validateSpecNoSupportAPI();
984
+ result.errors.push(...validationResult.errors);
985
+ return result;
986
+ }
987
+ validateAPI(method, path) {
988
+ const result = { isValid: true, reason: [] };
989
+ method = method.toLocaleLowerCase();
990
+ // validate method and path
991
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
992
+ if (!methodAndPathResult.isValid) {
993
+ return methodAndPathResult;
994
+ }
995
+ const operationObject = this.spec.paths[path][method];
996
+ // validate operationId
997
+ if (!this.options.allowMissingId && !operationObject.operationId) {
998
+ result.reason.push(ErrorType.MissingOperationId);
999
+ }
1000
+ // validate server
1001
+ const validateServerResult = this.validateServer(method, path);
1002
+ result.reason.push(...validateServerResult.reason);
1003
+ if (result.reason.length > 0) {
1004
+ result.isValid = false;
1005
+ }
1006
+ return result;
1007
+ }
1008
+ }
1009
+
1010
+ class ValidatorFactory {
1011
+ static create(spec, options) {
1012
+ var _a;
1013
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1014
+ switch (type) {
1015
+ case ProjectType.SME:
1016
+ return new SMEValidator(spec, options);
1017
+ case ProjectType.Copilot:
1018
+ return new CopilotValidator(spec, options);
1019
+ case ProjectType.TeamsAi:
1020
+ return new TeamsAIValidator(spec, options);
1021
+ default:
1022
+ throw new Error(`Invalid project type: ${type}`);
1023
+ }
828
1024
  }
829
1025
  }
830
1026
 
@@ -847,6 +1043,8 @@ class SpecParser {
847
1043
  allowBearerTokenAuth: false,
848
1044
  allowOauth2: false,
849
1045
  allowMethods: ["get", "post"],
1046
+ allowConversationStarters: false,
1047
+ allowResponseSemantics: false,
850
1048
  projectType: ProjectType.SME,
851
1049
  };
852
1050
  this.pathOrSpec = pathOrDoc;
@@ -862,11 +1060,7 @@ class SpecParser {
862
1060
  try {
863
1061
  try {
864
1062
  await this.loadSpec();
865
- await this.parser.validate(this.spec, {
866
- validate: {
867
- schema: false,
868
- },
869
- });
1063
+ await this.parser.validate(this.spec);
870
1064
  }
871
1065
  catch (e) {
872
1066
  return {
@@ -875,16 +1069,46 @@ class SpecParser {
875
1069
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
876
1070
  };
877
1071
  }
1072
+ const errors = [];
1073
+ const warnings = [];
878
1074
  if (!this.options.allowSwagger && this.isSwaggerFile) {
879
1075
  return {
880
1076
  status: ValidationStatus.Error,
881
1077
  warnings: [],
882
1078
  errors: [
883
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1079
+ {
1080
+ type: ErrorType.SwaggerNotSupported,
1081
+ content: ConstantString.SwaggerNotSupported,
1082
+ },
884
1083
  ],
885
1084
  };
886
1085
  }
887
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1086
+ // Remote reference not supported
1087
+ const refPaths = this.parser.$refs.paths();
1088
+ // refPaths [0] is the current spec file path
1089
+ if (refPaths.length > 1) {
1090
+ errors.push({
1091
+ type: ErrorType.RemoteRefNotSupported,
1092
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1093
+ data: refPaths,
1094
+ });
1095
+ }
1096
+ const validator = this.getValidator(this.spec);
1097
+ const validationResult = validator.validateSpec();
1098
+ warnings.push(...validationResult.warnings);
1099
+ errors.push(...validationResult.errors);
1100
+ let status = ValidationStatus.Valid;
1101
+ if (warnings.length > 0 && errors.length === 0) {
1102
+ status = ValidationStatus.Warning;
1103
+ }
1104
+ else if (errors.length > 0) {
1105
+ status = ValidationStatus.Error;
1106
+ }
1107
+ return {
1108
+ status: status,
1109
+ warnings: warnings,
1110
+ errors: errors,
1111
+ };
888
1112
  }
889
1113
  catch (err) {
890
1114
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -893,17 +1117,20 @@ class SpecParser {
893
1117
  async listSupportedAPIInfo() {
894
1118
  try {
895
1119
  await this.loadSpec();
896
- const apiMap = this.getAllSupportedAPIs(this.spec);
1120
+ const apiMap = this.getAPIs(this.spec);
897
1121
  const apiInfos = [];
898
1122
  for (const key in apiMap) {
899
- const pathObjectItem = apiMap[key];
1123
+ const { operation, isValid } = apiMap[key];
1124
+ if (!isValid) {
1125
+ continue;
1126
+ }
900
1127
  const [method, path] = key.split(" ");
901
- const operationId = pathObjectItem.operationId;
1128
+ const operationId = operation.operationId;
902
1129
  // In Browser environment, this api is by default not support api without operationId
903
1130
  if (!operationId) {
904
1131
  continue;
905
1132
  }
906
- const command = Utils.parseApiInfo(pathObjectItem, this.options);
1133
+ const command = Utils.parseApiInfo(operation, this.options);
907
1134
  const apiInfo = {
908
1135
  method: method,
909
1136
  path: path,
@@ -970,31 +1197,18 @@ class SpecParser {
970
1197
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
971
1198
  }
972
1199
  }
973
- getAllSupportedAPIs(spec) {
974
- if (this.apiMap !== undefined) {
975
- return this.apiMap;
976
- }
977
- const result = this.listSupportedAPIs(spec, this.options);
978
- this.apiMap = result;
979
- return result;
1200
+ getAPIs(spec) {
1201
+ const validator = this.getValidator(spec);
1202
+ const apiMap = validator.listAPIs();
1203
+ return apiMap;
980
1204
  }
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
- }
1205
+ getValidator(spec) {
1206
+ if (this.validator) {
1207
+ return this.validator;
996
1208
  }
997
- return result;
1209
+ const validator = ValidatorFactory.create(spec, this.options);
1210
+ this.validator = validator;
1211
+ return validator;
998
1212
  }
999
1213
  }
1000
1214