@microsoft/m365-spec-parser 0.1.1-alpha.4cb5c08a8.0 → 0.1.1-alpha.4e708f092.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -466,7 +227,7 @@ class Utils {
466
227
  static updateFirstLetter(str) {
467
228
  return str.charAt(0).toUpperCase() + str.slice(1);
468
229
  }
469
- static getResponseJson(operationObject, isTeamsAiProject = false) {
230
+ static getResponseJson(operationObject) {
470
231
  var _a, _b;
471
232
  let json = {};
472
233
  let multipleMediaType = false;
@@ -477,9 +238,6 @@ class Utils {
477
238
  json = responseObject.content["application/json"];
478
239
  if (Utils.containMultipleMediaTypes(responseObject)) {
479
240
  multipleMediaType = true;
480
- if (isTeamsAiProject) {
481
- break;
482
- }
483
241
  json = {};
484
242
  }
485
243
  else {
@@ -707,13 +465,7 @@ class Utils {
707
465
  }
708
466
  }
709
467
  const operationId = operationItem.operationId;
710
- const parameters = [];
711
- if (requiredParams.length !== 0) {
712
- parameters.push(...requiredParams);
713
- }
714
- else {
715
- parameters.push(optionalParams[0]);
716
- }
468
+ const parameters = [...requiredParams, ...optionalParams];
717
469
  const command = {
718
470
  context: ["compose"],
719
471
  type: "query",
@@ -722,26 +474,51 @@ class Utils {
722
474
  parameters: parameters,
723
475
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
724
476
  };
725
- let warning = undefined;
726
- if (requiredParams.length === 0 && optionalParams.length > 1) {
727
- warning = {
728
- type: WarningType.OperationOnlyContainsOptionalParam,
729
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
730
- data: operationId,
731
- };
477
+ return command;
478
+ }
479
+ static format(str, ...args) {
480
+ let index = 0;
481
+ return str.replace(/%s/g, () => {
482
+ const arg = args[index++];
483
+ return arg !== undefined ? arg : "";
484
+ });
485
+ }
486
+ static getSafeRegistrationIdEnvName(authName) {
487
+ if (!authName) {
488
+ return "";
489
+ }
490
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
491
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
492
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
732
493
  }
733
- return [command, warning];
494
+ return safeRegistrationIdEnvName;
495
+ }
496
+ static getServerObject(spec, method, path) {
497
+ const pathObj = spec.paths[path];
498
+ const operationObject = pathObj[method];
499
+ const rootServer = spec.servers && spec.servers[0];
500
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
501
+ const operationServer = operationObject.servers && operationObject.servers[0];
502
+ const serverUrl = operationServer || methodServer || rootServer;
503
+ return serverUrl;
734
504
  }
735
- static listAPIs(spec, options) {
505
+ }
506
+
507
+ // Copyright (c) Microsoft Corporation.
508
+ class Validator {
509
+ listAPIs() {
736
510
  var _a;
737
- const paths = spec.paths;
511
+ if (this.apiMap) {
512
+ return this.apiMap;
513
+ }
514
+ const paths = this.spec.paths;
738
515
  const result = {};
739
516
  for (const path in paths) {
740
517
  const methods = paths[path];
741
518
  for (const method in methods) {
742
519
  const operationObject = methods[method];
743
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
744
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
520
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
521
+ const validateResult = this.validateAPI(method, path);
745
522
  result[`${method.toUpperCase()} ${path}`] = {
746
523
  operation: operationObject,
747
524
  isValid: validateResult.isValid,
@@ -750,38 +527,48 @@ class Utils {
750
527
  }
751
528
  }
752
529
  }
530
+ this.apiMap = result;
753
531
  return result;
754
532
  }
755
- static validateSpec(spec, parser, isSwaggerFile, options) {
756
- const errors = [];
757
- const warnings = [];
758
- const apiMap = Utils.listAPIs(spec, options);
759
- if (isSwaggerFile) {
760
- warnings.push({
761
- type: WarningType.ConvertSwaggerToOpenAPI,
762
- content: ConstantString.ConvertSwaggerToOpenAPI,
763
- });
764
- }
765
- const serverErrors = Utils.validateServer(spec, options);
766
- errors.push(...serverErrors);
767
- // Remote reference not supported
768
- const refPaths = parser.$refs.paths();
769
- // refPaths [0] is the current spec file path
770
- if (refPaths.length > 1) {
771
- errors.push({
772
- type: ErrorType.RemoteRefNotSupported,
773
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
774
- data: refPaths,
533
+ validateSpecVersion() {
534
+ const result = { errors: [], warnings: [] };
535
+ if (this.spec.openapi >= "3.1.0") {
536
+ result.errors.push({
537
+ type: ErrorType.SpecVersionNotSupported,
538
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
539
+ data: this.spec.openapi,
775
540
  });
776
541
  }
777
- // No supported API
542
+ return result;
543
+ }
544
+ validateSpecServer() {
545
+ const result = { errors: [], warnings: [] };
546
+ const serverErrors = Utils.validateServer(this.spec, this.options);
547
+ result.errors.push(...serverErrors);
548
+ return result;
549
+ }
550
+ validateSpecNoSupportAPI() {
551
+ const result = { errors: [], warnings: [] };
552
+ const apiMap = this.listAPIs();
778
553
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
779
554
  if (validAPIs.length === 0) {
780
- errors.push({
555
+ const data = [];
556
+ for (const key in apiMap) {
557
+ const { reason } = apiMap[key];
558
+ const apiInvalidReason = { api: key, reason: reason };
559
+ data.push(apiInvalidReason);
560
+ }
561
+ result.errors.push({
781
562
  type: ErrorType.NoSupportedApi,
782
563
  content: ConstantString.NoSupportedApi,
564
+ data,
783
565
  });
784
566
  }
567
+ return result;
568
+ }
569
+ validateSpecOperationId() {
570
+ const result = { errors: [], warnings: [] };
571
+ const apiMap = this.listAPIs();
785
572
  // OperationId missing
786
573
  const apisMissingOperationId = [];
787
574
  for (const key in apiMap) {
@@ -791,54 +578,431 @@ class Utils {
791
578
  }
792
579
  }
793
580
  if (apisMissingOperationId.length > 0) {
794
- warnings.push({
581
+ result.warnings.push({
795
582
  type: WarningType.OperationIdMissing,
796
583
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
797
584
  data: apisMissingOperationId,
798
585
  });
799
586
  }
800
- let status = ValidationStatus.Valid;
801
- if (warnings.length > 0 && errors.length === 0) {
802
- status = ValidationStatus.Warning;
587
+ return result;
588
+ }
589
+ validateMethodAndPath(method, path) {
590
+ const result = { isValid: true, reason: [] };
591
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
592
+ result.isValid = false;
593
+ result.reason.push(ErrorType.MethodNotAllowed);
594
+ return result;
803
595
  }
804
- else if (errors.length > 0) {
805
- status = ValidationStatus.Error;
596
+ const pathObj = this.spec.paths[path];
597
+ if (!pathObj || !pathObj[method]) {
598
+ result.isValid = false;
599
+ result.reason.push(ErrorType.UrlPathNotExist);
600
+ return result;
806
601
  }
807
- return {
808
- status,
809
- warnings,
810
- errors,
811
- };
602
+ return result;
812
603
  }
813
- static format(str, ...args) {
814
- let index = 0;
815
- return str.replace(/%s/g, () => {
816
- const arg = args[index++];
817
- return arg !== undefined ? arg : "";
818
- });
604
+ validateResponse(method, path) {
605
+ const result = { isValid: true, reason: [] };
606
+ const operationObject = this.spec.paths[path][method];
607
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
608
+ // only support response body only contains “application/json” content type
609
+ if (multipleMediaType) {
610
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
611
+ }
612
+ else if (Object.keys(json).length === 0) {
613
+ // response body should not be empty
614
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
615
+ }
616
+ return result;
819
617
  }
820
- static getSafeRegistrationIdEnvName(authName) {
821
- if (!authName) {
822
- return "";
618
+ validateServer(method, path) {
619
+ const result = { isValid: true, reason: [] };
620
+ const serverObj = Utils.getServerObject(this.spec, method, path);
621
+ if (!serverObj) {
622
+ // should contain server URL
623
+ result.reason.push(ErrorType.NoServerInformation);
823
624
  }
824
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
825
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
826
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
625
+ else {
626
+ // server url should be absolute url with https protocol
627
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
628
+ result.reason.push(...serverValidateResult.map((item) => item.type));
827
629
  }
828
- return safeRegistrationIdEnvName;
630
+ return result;
829
631
  }
830
- static getAllAPICount(spec) {
831
- let count = 0;
832
- const paths = spec.paths;
833
- for (const path in paths) {
834
- const methods = paths[path];
835
- for (const method in methods) {
836
- if (ConstantString.AllOperationMethods.includes(method)) {
837
- count++;
632
+ validateAuth(method, path) {
633
+ const pathObj = this.spec.paths[path];
634
+ const operationObject = pathObj[method];
635
+ const securities = operationObject.security;
636
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
637
+ if (authSchemeArray.length === 0) {
638
+ return { isValid: true, reason: [] };
639
+ }
640
+ if (this.options.allowAPIKeyAuth ||
641
+ this.options.allowOauth2 ||
642
+ this.options.allowBearerTokenAuth) {
643
+ // Currently we don't support multiple auth in one operation
644
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
645
+ return {
646
+ isValid: false,
647
+ reason: [ErrorType.MultipleAuthNotSupported],
648
+ };
649
+ }
650
+ for (const auths of authSchemeArray) {
651
+ if (auths.length === 1) {
652
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
653
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
654
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
655
+ return { isValid: true, reason: [] };
656
+ }
838
657
  }
839
658
  }
840
659
  }
841
- return count;
660
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
661
+ }
662
+ checkPostBodySchema(schema, isRequired = false) {
663
+ var _a;
664
+ const paramResult = {
665
+ requiredNum: 0,
666
+ optionalNum: 0,
667
+ isValid: true,
668
+ reason: [],
669
+ };
670
+ if (Object.keys(schema).length === 0) {
671
+ return paramResult;
672
+ }
673
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
674
+ const isCopilot = this.projectType === ProjectType.Copilot;
675
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
676
+ paramResult.isValid = false;
677
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
678
+ return paramResult;
679
+ }
680
+ if (schema.type === "string" ||
681
+ schema.type === "integer" ||
682
+ schema.type === "boolean" ||
683
+ schema.type === "number") {
684
+ if (isRequiredWithoutDefault) {
685
+ paramResult.requiredNum = paramResult.requiredNum + 1;
686
+ }
687
+ else {
688
+ paramResult.optionalNum = paramResult.optionalNum + 1;
689
+ }
690
+ }
691
+ else if (schema.type === "object") {
692
+ const { properties } = schema;
693
+ for (const property in properties) {
694
+ let isRequired = false;
695
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
696
+ isRequired = true;
697
+ }
698
+ const result = this.checkPostBodySchema(properties[property], isRequired);
699
+ paramResult.requiredNum += result.requiredNum;
700
+ paramResult.optionalNum += result.optionalNum;
701
+ paramResult.isValid = paramResult.isValid && result.isValid;
702
+ paramResult.reason.push(...result.reason);
703
+ }
704
+ }
705
+ else {
706
+ if (isRequiredWithoutDefault && !isCopilot) {
707
+ paramResult.isValid = false;
708
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
709
+ }
710
+ }
711
+ return paramResult;
712
+ }
713
+ checkParamSchema(paramObject) {
714
+ const paramResult = {
715
+ requiredNum: 0,
716
+ optionalNum: 0,
717
+ isValid: true,
718
+ reason: [],
719
+ };
720
+ if (!paramObject) {
721
+ return paramResult;
722
+ }
723
+ const isCopilot = this.projectType === ProjectType.Copilot;
724
+ for (let i = 0; i < paramObject.length; i++) {
725
+ const param = paramObject[i];
726
+ const schema = param.schema;
727
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
728
+ paramResult.isValid = false;
729
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
730
+ continue;
731
+ }
732
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
733
+ if (isCopilot) {
734
+ if (isRequiredWithoutDefault) {
735
+ paramResult.requiredNum = paramResult.requiredNum + 1;
736
+ }
737
+ else {
738
+ paramResult.optionalNum = paramResult.optionalNum + 1;
739
+ }
740
+ continue;
741
+ }
742
+ if (param.in === "header" || param.in === "cookie") {
743
+ if (isRequiredWithoutDefault) {
744
+ paramResult.isValid = false;
745
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
746
+ }
747
+ continue;
748
+ }
749
+ if (schema.type !== "boolean" &&
750
+ schema.type !== "string" &&
751
+ schema.type !== "number" &&
752
+ schema.type !== "integer") {
753
+ if (isRequiredWithoutDefault) {
754
+ paramResult.isValid = false;
755
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
756
+ }
757
+ continue;
758
+ }
759
+ if (param.in === "query" || param.in === "path") {
760
+ if (isRequiredWithoutDefault) {
761
+ paramResult.requiredNum = paramResult.requiredNum + 1;
762
+ }
763
+ else {
764
+ paramResult.optionalNum = paramResult.optionalNum + 1;
765
+ }
766
+ }
767
+ }
768
+ return paramResult;
769
+ }
770
+ hasNestedObjectInSchema(schema) {
771
+ if (schema.type === "object") {
772
+ for (const property in schema.properties) {
773
+ const nestedSchema = schema.properties[property];
774
+ if (nestedSchema.type === "object") {
775
+ return true;
776
+ }
777
+ }
778
+ }
779
+ return false;
780
+ }
781
+ }
782
+
783
+ // Copyright (c) Microsoft Corporation.
784
+ class CopilotValidator extends Validator {
785
+ constructor(spec, options) {
786
+ super();
787
+ this.projectType = ProjectType.Copilot;
788
+ this.options = options;
789
+ this.spec = spec;
790
+ }
791
+ validateSpec() {
792
+ const result = { errors: [], warnings: [] };
793
+ // validate spec version
794
+ let validationResult = this.validateSpecVersion();
795
+ result.errors.push(...validationResult.errors);
796
+ // validate spec server
797
+ validationResult = this.validateSpecServer();
798
+ result.errors.push(...validationResult.errors);
799
+ // validate no supported API
800
+ validationResult = this.validateSpecNoSupportAPI();
801
+ result.errors.push(...validationResult.errors);
802
+ // validate operationId missing
803
+ validationResult = this.validateSpecOperationId();
804
+ result.warnings.push(...validationResult.warnings);
805
+ return result;
806
+ }
807
+ validateAPI(method, path) {
808
+ const result = { isValid: true, reason: [] };
809
+ method = method.toLocaleLowerCase();
810
+ // validate method and path
811
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
812
+ if (!methodAndPathResult.isValid) {
813
+ return methodAndPathResult;
814
+ }
815
+ const operationObject = this.spec.paths[path][method];
816
+ // validate auth
817
+ const authCheckResult = this.validateAuth(method, path);
818
+ result.reason.push(...authCheckResult.reason);
819
+ // validate operationId
820
+ if (!this.options.allowMissingId && !operationObject.operationId) {
821
+ result.reason.push(ErrorType.MissingOperationId);
822
+ }
823
+ // validate server
824
+ const validateServerResult = this.validateServer(method, path);
825
+ result.reason.push(...validateServerResult.reason);
826
+ // validate response
827
+ const validateResponseResult = this.validateResponse(method, path);
828
+ result.reason.push(...validateResponseResult.reason);
829
+ // validate requestBody
830
+ const requestBody = operationObject.requestBody;
831
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
832
+ if (Utils.containMultipleMediaTypes(requestBody)) {
833
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
834
+ }
835
+ if (requestJsonBody) {
836
+ const requestBodySchema = requestJsonBody.schema;
837
+ if (requestBodySchema.type !== "object") {
838
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
839
+ }
840
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
841
+ result.reason.push(...requestBodyParamResult.reason);
842
+ }
843
+ // validate parameters
844
+ const paramObject = operationObject.parameters;
845
+ const paramResult = this.checkParamSchema(paramObject);
846
+ result.reason.push(...paramResult.reason);
847
+ if (result.reason.length > 0) {
848
+ result.isValid = false;
849
+ }
850
+ return result;
851
+ }
852
+ }
853
+
854
+ // Copyright (c) Microsoft Corporation.
855
+ class SMEValidator extends Validator {
856
+ constructor(spec, options) {
857
+ super();
858
+ this.projectType = ProjectType.SME;
859
+ this.options = options;
860
+ this.spec = spec;
861
+ }
862
+ validateSpec() {
863
+ const result = { errors: [], warnings: [] };
864
+ // validate spec version
865
+ let validationResult = this.validateSpecVersion();
866
+ result.errors.push(...validationResult.errors);
867
+ // validate spec server
868
+ validationResult = this.validateSpecServer();
869
+ result.errors.push(...validationResult.errors);
870
+ // validate no supported API
871
+ validationResult = this.validateSpecNoSupportAPI();
872
+ result.errors.push(...validationResult.errors);
873
+ // validate operationId missing
874
+ if (this.options.allowMissingId) {
875
+ validationResult = this.validateSpecOperationId();
876
+ result.warnings.push(...validationResult.warnings);
877
+ }
878
+ return result;
879
+ }
880
+ validateAPI(method, path) {
881
+ const result = { isValid: true, reason: [] };
882
+ method = method.toLocaleLowerCase();
883
+ // validate method and path
884
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
885
+ if (!methodAndPathResult.isValid) {
886
+ return methodAndPathResult;
887
+ }
888
+ const operationObject = this.spec.paths[path][method];
889
+ // validate auth
890
+ const authCheckResult = this.validateAuth(method, path);
891
+ result.reason.push(...authCheckResult.reason);
892
+ // validate operationId
893
+ if (!this.options.allowMissingId && !operationObject.operationId) {
894
+ result.reason.push(ErrorType.MissingOperationId);
895
+ }
896
+ // validate server
897
+ const validateServerResult = this.validateServer(method, path);
898
+ result.reason.push(...validateServerResult.reason);
899
+ // validate response
900
+ const validateResponseResult = this.validateResponse(method, path);
901
+ result.reason.push(...validateResponseResult.reason);
902
+ let postBodyResult = {
903
+ requiredNum: 0,
904
+ optionalNum: 0,
905
+ isValid: true,
906
+ reason: [],
907
+ };
908
+ // validate requestBody
909
+ const requestBody = operationObject.requestBody;
910
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
911
+ if (Utils.containMultipleMediaTypes(requestBody)) {
912
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
913
+ }
914
+ if (requestJsonBody) {
915
+ const requestBodySchema = requestJsonBody.schema;
916
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
917
+ result.reason.push(...postBodyResult.reason);
918
+ }
919
+ // validate parameters
920
+ const paramObject = operationObject.parameters;
921
+ const paramResult = this.checkParamSchema(paramObject);
922
+ result.reason.push(...paramResult.reason);
923
+ // validate total parameters count
924
+ if (paramResult.isValid && postBodyResult.isValid) {
925
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
926
+ result.reason.push(...paramCountResult.reason);
927
+ }
928
+ if (result.reason.length > 0) {
929
+ result.isValid = false;
930
+ }
931
+ return result;
932
+ }
933
+ validateParamCount(postBodyResult, paramResult) {
934
+ const result = { isValid: true, reason: [] };
935
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
936
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
937
+ if (totalRequiredParams > 1) {
938
+ if (!this.options.allowMultipleParameters ||
939
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
940
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
941
+ }
942
+ }
943
+ else if (totalParams === 0) {
944
+ result.reason.push(ErrorType.NoParameter);
945
+ }
946
+ return result;
947
+ }
948
+ }
949
+ SMEValidator.SMERequiredParamsMaxNum = 5;
950
+
951
+ // Copyright (c) Microsoft Corporation.
952
+ class TeamsAIValidator extends Validator {
953
+ constructor(spec, options) {
954
+ super();
955
+ this.projectType = ProjectType.TeamsAi;
956
+ this.options = options;
957
+ this.spec = spec;
958
+ }
959
+ validateSpec() {
960
+ const result = { errors: [], warnings: [] };
961
+ // validate spec server
962
+ let validationResult = this.validateSpecServer();
963
+ result.errors.push(...validationResult.errors);
964
+ // validate no supported API
965
+ validationResult = this.validateSpecNoSupportAPI();
966
+ result.errors.push(...validationResult.errors);
967
+ return result;
968
+ }
969
+ validateAPI(method, path) {
970
+ const result = { isValid: true, reason: [] };
971
+ method = method.toLocaleLowerCase();
972
+ // validate method and path
973
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
974
+ if (!methodAndPathResult.isValid) {
975
+ return methodAndPathResult;
976
+ }
977
+ const operationObject = this.spec.paths[path][method];
978
+ // validate operationId
979
+ if (!this.options.allowMissingId && !operationObject.operationId) {
980
+ result.reason.push(ErrorType.MissingOperationId);
981
+ }
982
+ // validate server
983
+ const validateServerResult = this.validateServer(method, path);
984
+ result.reason.push(...validateServerResult.reason);
985
+ if (result.reason.length > 0) {
986
+ result.isValid = false;
987
+ }
988
+ return result;
989
+ }
990
+ }
991
+
992
+ class ValidatorFactory {
993
+ static create(spec, options) {
994
+ var _a;
995
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
996
+ switch (type) {
997
+ case ProjectType.SME:
998
+ return new SMEValidator(spec, options);
999
+ case ProjectType.Copilot:
1000
+ return new CopilotValidator(spec, options);
1001
+ case ProjectType.TeamsAi:
1002
+ return new TeamsAIValidator(spec, options);
1003
+ default:
1004
+ throw new Error(`Invalid project type: ${type}`);
1005
+ }
842
1006
  }
843
1007
  }
844
1008
 
@@ -861,6 +1025,8 @@ class SpecParser {
861
1025
  allowBearerTokenAuth: false,
862
1026
  allowOauth2: false,
863
1027
  allowMethods: ["get", "post"],
1028
+ allowConversationStarters: false,
1029
+ allowResponseSemantics: false,
864
1030
  projectType: ProjectType.SME,
865
1031
  };
866
1032
  this.pathOrSpec = pathOrDoc;
@@ -876,11 +1042,7 @@ class SpecParser {
876
1042
  try {
877
1043
  try {
878
1044
  await this.loadSpec();
879
- await this.parser.validate(this.spec, {
880
- validate: {
881
- schema: false,
882
- },
883
- });
1045
+ await this.parser.validate(this.spec);
884
1046
  }
885
1047
  catch (e) {
886
1048
  return {
@@ -889,16 +1051,46 @@ class SpecParser {
889
1051
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
890
1052
  };
891
1053
  }
1054
+ const errors = [];
1055
+ const warnings = [];
892
1056
  if (!this.options.allowSwagger && this.isSwaggerFile) {
893
1057
  return {
894
1058
  status: ValidationStatus.Error,
895
1059
  warnings: [],
896
1060
  errors: [
897
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1061
+ {
1062
+ type: ErrorType.SwaggerNotSupported,
1063
+ content: ConstantString.SwaggerNotSupported,
1064
+ },
898
1065
  ],
899
1066
  };
900
1067
  }
901
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1068
+ // Remote reference not supported
1069
+ const refPaths = this.parser.$refs.paths();
1070
+ // refPaths [0] is the current spec file path
1071
+ if (refPaths.length > 1) {
1072
+ errors.push({
1073
+ type: ErrorType.RemoteRefNotSupported,
1074
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1075
+ data: refPaths,
1076
+ });
1077
+ }
1078
+ const validator = this.getValidator(this.spec);
1079
+ const validationResult = validator.validateSpec();
1080
+ warnings.push(...validationResult.warnings);
1081
+ errors.push(...validationResult.errors);
1082
+ let status = ValidationStatus.Valid;
1083
+ if (warnings.length > 0 && errors.length === 0) {
1084
+ status = ValidationStatus.Warning;
1085
+ }
1086
+ else if (errors.length > 0) {
1087
+ status = ValidationStatus.Error;
1088
+ }
1089
+ return {
1090
+ status: status,
1091
+ warnings: warnings,
1092
+ errors: errors,
1093
+ };
902
1094
  }
903
1095
  catch (err) {
904
1096
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -907,17 +1099,20 @@ class SpecParser {
907
1099
  async listSupportedAPIInfo() {
908
1100
  try {
909
1101
  await this.loadSpec();
910
- const apiMap = this.getAllSupportedAPIs(this.spec);
1102
+ const apiMap = this.getAPIs(this.spec);
911
1103
  const apiInfos = [];
912
1104
  for (const key in apiMap) {
913
- const pathObjectItem = apiMap[key];
1105
+ const { operation, isValid } = apiMap[key];
1106
+ if (!isValid) {
1107
+ continue;
1108
+ }
914
1109
  const [method, path] = key.split(" ");
915
- const operationId = pathObjectItem.operationId;
1110
+ const operationId = operation.operationId;
916
1111
  // In Browser environment, this api is by default not support api without operationId
917
1112
  if (!operationId) {
918
1113
  continue;
919
1114
  }
920
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1115
+ const command = Utils.parseApiInfo(operation, this.options);
921
1116
  const apiInfo = {
922
1117
  method: method,
923
1118
  path: path,
@@ -926,9 +1121,6 @@ class SpecParser {
926
1121
  parameters: command.parameters,
927
1122
  description: command.description,
928
1123
  };
929
- if (warning) {
930
- apiInfo.warning = warning;
931
- }
932
1124
  apiInfos.push(apiInfo);
933
1125
  }
934
1126
  return apiInfos;
@@ -987,31 +1179,18 @@ class SpecParser {
987
1179
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
988
1180
  }
989
1181
  }
990
- getAllSupportedAPIs(spec) {
991
- if (this.apiMap !== undefined) {
992
- return this.apiMap;
993
- }
994
- const result = this.listSupportedAPIs(spec, this.options);
995
- this.apiMap = result;
996
- return result;
1182
+ getAPIs(spec) {
1183
+ const validator = this.getValidator(spec);
1184
+ const apiMap = validator.listAPIs();
1185
+ return apiMap;
997
1186
  }
998
- listSupportedAPIs(spec, options) {
999
- var _a;
1000
- const paths = spec.paths;
1001
- const result = {};
1002
- for (const path in paths) {
1003
- const methods = paths[path];
1004
- for (const method in methods) {
1005
- const operationObject = methods[method];
1006
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
1007
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
1008
- if (validateResult.isValid) {
1009
- result[`${method.toUpperCase()} ${path}`] = operationObject;
1010
- }
1011
- }
1012
- }
1187
+ getValidator(spec) {
1188
+ if (this.validator) {
1189
+ return this.validator;
1013
1190
  }
1014
- return result;
1191
+ const validator = ValidatorFactory.create(spec, this.options);
1192
+ this.validator = validator;
1193
+ return validator;
1015
1194
  }
1016
1195
  }
1017
1196