@microsoft/m365-spec-parser 0.1.1-alpha.7fe3da414.0 → 0.1.1-alpha.87e946b8f.0

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