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