@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.
@@ -103,6 +103,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
103
103
  ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
104
104
  ConstantString.AdaptiveCardType = "AdaptiveCard";
105
105
  ConstantString.TextBlockType = "TextBlock";
106
+ ConstantString.ImageType = "Image";
106
107
  ConstantString.ContainerType = "Container";
107
108
  ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
108
109
  ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
@@ -166,7 +167,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
166
167
  ConstantString.ParameterDescriptionMaxLens = 128;
167
168
  ConstantString.CommandTitleMaxLens = 32;
168
169
  ConstantString.ParameterTitleMaxLens = 32;
169
- ConstantString.SMERequiredParamsMaxNum = 5;
170
+ ConstantString.SMERequiredParamsMaxNum = 5;
171
+ ConstantString.DefaultPluginId = "plugin_1";
170
172
 
171
173
  // Copyright (c) Microsoft Corporation.
172
174
  class SpecParserError extends Error {
@@ -189,249 +191,9 @@ class Utils {
189
191
  }
190
192
  return false;
191
193
  }
192
- static checkParameters(paramObject, isCopilot) {
193
- const paramResult = {
194
- requiredNum: 0,
195
- optionalNum: 0,
196
- isValid: true,
197
- reason: [],
198
- };
199
- if (!paramObject) {
200
- return paramResult;
201
- }
202
- for (let i = 0; i < paramObject.length; i++) {
203
- const param = paramObject[i];
204
- const schema = param.schema;
205
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
206
- paramResult.isValid = false;
207
- paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
208
- continue;
209
- }
210
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
211
- if (isCopilot) {
212
- if (isRequiredWithoutDefault) {
213
- paramResult.requiredNum = paramResult.requiredNum + 1;
214
- }
215
- else {
216
- paramResult.optionalNum = paramResult.optionalNum + 1;
217
- }
218
- continue;
219
- }
220
- if (param.in === "header" || param.in === "cookie") {
221
- if (isRequiredWithoutDefault) {
222
- paramResult.isValid = false;
223
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
224
- }
225
- continue;
226
- }
227
- if (schema.type !== "boolean" &&
228
- schema.type !== "string" &&
229
- schema.type !== "number" &&
230
- schema.type !== "integer") {
231
- if (isRequiredWithoutDefault) {
232
- paramResult.isValid = false;
233
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
234
- }
235
- continue;
236
- }
237
- if (param.in === "query" || param.in === "path") {
238
- if (isRequiredWithoutDefault) {
239
- paramResult.requiredNum = paramResult.requiredNum + 1;
240
- }
241
- else {
242
- paramResult.optionalNum = paramResult.optionalNum + 1;
243
- }
244
- }
245
- }
246
- return paramResult;
247
- }
248
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
249
- var _a;
250
- const paramResult = {
251
- requiredNum: 0,
252
- optionalNum: 0,
253
- isValid: true,
254
- reason: [],
255
- };
256
- if (Object.keys(schema).length === 0) {
257
- return paramResult;
258
- }
259
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
260
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
261
- paramResult.isValid = false;
262
- paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
263
- return paramResult;
264
- }
265
- if (schema.type === "string" ||
266
- schema.type === "integer" ||
267
- schema.type === "boolean" ||
268
- schema.type === "number") {
269
- if (isRequiredWithoutDefault) {
270
- paramResult.requiredNum = paramResult.requiredNum + 1;
271
- }
272
- else {
273
- paramResult.optionalNum = paramResult.optionalNum + 1;
274
- }
275
- }
276
- else if (schema.type === "object") {
277
- const { properties } = schema;
278
- for (const property in properties) {
279
- let isRequired = false;
280
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
281
- isRequired = true;
282
- }
283
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
284
- paramResult.requiredNum += result.requiredNum;
285
- paramResult.optionalNum += result.optionalNum;
286
- paramResult.isValid = paramResult.isValid && result.isValid;
287
- paramResult.reason.push(...result.reason);
288
- }
289
- }
290
- else {
291
- if (isRequiredWithoutDefault && !isCopilot) {
292
- paramResult.isValid = false;
293
- paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
294
- }
295
- }
296
- return paramResult;
297
- }
298
194
  static containMultipleMediaTypes(bodyObject) {
299
195
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
300
196
  }
301
- /**
302
- * Checks if the given API is supported.
303
- * @param {string} method - The HTTP method of the API.
304
- * @param {string} path - The path of the API.
305
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
306
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
307
- * @description The following APIs are supported:
308
- * 1. only support Get/Post operation without auth property
309
- * 2. parameter inside query or path only support string, number, boolean and integer
310
- * 3. parameter inside post body only support string, number, boolean, integer and object
311
- * 4. request body + required parameters <= 1
312
- * 5. response body should be “application/json” and not empty, and response code should be 20X
313
- * 6. only support request body with “application/json” content type
314
- */
315
- static isSupportedApi(method, path, spec, options) {
316
- var _a;
317
- const result = { isValid: true, reason: [] };
318
- method = method.toLocaleLowerCase();
319
- if (options.allowMethods && !options.allowMethods.includes(method)) {
320
- result.isValid = false;
321
- result.reason.push(ErrorType.MethodNotAllowed);
322
- return result;
323
- }
324
- const pathObj = spec.paths[path];
325
- if (!pathObj || !pathObj[method]) {
326
- result.isValid = false;
327
- result.reason.push(ErrorType.UrlPathNotExist);
328
- return result;
329
- }
330
- const securities = pathObj[method].security;
331
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
332
- const isCopilot = options.projectType === ProjectType.Copilot;
333
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
334
- if (!isTeamsAi) {
335
- const authArray = Utils.getAuthArray(securities, spec);
336
- const authCheckResult = Utils.isSupportedAuth(authArray, options);
337
- if (!authCheckResult.isValid) {
338
- result.reason.push(...authCheckResult.reason);
339
- }
340
- }
341
- const operationObject = pathObj[method];
342
- if (!options.allowMissingId && !operationObject.operationId) {
343
- result.reason.push(ErrorType.MissingOperationId);
344
- }
345
- const rootServer = spec.servers && spec.servers[0];
346
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
347
- const operationServer = operationObject.servers && operationObject.servers[0];
348
- const serverUrl = operationServer || methodServer || rootServer;
349
- if (!serverUrl) {
350
- result.reason.push(ErrorType.NoServerInformation);
351
- }
352
- else {
353
- const serverValidateResult = Utils.checkServerUrl([serverUrl]);
354
- result.reason.push(...serverValidateResult.map((item) => item.type));
355
- }
356
- const paramObject = operationObject.parameters;
357
- const requestBody = operationObject.requestBody;
358
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
359
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
360
- result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
361
- }
362
- const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
363
- if (multipleMediaType && !isTeamsAi) {
364
- result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
365
- }
366
- else if (Object.keys(json).length === 0) {
367
- result.reason.push(ErrorType.ResponseJsonIsEmpty);
368
- }
369
- // Teams AI project doesn't care about request parameters/body
370
- if (!isTeamsAi) {
371
- let requestBodyParamResult = {
372
- requiredNum: 0,
373
- optionalNum: 0,
374
- isValid: true,
375
- reason: [],
376
- };
377
- if (requestJsonBody) {
378
- const requestBodySchema = requestJsonBody.schema;
379
- if (isCopilot && requestBodySchema.type !== "object") {
380
- result.reason.push(ErrorType.PostBodySchemaIsNotJson);
381
- }
382
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
383
- if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
384
- result.reason.push(...requestBodyParamResult.reason);
385
- }
386
- }
387
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
388
- if (!paramResult.isValid && paramResult.reason) {
389
- result.reason.push(...paramResult.reason);
390
- }
391
- // Copilot support arbitrary parameters
392
- if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
393
- const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
394
- const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
395
- if (totalRequiredParams > 1) {
396
- if (!options.allowMultipleParameters ||
397
- totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
398
- result.reason.push(ErrorType.ExceededRequiredParamsLimit);
399
- }
400
- }
401
- else if (totalParams === 0) {
402
- result.reason.push(ErrorType.NoParameter);
403
- }
404
- }
405
- }
406
- if (result.reason.length > 0) {
407
- result.isValid = false;
408
- }
409
- return result;
410
- }
411
- static isSupportedAuth(authSchemeArray, options) {
412
- if (authSchemeArray.length === 0) {
413
- return { isValid: true, reason: [] };
414
- }
415
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
416
- // Currently we don't support multiple auth in one operation
417
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
418
- return {
419
- isValid: false,
420
- reason: [ErrorType.MultipleAuthNotSupported],
421
- };
422
- }
423
- for (const auths of authSchemeArray) {
424
- if (auths.length === 1) {
425
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
426
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
427
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
428
- return { isValid: true, reason: [] };
429
- }
430
- }
431
- }
432
- }
433
- return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
434
- }
435
197
  static isBearerTokenAuth(authScheme) {
436
198
  return authScheme.type === "http" && authScheme.scheme === "bearer";
437
199
  }
@@ -439,10 +201,9 @@ class Utils {
439
201
  return authScheme.type === "apiKey";
440
202
  }
441
203
  static isOAuthWithAuthCodeFlow(authScheme) {
442
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
443
- return true;
444
- }
445
- return false;
204
+ return !!(authScheme.type === "oauth2" &&
205
+ authScheme.flows &&
206
+ authScheme.flows.authorizationCode);
446
207
  }
447
208
  static getAuthArray(securities, spec) {
448
209
  var _a;
@@ -470,7 +231,7 @@ class Utils {
470
231
  static updateFirstLetter(str) {
471
232
  return str.charAt(0).toUpperCase() + str.slice(1);
472
233
  }
473
- static getResponseJson(operationObject, isTeamsAiProject = false) {
234
+ static getResponseJson(operationObject) {
474
235
  var _a, _b;
475
236
  let json = {};
476
237
  let multipleMediaType = false;
@@ -481,9 +242,6 @@ class Utils {
481
242
  json = responseObject.content["application/json"];
482
243
  if (Utils.containMultipleMediaTypes(responseObject)) {
483
244
  multipleMediaType = true;
484
- if (isTeamsAiProject) {
485
- break;
486
- }
487
245
  json = {};
488
246
  }
489
247
  else {
@@ -711,13 +469,7 @@ class Utils {
711
469
  }
712
470
  }
713
471
  const operationId = operationItem.operationId;
714
- const parameters = [];
715
- if (requiredParams.length !== 0) {
716
- parameters.push(...requiredParams);
717
- }
718
- else {
719
- parameters.push(optionalParams[0]);
720
- }
472
+ const parameters = [...requiredParams, ...optionalParams];
721
473
  const command = {
722
474
  context: ["compose"],
723
475
  type: "query",
@@ -726,26 +478,51 @@ class Utils {
726
478
  parameters: parameters,
727
479
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
728
480
  };
729
- let warning = undefined;
730
- if (requiredParams.length === 0 && optionalParams.length > 1) {
731
- warning = {
732
- type: WarningType.OperationOnlyContainsOptionalParam,
733
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
734
- data: operationId,
735
- };
481
+ return command;
482
+ }
483
+ static format(str, ...args) {
484
+ let index = 0;
485
+ return str.replace(/%s/g, () => {
486
+ const arg = args[index++];
487
+ return arg !== undefined ? arg : "";
488
+ });
489
+ }
490
+ static getSafeRegistrationIdEnvName(authName) {
491
+ if (!authName) {
492
+ return "";
736
493
  }
737
- return [command, warning];
494
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
495
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
496
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
497
+ }
498
+ return safeRegistrationIdEnvName;
499
+ }
500
+ static getServerObject(spec, method, path) {
501
+ const pathObj = spec.paths[path];
502
+ const operationObject = pathObj[method];
503
+ const rootServer = spec.servers && spec.servers[0];
504
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
505
+ const operationServer = operationObject.servers && operationObject.servers[0];
506
+ const serverUrl = operationServer || methodServer || rootServer;
507
+ return serverUrl;
738
508
  }
739
- static listAPIs(spec, options) {
509
+ }
510
+
511
+ // Copyright (c) Microsoft Corporation.
512
+ class Validator {
513
+ listAPIs() {
740
514
  var _a;
741
- const paths = spec.paths;
515
+ if (this.apiMap) {
516
+ return this.apiMap;
517
+ }
518
+ const paths = this.spec.paths;
742
519
  const result = {};
743
520
  for (const path in paths) {
744
521
  const methods = paths[path];
745
522
  for (const method in methods) {
746
523
  const operationObject = methods[method];
747
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
748
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
524
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
525
+ const validateResult = this.validateAPI(method, path);
749
526
  result[`${method.toUpperCase()} ${path}`] = {
750
527
  operation: operationObject,
751
528
  isValid: validateResult.isValid,
@@ -754,38 +531,48 @@ class Utils {
754
531
  }
755
532
  }
756
533
  }
534
+ this.apiMap = result;
757
535
  return result;
758
536
  }
759
- static validateSpec(spec, parser, isSwaggerFile, options) {
760
- const errors = [];
761
- const warnings = [];
762
- const apiMap = Utils.listAPIs(spec, options);
763
- if (isSwaggerFile) {
764
- warnings.push({
765
- type: WarningType.ConvertSwaggerToOpenAPI,
766
- content: ConstantString.ConvertSwaggerToOpenAPI,
767
- });
768
- }
769
- const serverErrors = Utils.validateServer(spec, options);
770
- errors.push(...serverErrors);
771
- // Remote reference not supported
772
- const refPaths = parser.$refs.paths();
773
- // refPaths [0] is the current spec file path
774
- if (refPaths.length > 1) {
775
- errors.push({
776
- type: ErrorType.RemoteRefNotSupported,
777
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
778
- data: refPaths,
537
+ validateSpecVersion() {
538
+ const result = { errors: [], warnings: [] };
539
+ if (this.spec.openapi >= "3.1.0") {
540
+ result.errors.push({
541
+ type: ErrorType.SpecVersionNotSupported,
542
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
543
+ data: this.spec.openapi,
779
544
  });
780
545
  }
781
- // No supported API
546
+ return result;
547
+ }
548
+ validateSpecServer() {
549
+ const result = { errors: [], warnings: [] };
550
+ const serverErrors = Utils.validateServer(this.spec, this.options);
551
+ result.errors.push(...serverErrors);
552
+ return result;
553
+ }
554
+ validateSpecNoSupportAPI() {
555
+ const result = { errors: [], warnings: [] };
556
+ const apiMap = this.listAPIs();
782
557
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
783
558
  if (validAPIs.length === 0) {
784
- errors.push({
559
+ const data = [];
560
+ for (const key in apiMap) {
561
+ const { reason } = apiMap[key];
562
+ const apiInvalidReason = { api: key, reason: reason };
563
+ data.push(apiInvalidReason);
564
+ }
565
+ result.errors.push({
785
566
  type: ErrorType.NoSupportedApi,
786
567
  content: ConstantString.NoSupportedApi,
568
+ data,
787
569
  });
788
570
  }
571
+ return result;
572
+ }
573
+ validateSpecOperationId() {
574
+ const result = { errors: [], warnings: [] };
575
+ const apiMap = this.listAPIs();
789
576
  // OperationId missing
790
577
  const apisMissingOperationId = [];
791
578
  for (const key in apiMap) {
@@ -795,54 +582,431 @@ class Utils {
795
582
  }
796
583
  }
797
584
  if (apisMissingOperationId.length > 0) {
798
- warnings.push({
585
+ result.warnings.push({
799
586
  type: WarningType.OperationIdMissing,
800
587
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
801
588
  data: apisMissingOperationId,
802
589
  });
803
590
  }
804
- let status = ValidationStatus.Valid;
805
- if (warnings.length > 0 && errors.length === 0) {
806
- status = ValidationStatus.Warning;
591
+ return result;
592
+ }
593
+ validateMethodAndPath(method, path) {
594
+ const result = { isValid: true, reason: [] };
595
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
596
+ result.isValid = false;
597
+ result.reason.push(ErrorType.MethodNotAllowed);
598
+ return result;
807
599
  }
808
- else if (errors.length > 0) {
809
- status = ValidationStatus.Error;
600
+ const pathObj = this.spec.paths[path];
601
+ if (!pathObj || !pathObj[method]) {
602
+ result.isValid = false;
603
+ result.reason.push(ErrorType.UrlPathNotExist);
604
+ return result;
810
605
  }
811
- return {
812
- status,
813
- warnings,
814
- errors,
815
- };
816
- }
817
- static format(str, ...args) {
818
- let index = 0;
819
- return str.replace(/%s/g, () => {
820
- const arg = args[index++];
821
- return arg !== undefined ? arg : "";
822
- });
606
+ return result;
823
607
  }
824
- static getSafeRegistrationIdEnvName(authName) {
825
- if (!authName) {
826
- return "";
608
+ validateResponse(method, path) {
609
+ const result = { isValid: true, reason: [] };
610
+ const operationObject = this.spec.paths[path][method];
611
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
612
+ // only support response body only contains “application/json” content type
613
+ if (multipleMediaType) {
614
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
827
615
  }
828
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
829
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
830
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
616
+ else if (Object.keys(json).length === 0) {
617
+ // response body should not be empty
618
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
831
619
  }
832
- return safeRegistrationIdEnvName;
620
+ return result;
833
621
  }
834
- static getAllAPICount(spec) {
835
- let count = 0;
836
- const paths = spec.paths;
837
- for (const path in paths) {
838
- const methods = paths[path];
839
- for (const method in methods) {
840
- if (ConstantString.AllOperationMethods.includes(method)) {
841
- count++;
622
+ validateServer(method, path) {
623
+ const result = { isValid: true, reason: [] };
624
+ const serverObj = Utils.getServerObject(this.spec, method, path);
625
+ if (!serverObj) {
626
+ // should contain server URL
627
+ result.reason.push(ErrorType.NoServerInformation);
628
+ }
629
+ else {
630
+ // server url should be absolute url with https protocol
631
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
632
+ result.reason.push(...serverValidateResult.map((item) => item.type));
633
+ }
634
+ return result;
635
+ }
636
+ validateAuth(method, path) {
637
+ const pathObj = this.spec.paths[path];
638
+ const operationObject = pathObj[method];
639
+ const securities = operationObject.security;
640
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
641
+ if (authSchemeArray.length === 0) {
642
+ return { isValid: true, reason: [] };
643
+ }
644
+ if (this.options.allowAPIKeyAuth ||
645
+ this.options.allowOauth2 ||
646
+ this.options.allowBearerTokenAuth) {
647
+ // Currently we don't support multiple auth in one operation
648
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
649
+ return {
650
+ isValid: false,
651
+ reason: [ErrorType.MultipleAuthNotSupported],
652
+ };
653
+ }
654
+ for (const auths of authSchemeArray) {
655
+ if (auths.length === 1) {
656
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
657
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
658
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
659
+ return { isValid: true, reason: [] };
660
+ }
661
+ }
662
+ }
663
+ }
664
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
665
+ }
666
+ checkPostBodySchema(schema, isRequired = false) {
667
+ var _a;
668
+ const paramResult = {
669
+ requiredNum: 0,
670
+ optionalNum: 0,
671
+ isValid: true,
672
+ reason: [],
673
+ };
674
+ if (Object.keys(schema).length === 0) {
675
+ return paramResult;
676
+ }
677
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
678
+ const isCopilot = this.projectType === ProjectType.Copilot;
679
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
680
+ paramResult.isValid = false;
681
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
682
+ return paramResult;
683
+ }
684
+ if (schema.type === "string" ||
685
+ schema.type === "integer" ||
686
+ schema.type === "boolean" ||
687
+ schema.type === "number") {
688
+ if (isRequiredWithoutDefault) {
689
+ paramResult.requiredNum = paramResult.requiredNum + 1;
690
+ }
691
+ else {
692
+ paramResult.optionalNum = paramResult.optionalNum + 1;
693
+ }
694
+ }
695
+ else if (schema.type === "object") {
696
+ const { properties } = schema;
697
+ for (const property in properties) {
698
+ let isRequired = false;
699
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
700
+ isRequired = true;
842
701
  }
702
+ const result = this.checkPostBodySchema(properties[property], isRequired);
703
+ paramResult.requiredNum += result.requiredNum;
704
+ paramResult.optionalNum += result.optionalNum;
705
+ paramResult.isValid = paramResult.isValid && result.isValid;
706
+ paramResult.reason.push(...result.reason);
843
707
  }
844
708
  }
845
- return count;
709
+ else {
710
+ if (isRequiredWithoutDefault && !isCopilot) {
711
+ paramResult.isValid = false;
712
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
713
+ }
714
+ }
715
+ return paramResult;
716
+ }
717
+ checkParamSchema(paramObject) {
718
+ const paramResult = {
719
+ requiredNum: 0,
720
+ optionalNum: 0,
721
+ isValid: true,
722
+ reason: [],
723
+ };
724
+ if (!paramObject) {
725
+ return paramResult;
726
+ }
727
+ const isCopilot = this.projectType === ProjectType.Copilot;
728
+ for (let i = 0; i < paramObject.length; i++) {
729
+ const param = paramObject[i];
730
+ const schema = param.schema;
731
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
732
+ paramResult.isValid = false;
733
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
734
+ continue;
735
+ }
736
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
737
+ if (isCopilot) {
738
+ if (isRequiredWithoutDefault) {
739
+ paramResult.requiredNum = paramResult.requiredNum + 1;
740
+ }
741
+ else {
742
+ paramResult.optionalNum = paramResult.optionalNum + 1;
743
+ }
744
+ continue;
745
+ }
746
+ if (param.in === "header" || param.in === "cookie") {
747
+ if (isRequiredWithoutDefault) {
748
+ paramResult.isValid = false;
749
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
750
+ }
751
+ continue;
752
+ }
753
+ if (schema.type !== "boolean" &&
754
+ schema.type !== "string" &&
755
+ schema.type !== "number" &&
756
+ schema.type !== "integer") {
757
+ if (isRequiredWithoutDefault) {
758
+ paramResult.isValid = false;
759
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
760
+ }
761
+ continue;
762
+ }
763
+ if (param.in === "query" || param.in === "path") {
764
+ if (isRequiredWithoutDefault) {
765
+ paramResult.requiredNum = paramResult.requiredNum + 1;
766
+ }
767
+ else {
768
+ paramResult.optionalNum = paramResult.optionalNum + 1;
769
+ }
770
+ }
771
+ }
772
+ return paramResult;
773
+ }
774
+ hasNestedObjectInSchema(schema) {
775
+ if (schema.type === "object") {
776
+ for (const property in schema.properties) {
777
+ const nestedSchema = schema.properties[property];
778
+ if (nestedSchema.type === "object") {
779
+ return true;
780
+ }
781
+ }
782
+ }
783
+ return false;
784
+ }
785
+ }
786
+
787
+ // Copyright (c) Microsoft Corporation.
788
+ class CopilotValidator extends Validator {
789
+ constructor(spec, options) {
790
+ super();
791
+ this.projectType = ProjectType.Copilot;
792
+ this.options = options;
793
+ this.spec = spec;
794
+ }
795
+ validateSpec() {
796
+ const result = { errors: [], warnings: [] };
797
+ // validate spec version
798
+ let validationResult = this.validateSpecVersion();
799
+ result.errors.push(...validationResult.errors);
800
+ // validate spec server
801
+ validationResult = this.validateSpecServer();
802
+ result.errors.push(...validationResult.errors);
803
+ // validate no supported API
804
+ validationResult = this.validateSpecNoSupportAPI();
805
+ result.errors.push(...validationResult.errors);
806
+ // validate operationId missing
807
+ validationResult = this.validateSpecOperationId();
808
+ result.warnings.push(...validationResult.warnings);
809
+ return result;
810
+ }
811
+ validateAPI(method, path) {
812
+ const result = { isValid: true, reason: [] };
813
+ method = method.toLocaleLowerCase();
814
+ // validate method and path
815
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
816
+ if (!methodAndPathResult.isValid) {
817
+ return methodAndPathResult;
818
+ }
819
+ const operationObject = this.spec.paths[path][method];
820
+ // validate auth
821
+ const authCheckResult = this.validateAuth(method, path);
822
+ result.reason.push(...authCheckResult.reason);
823
+ // validate operationId
824
+ if (!this.options.allowMissingId && !operationObject.operationId) {
825
+ result.reason.push(ErrorType.MissingOperationId);
826
+ }
827
+ // validate server
828
+ const validateServerResult = this.validateServer(method, path);
829
+ result.reason.push(...validateServerResult.reason);
830
+ // validate response
831
+ const validateResponseResult = this.validateResponse(method, path);
832
+ result.reason.push(...validateResponseResult.reason);
833
+ // validate requestBody
834
+ const requestBody = operationObject.requestBody;
835
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
836
+ if (Utils.containMultipleMediaTypes(requestBody)) {
837
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
838
+ }
839
+ if (requestJsonBody) {
840
+ const requestBodySchema = requestJsonBody.schema;
841
+ if (requestBodySchema.type !== "object") {
842
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
843
+ }
844
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
845
+ result.reason.push(...requestBodyParamResult.reason);
846
+ }
847
+ // validate parameters
848
+ const paramObject = operationObject.parameters;
849
+ const paramResult = this.checkParamSchema(paramObject);
850
+ result.reason.push(...paramResult.reason);
851
+ if (result.reason.length > 0) {
852
+ result.isValid = false;
853
+ }
854
+ return result;
855
+ }
856
+ }
857
+
858
+ // Copyright (c) Microsoft Corporation.
859
+ class SMEValidator extends Validator {
860
+ constructor(spec, options) {
861
+ super();
862
+ this.projectType = ProjectType.SME;
863
+ this.options = options;
864
+ this.spec = spec;
865
+ }
866
+ validateSpec() {
867
+ const result = { errors: [], warnings: [] };
868
+ // validate spec version
869
+ let validationResult = this.validateSpecVersion();
870
+ result.errors.push(...validationResult.errors);
871
+ // validate spec server
872
+ validationResult = this.validateSpecServer();
873
+ result.errors.push(...validationResult.errors);
874
+ // validate no supported API
875
+ validationResult = this.validateSpecNoSupportAPI();
876
+ result.errors.push(...validationResult.errors);
877
+ // validate operationId missing
878
+ if (this.options.allowMissingId) {
879
+ validationResult = this.validateSpecOperationId();
880
+ result.warnings.push(...validationResult.warnings);
881
+ }
882
+ return result;
883
+ }
884
+ validateAPI(method, path) {
885
+ const result = { isValid: true, reason: [] };
886
+ method = method.toLocaleLowerCase();
887
+ // validate method and path
888
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
889
+ if (!methodAndPathResult.isValid) {
890
+ return methodAndPathResult;
891
+ }
892
+ const operationObject = this.spec.paths[path][method];
893
+ // validate auth
894
+ const authCheckResult = this.validateAuth(method, path);
895
+ result.reason.push(...authCheckResult.reason);
896
+ // validate operationId
897
+ if (!this.options.allowMissingId && !operationObject.operationId) {
898
+ result.reason.push(ErrorType.MissingOperationId);
899
+ }
900
+ // validate server
901
+ const validateServerResult = this.validateServer(method, path);
902
+ result.reason.push(...validateServerResult.reason);
903
+ // validate response
904
+ const validateResponseResult = this.validateResponse(method, path);
905
+ result.reason.push(...validateResponseResult.reason);
906
+ let postBodyResult = {
907
+ requiredNum: 0,
908
+ optionalNum: 0,
909
+ isValid: true,
910
+ reason: [],
911
+ };
912
+ // validate requestBody
913
+ const requestBody = operationObject.requestBody;
914
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
915
+ if (Utils.containMultipleMediaTypes(requestBody)) {
916
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
917
+ }
918
+ if (requestJsonBody) {
919
+ const requestBodySchema = requestJsonBody.schema;
920
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
921
+ result.reason.push(...postBodyResult.reason);
922
+ }
923
+ // validate parameters
924
+ const paramObject = operationObject.parameters;
925
+ const paramResult = this.checkParamSchema(paramObject);
926
+ result.reason.push(...paramResult.reason);
927
+ // validate total parameters count
928
+ if (paramResult.isValid && postBodyResult.isValid) {
929
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
930
+ result.reason.push(...paramCountResult.reason);
931
+ }
932
+ if (result.reason.length > 0) {
933
+ result.isValid = false;
934
+ }
935
+ return result;
936
+ }
937
+ validateParamCount(postBodyResult, paramResult) {
938
+ const result = { isValid: true, reason: [] };
939
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
940
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
941
+ if (totalRequiredParams > 1) {
942
+ if (!this.options.allowMultipleParameters ||
943
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
944
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
945
+ }
946
+ }
947
+ else if (totalParams === 0) {
948
+ result.reason.push(ErrorType.NoParameter);
949
+ }
950
+ return result;
951
+ }
952
+ }
953
+ SMEValidator.SMERequiredParamsMaxNum = 5;
954
+
955
+ // Copyright (c) Microsoft Corporation.
956
+ class TeamsAIValidator extends Validator {
957
+ constructor(spec, options) {
958
+ super();
959
+ this.projectType = ProjectType.TeamsAi;
960
+ this.options = options;
961
+ this.spec = spec;
962
+ }
963
+ validateSpec() {
964
+ const result = { errors: [], warnings: [] };
965
+ // validate spec server
966
+ let validationResult = this.validateSpecServer();
967
+ result.errors.push(...validationResult.errors);
968
+ // validate no supported API
969
+ validationResult = this.validateSpecNoSupportAPI();
970
+ result.errors.push(...validationResult.errors);
971
+ return result;
972
+ }
973
+ validateAPI(method, path) {
974
+ const result = { isValid: true, reason: [] };
975
+ method = method.toLocaleLowerCase();
976
+ // validate method and path
977
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
978
+ if (!methodAndPathResult.isValid) {
979
+ return methodAndPathResult;
980
+ }
981
+ const operationObject = this.spec.paths[path][method];
982
+ // validate operationId
983
+ if (!this.options.allowMissingId && !operationObject.operationId) {
984
+ result.reason.push(ErrorType.MissingOperationId);
985
+ }
986
+ // validate server
987
+ const validateServerResult = this.validateServer(method, path);
988
+ result.reason.push(...validateServerResult.reason);
989
+ if (result.reason.length > 0) {
990
+ result.isValid = false;
991
+ }
992
+ return result;
993
+ }
994
+ }
995
+
996
+ class ValidatorFactory {
997
+ static create(spec, options) {
998
+ var _a;
999
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1000
+ switch (type) {
1001
+ case ProjectType.SME:
1002
+ return new SMEValidator(spec, options);
1003
+ case ProjectType.Copilot:
1004
+ return new CopilotValidator(spec, options);
1005
+ case ProjectType.TeamsAi:
1006
+ return new TeamsAIValidator(spec, options);
1007
+ default:
1008
+ throw new Error(`Invalid project type: ${type}`);
1009
+ }
846
1010
  }
847
1011
  }
848
1012
 
@@ -860,7 +1024,8 @@ class SpecFilter {
860
1024
  if (ConstantString.AllOperationMethods.includes(methodName) &&
861
1025
  pathObj &&
862
1026
  pathObj[methodName]) {
863
- const validateResult = Utils.isSupportedApi(methodName, path, resolvedSpec, options);
1027
+ const validator = ValidatorFactory.create(resolvedSpec, options);
1028
+ const validateResult = validator.validateAPI(methodName, path);
864
1029
  if (!validateResult.isValid) {
865
1030
  continue;
866
1031
  }
@@ -886,6 +1051,293 @@ class SpecFilter {
886
1051
  }
887
1052
  }
888
1053
 
1054
+ // Copyright (c) Microsoft Corporation.
1055
+ class AdaptiveCardGenerator {
1056
+ static generateAdaptiveCard(operationItem) {
1057
+ try {
1058
+ const { json } = Utils.getResponseJson(operationItem);
1059
+ let cardBody = [];
1060
+ let schema = json.schema;
1061
+ let jsonPath = "$";
1062
+ if (schema && Object.keys(schema).length > 0) {
1063
+ jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
1064
+ if (jsonPath !== "$") {
1065
+ schema = schema.properties[jsonPath];
1066
+ }
1067
+ cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
1068
+ }
1069
+ // if no schema, try to use example value
1070
+ if (cardBody.length === 0 && (json.examples || json.example)) {
1071
+ cardBody = [
1072
+ {
1073
+ type: ConstantString.TextBlockType,
1074
+ text: "${jsonStringify($root)}",
1075
+ wrap: true,
1076
+ },
1077
+ ];
1078
+ }
1079
+ // if no example value, use default success response
1080
+ if (cardBody.length === 0) {
1081
+ cardBody = [
1082
+ {
1083
+ type: ConstantString.TextBlockType,
1084
+ text: "success",
1085
+ wrap: true,
1086
+ },
1087
+ ];
1088
+ }
1089
+ const fullCard = {
1090
+ type: ConstantString.AdaptiveCardType,
1091
+ $schema: ConstantString.AdaptiveCardSchema,
1092
+ version: ConstantString.AdaptiveCardVersion,
1093
+ body: cardBody,
1094
+ };
1095
+ return [fullCard, jsonPath];
1096
+ }
1097
+ catch (err) {
1098
+ throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
1099
+ }
1100
+ }
1101
+ static generateCardFromResponse(schema, name, parentArrayName = "") {
1102
+ if (schema.type === "array") {
1103
+ // schema.items can be arbitrary object: schema { type: array, items: {} }
1104
+ if (Object.keys(schema.items).length === 0) {
1105
+ return [
1106
+ {
1107
+ type: ConstantString.TextBlockType,
1108
+ text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
1109
+ wrap: true,
1110
+ },
1111
+ ];
1112
+ }
1113
+ const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
1114
+ const template = {
1115
+ type: ConstantString.ContainerType,
1116
+ $data: name ? `\${${name}}` : "${$root}",
1117
+ items: Array(),
1118
+ };
1119
+ template.items.push(...obj);
1120
+ return [template];
1121
+ }
1122
+ // some schema may not contain type but contain properties
1123
+ if (schema.type === "object" || (!schema.type && schema.properties)) {
1124
+ const { properties } = schema;
1125
+ const result = [];
1126
+ for (const property in properties) {
1127
+ const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
1128
+ result.push(...obj);
1129
+ }
1130
+ if (schema.additionalProperties) {
1131
+ // TODO: better ways to handler warnings.
1132
+ console.warn(ConstantString.AdditionalPropertiesNotSupported);
1133
+ }
1134
+ return result;
1135
+ }
1136
+ if (schema.type === "string" ||
1137
+ schema.type === "integer" ||
1138
+ schema.type === "boolean" ||
1139
+ schema.type === "number") {
1140
+ if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
1141
+ // string in root: "ddd"
1142
+ let text = "result: ${$root}";
1143
+ if (name) {
1144
+ // object { id: "1" }
1145
+ text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
1146
+ if (parentArrayName) {
1147
+ // object types inside array: { tags: ["id": 1, "name": "name"] }
1148
+ text = `${parentArrayName}.${text}`;
1149
+ }
1150
+ }
1151
+ else if (parentArrayName) {
1152
+ // string array: photoUrls: ["1", "2"]
1153
+ text = `${parentArrayName}: ` + "${$data}";
1154
+ }
1155
+ return [
1156
+ {
1157
+ type: ConstantString.TextBlockType,
1158
+ text,
1159
+ wrap: true,
1160
+ },
1161
+ ];
1162
+ }
1163
+ else {
1164
+ if (name) {
1165
+ return [
1166
+ {
1167
+ type: "Image",
1168
+ url: `\${${name}}`,
1169
+ $when: `\${${name} != null}`,
1170
+ },
1171
+ ];
1172
+ }
1173
+ else {
1174
+ return [
1175
+ {
1176
+ type: "Image",
1177
+ url: "${$data}",
1178
+ $when: "${$data != null}",
1179
+ },
1180
+ ];
1181
+ }
1182
+ }
1183
+ }
1184
+ if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
1185
+ throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
1186
+ }
1187
+ throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
1188
+ }
1189
+ // Find the first array property in the response schema object with the well-known name
1190
+ static getResponseJsonPathFromSchema(schema) {
1191
+ if (schema.type === "object" || (!schema.type && schema.properties)) {
1192
+ const { properties } = schema;
1193
+ for (const property in properties) {
1194
+ const schema = properties[property];
1195
+ if (schema.type === "array" &&
1196
+ Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
1197
+ return property;
1198
+ }
1199
+ }
1200
+ }
1201
+ return "$";
1202
+ }
1203
+ static isImageUrlProperty(schema, name, parentArrayName) {
1204
+ const propertyName = name ? name : parentArrayName;
1205
+ return (!!propertyName &&
1206
+ schema.type === "string" &&
1207
+ Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
1208
+ (propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
1209
+ }
1210
+ }
1211
+
1212
+ // Copyright (c) Microsoft Corporation.
1213
+ function wrapAdaptiveCard(card, jsonPath) {
1214
+ const result = {
1215
+ version: ConstantString.WrappedCardVersion,
1216
+ $schema: ConstantString.WrappedCardSchema,
1217
+ jsonPath: jsonPath,
1218
+ responseLayout: ConstantString.WrappedCardResponseLayout,
1219
+ responseCardTemplate: card,
1220
+ previewCardTemplate: inferPreviewCardTemplate(card),
1221
+ };
1222
+ return result;
1223
+ }
1224
+ function wrapResponseSemantics(card, jsonPath) {
1225
+ const props = inferProperties(card);
1226
+ const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
1227
+ const result = {
1228
+ data_path: dataPath,
1229
+ };
1230
+ if (props.title || props.subtitle || props.imageUrl) {
1231
+ result.properties = {};
1232
+ if (props.title) {
1233
+ result.properties.title = "$." + props.title;
1234
+ }
1235
+ if (props.subtitle) {
1236
+ result.properties.subtitle = "$." + props.subtitle;
1237
+ }
1238
+ if (props.imageUrl) {
1239
+ result.properties.url = "$." + props.imageUrl;
1240
+ }
1241
+ }
1242
+ result.static_template = card;
1243
+ return result;
1244
+ }
1245
+ /**
1246
+ * Infers the preview card template from an Adaptive Card and a JSON path.
1247
+ * The preview card template includes a title and an optional subtitle and image.
1248
+ * It populates the preview card template with the first text block that matches
1249
+ * each well-known name, in the order of title, subtitle, and image.
1250
+ * If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
1251
+ * If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
1252
+ * @param card The Adaptive Card to infer the preview card template from.
1253
+ * @param jsonPath The JSON path to the root object in the card body.
1254
+ * @returns The inferred preview card template.
1255
+ */
1256
+ function inferPreviewCardTemplate(card) {
1257
+ const result = {
1258
+ title: "result",
1259
+ };
1260
+ const inferredProperties = inferProperties(card);
1261
+ if (inferredProperties.title) {
1262
+ result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
1263
+ }
1264
+ if (inferredProperties.subtitle) {
1265
+ result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
1266
+ }
1267
+ if (inferredProperties.imageUrl) {
1268
+ result.image = {
1269
+ url: `\${${inferredProperties.imageUrl}}`,
1270
+ alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
1271
+ $when: `\${${inferredProperties.imageUrl} != null}`,
1272
+ };
1273
+ }
1274
+ return result;
1275
+ }
1276
+ function inferProperties(card) {
1277
+ var _a;
1278
+ const result = {};
1279
+ const nameSet = new Set();
1280
+ let rootObject;
1281
+ if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
1282
+ rootObject = card.body[0].items;
1283
+ }
1284
+ else {
1285
+ rootObject = card.body;
1286
+ }
1287
+ for (const element of rootObject) {
1288
+ if (element.type === ConstantString.TextBlockType) {
1289
+ const textElement = element;
1290
+ const index = textElement.text.indexOf("${if(");
1291
+ if (index > 0) {
1292
+ const text = textElement.text.substring(index);
1293
+ const match = text.match(/\${if\(([^,]+),/);
1294
+ const property = match ? match[1] : "";
1295
+ if (property) {
1296
+ nameSet.add(property);
1297
+ }
1298
+ }
1299
+ }
1300
+ else if (element.type === ConstantString.ImageType) {
1301
+ const imageElement = element;
1302
+ const match = imageElement.url.match(/\${([^,]+)}/);
1303
+ const property = match ? match[1] : "";
1304
+ if (property) {
1305
+ nameSet.add(property);
1306
+ }
1307
+ }
1308
+ }
1309
+ for (const name of nameSet) {
1310
+ if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
1311
+ result.title = name;
1312
+ nameSet.delete(name);
1313
+ }
1314
+ else if (!result.subtitle &&
1315
+ Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
1316
+ result.subtitle = name;
1317
+ nameSet.delete(name);
1318
+ }
1319
+ else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
1320
+ result.imageUrl = name;
1321
+ nameSet.delete(name);
1322
+ }
1323
+ }
1324
+ for (const name of nameSet) {
1325
+ if (!result.title) {
1326
+ result.title = name;
1327
+ nameSet.delete(name);
1328
+ }
1329
+ else if (!result.subtitle) {
1330
+ result.subtitle = name;
1331
+ nameSet.delete(name);
1332
+ }
1333
+ }
1334
+ if (!result.title && result.subtitle) {
1335
+ result.title = result.subtitle;
1336
+ delete result.subtitle;
1337
+ }
1338
+ return result;
1339
+ }
1340
+
889
1341
  // Copyright (c) Microsoft Corporation.
890
1342
  class ManifestUpdater {
891
1343
  static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
@@ -893,13 +1345,14 @@ class ManifestUpdater {
893
1345
  const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
894
1346
  manifest.plugins = [
895
1347
  {
896
- pluginFile: apiPluginRelativePath,
1348
+ file: apiPluginRelativePath,
1349
+ id: ConstantString.DefaultPluginId,
897
1350
  },
898
1351
  ];
899
1352
  const appName = this.removeEnvs(manifest.name.short);
900
1353
  ManifestUpdater.updateManifestDescription(manifest, spec);
901
1354
  const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
902
- const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
1355
+ const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
903
1356
  return [manifest, apiPlugin];
904
1357
  }
905
1358
  static updateManifestDescription(manifest, spec) {
@@ -923,10 +1376,11 @@ class ManifestUpdater {
923
1376
  }
924
1377
  return parameter;
925
1378
  }
926
- static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
927
- var _a, _b, _c;
1379
+ static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
1380
+ var _a, _b, _c, _d;
928
1381
  const functions = [];
929
1382
  const functionNames = [];
1383
+ const conversationStarters = [];
930
1384
  const paths = spec.paths;
931
1385
  for (const pathUrl in paths) {
932
1386
  const pathItem = paths[pathUrl];
@@ -979,31 +1433,82 @@ class ManifestUpdater {
979
1433
  description: description,
980
1434
  parameters: parameters,
981
1435
  };
1436
+ if (options.allowResponseSemantics) {
1437
+ const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
1438
+ const responseSemantic = wrapResponseSemantics(card, jsonPath);
1439
+ funcObj.capabilities = {
1440
+ response_semantics: responseSemantic,
1441
+ };
1442
+ }
982
1443
  functions.push(funcObj);
983
1444
  functionNames.push(operationId);
1445
+ if (description) {
1446
+ conversationStarters.push(description);
1447
+ }
984
1448
  }
985
1449
  }
986
1450
  }
987
1451
  }
988
1452
  }
989
- const apiPlugin = {
990
- schema_version: "v2",
991
- name_for_human: appName,
992
- description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
993
- functions: functions,
994
- runtimes: [
995
- {
996
- type: "OpenApi",
997
- auth: {
998
- type: "none", // TODO, support auth in the future
999
- },
1000
- spec: {
1001
- url: specRelativePath,
1002
- },
1003
- run_for_functions: functionNames,
1453
+ let apiPlugin;
1454
+ if (await fs.pathExists(apiPluginFilePath)) {
1455
+ apiPlugin = await fs.readJSON(apiPluginFilePath);
1456
+ }
1457
+ else {
1458
+ apiPlugin = {
1459
+ schema_version: "v2",
1460
+ name_for_human: "",
1461
+ description_for_human: "",
1462
+ functions: [],
1463
+ runtimes: [],
1464
+ };
1465
+ }
1466
+ apiPlugin.functions = apiPlugin.functions || [];
1467
+ for (const func of functions) {
1468
+ const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
1469
+ if (index === -1) {
1470
+ apiPlugin.functions.push(func);
1471
+ }
1472
+ else {
1473
+ apiPlugin.functions[index] = func;
1474
+ }
1475
+ }
1476
+ apiPlugin.runtimes = apiPlugin.runtimes || [];
1477
+ const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath);
1478
+ if (index === -1) {
1479
+ apiPlugin.runtimes.push({
1480
+ type: "OpenApi",
1481
+ auth: {
1482
+ type: "none",
1004
1483
  },
1005
- ],
1006
- };
1484
+ spec: {
1485
+ url: specRelativePath,
1486
+ },
1487
+ run_for_functions: functionNames,
1488
+ });
1489
+ }
1490
+ else {
1491
+ apiPlugin.runtimes[index].run_for_functions = functionNames;
1492
+ }
1493
+ if (!apiPlugin.name_for_human) {
1494
+ apiPlugin.name_for_human = appName;
1495
+ }
1496
+ if (!apiPlugin.description_for_human) {
1497
+ apiPlugin.description_for_human =
1498
+ (_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
1499
+ }
1500
+ if (options.allowConversationStarters && conversationStarters.length > 0) {
1501
+ if (!apiPlugin.capabilities) {
1502
+ apiPlugin.capabilities = {
1503
+ localization: {},
1504
+ };
1505
+ }
1506
+ if (!apiPlugin.capabilities.conversation_starters) {
1507
+ apiPlugin.capabilities.conversation_starters = conversationStarters
1508
+ .slice(0, 5)
1509
+ .map((text) => ({ text }));
1510
+ }
1511
+ }
1007
1512
  return apiPlugin;
1008
1513
  }
1009
1514
  static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
@@ -1072,16 +1577,26 @@ class ManifestUpdater {
1072
1577
  if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
1073
1578
  const operationItem = operations[method];
1074
1579
  if (operationItem) {
1075
- const [command, warning] = Utils.parseApiInfo(operationItem, options);
1580
+ const command = Utils.parseApiInfo(operationItem, options);
1581
+ if (command.parameters &&
1582
+ command.parameters.length >= 1 &&
1583
+ command.parameters.some((param) => param.isRequired)) {
1584
+ command.parameters = command.parameters.filter((param) => param.isRequired);
1585
+ }
1586
+ else if (command.parameters && command.parameters.length > 0) {
1587
+ command.parameters = [command.parameters[0]];
1588
+ warnings.push({
1589
+ type: WarningType.OperationOnlyContainsOptionalParam,
1590
+ content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
1591
+ data: command.id,
1592
+ });
1593
+ }
1076
1594
  if (adaptiveCardFolder) {
1077
1595
  const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
1078
1596
  command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
1079
1597
  ? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
1080
1598
  : "";
1081
1599
  }
1082
- if (warning) {
1083
- warnings.push(warning);
1084
- }
1085
1600
  commands.push(command);
1086
1601
  }
1087
1602
  }
@@ -1106,255 +1621,6 @@ class ManifestUpdater {
1106
1621
  }
1107
1622
  }
1108
1623
 
1109
- // Copyright (c) Microsoft Corporation.
1110
- class AdaptiveCardGenerator {
1111
- static generateAdaptiveCard(operationItem) {
1112
- try {
1113
- const { json } = Utils.getResponseJson(operationItem);
1114
- let cardBody = [];
1115
- let schema = json.schema;
1116
- let jsonPath = "$";
1117
- if (schema && Object.keys(schema).length > 0) {
1118
- jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
1119
- if (jsonPath !== "$") {
1120
- schema = schema.properties[jsonPath];
1121
- }
1122
- cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
1123
- }
1124
- // if no schema, try to use example value
1125
- if (cardBody.length === 0 && (json.examples || json.example)) {
1126
- cardBody = [
1127
- {
1128
- type: ConstantString.TextBlockType,
1129
- text: "${jsonStringify($root)}",
1130
- wrap: true,
1131
- },
1132
- ];
1133
- }
1134
- // if no example value, use default success response
1135
- if (cardBody.length === 0) {
1136
- cardBody = [
1137
- {
1138
- type: ConstantString.TextBlockType,
1139
- text: "success",
1140
- wrap: true,
1141
- },
1142
- ];
1143
- }
1144
- const fullCard = {
1145
- type: ConstantString.AdaptiveCardType,
1146
- $schema: ConstantString.AdaptiveCardSchema,
1147
- version: ConstantString.AdaptiveCardVersion,
1148
- body: cardBody,
1149
- };
1150
- return [fullCard, jsonPath];
1151
- }
1152
- catch (err) {
1153
- throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
1154
- }
1155
- }
1156
- static generateCardFromResponse(schema, name, parentArrayName = "") {
1157
- if (schema.type === "array") {
1158
- // schema.items can be arbitrary object: schema { type: array, items: {} }
1159
- if (Object.keys(schema.items).length === 0) {
1160
- return [
1161
- {
1162
- type: ConstantString.TextBlockType,
1163
- text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
1164
- wrap: true,
1165
- },
1166
- ];
1167
- }
1168
- const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
1169
- const template = {
1170
- type: ConstantString.ContainerType,
1171
- $data: name ? `\${${name}}` : "${$root}",
1172
- items: Array(),
1173
- };
1174
- template.items.push(...obj);
1175
- return [template];
1176
- }
1177
- // some schema may not contain type but contain properties
1178
- if (schema.type === "object" || (!schema.type && schema.properties)) {
1179
- const { properties } = schema;
1180
- const result = [];
1181
- for (const property in properties) {
1182
- const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
1183
- result.push(...obj);
1184
- }
1185
- if (schema.additionalProperties) {
1186
- // TODO: better ways to handler warnings.
1187
- console.warn(ConstantString.AdditionalPropertiesNotSupported);
1188
- }
1189
- return result;
1190
- }
1191
- if (schema.type === "string" ||
1192
- schema.type === "integer" ||
1193
- schema.type === "boolean" ||
1194
- schema.type === "number") {
1195
- if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
1196
- // string in root: "ddd"
1197
- let text = "result: ${$root}";
1198
- if (name) {
1199
- // object { id: "1" }
1200
- text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
1201
- if (parentArrayName) {
1202
- // object types inside array: { tags: ["id": 1, "name": "name"] }
1203
- text = `${parentArrayName}.${text}`;
1204
- }
1205
- }
1206
- else if (parentArrayName) {
1207
- // string array: photoUrls: ["1", "2"]
1208
- text = `${parentArrayName}: ` + "${$data}";
1209
- }
1210
- return [
1211
- {
1212
- type: ConstantString.TextBlockType,
1213
- text,
1214
- wrap: true,
1215
- },
1216
- ];
1217
- }
1218
- else {
1219
- if (name) {
1220
- return [
1221
- {
1222
- type: "Image",
1223
- url: `\${${name}}`,
1224
- $when: `\${${name} != null}`,
1225
- },
1226
- ];
1227
- }
1228
- else {
1229
- return [
1230
- {
1231
- type: "Image",
1232
- url: "${$data}",
1233
- $when: "${$data != null}",
1234
- },
1235
- ];
1236
- }
1237
- }
1238
- }
1239
- if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
1240
- throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
1241
- }
1242
- throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
1243
- }
1244
- // Find the first array property in the response schema object with the well-known name
1245
- static getResponseJsonPathFromSchema(schema) {
1246
- if (schema.type === "object" || (!schema.type && schema.properties)) {
1247
- const { properties } = schema;
1248
- for (const property in properties) {
1249
- const schema = properties[property];
1250
- if (schema.type === "array" &&
1251
- Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
1252
- return property;
1253
- }
1254
- }
1255
- }
1256
- return "$";
1257
- }
1258
- static isImageUrlProperty(schema, name, parentArrayName) {
1259
- const propertyName = name ? name : parentArrayName;
1260
- return (!!propertyName &&
1261
- schema.type === "string" &&
1262
- Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
1263
- (propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
1264
- }
1265
- }
1266
-
1267
- // Copyright (c) Microsoft Corporation.
1268
- function wrapAdaptiveCard(card, jsonPath) {
1269
- const result = {
1270
- version: ConstantString.WrappedCardVersion,
1271
- $schema: ConstantString.WrappedCardSchema,
1272
- jsonPath: jsonPath,
1273
- responseLayout: ConstantString.WrappedCardResponseLayout,
1274
- responseCardTemplate: card,
1275
- previewCardTemplate: inferPreviewCardTemplate(card),
1276
- };
1277
- return result;
1278
- }
1279
- /**
1280
- * Infers the preview card template from an Adaptive Card and a JSON path.
1281
- * The preview card template includes a title and an optional subtitle and image.
1282
- * It populates the preview card template with the first text block that matches
1283
- * each well-known name, in the order of title, subtitle, and image.
1284
- * If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
1285
- * If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
1286
- * @param card The Adaptive Card to infer the preview card template from.
1287
- * @param jsonPath The JSON path to the root object in the card body.
1288
- * @returns The inferred preview card template.
1289
- */
1290
- function inferPreviewCardTemplate(card) {
1291
- var _a;
1292
- const result = {
1293
- title: "",
1294
- };
1295
- const textBlockElements = new Set();
1296
- let rootObject;
1297
- if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
1298
- rootObject = card.body[0].items;
1299
- }
1300
- else {
1301
- rootObject = card.body;
1302
- }
1303
- for (const element of rootObject) {
1304
- if (element.type === ConstantString.TextBlockType) {
1305
- const textElement = element;
1306
- const index = textElement.text.indexOf("${if(");
1307
- if (index > 0) {
1308
- textElement.text = textElement.text.substring(index);
1309
- textBlockElements.add(textElement);
1310
- }
1311
- }
1312
- }
1313
- for (const element of textBlockElements) {
1314
- const text = element.text;
1315
- if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
1316
- result.title = text;
1317
- textBlockElements.delete(element);
1318
- }
1319
- else if (!result.subtitle &&
1320
- Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
1321
- result.subtitle = text;
1322
- textBlockElements.delete(element);
1323
- }
1324
- else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
1325
- const match = text.match(/\${if\(([^,]+),/);
1326
- const property = match ? match[1] : "";
1327
- if (property) {
1328
- result.image = {
1329
- url: `\${${property}}`,
1330
- alt: text,
1331
- $when: `\${${property} != null}`,
1332
- };
1333
- }
1334
- textBlockElements.delete(element);
1335
- }
1336
- }
1337
- for (const element of textBlockElements) {
1338
- const text = element.text;
1339
- if (!result.title) {
1340
- result.title = text;
1341
- textBlockElements.delete(element);
1342
- }
1343
- else if (!result.subtitle) {
1344
- result.subtitle = text;
1345
- textBlockElements.delete(element);
1346
- }
1347
- }
1348
- if (!result.title && result.subtitle) {
1349
- result.title = result.subtitle;
1350
- delete result.subtitle;
1351
- }
1352
- if (!result.title) {
1353
- result.title = "result";
1354
- }
1355
- return result;
1356
- }
1357
-
1358
1624
  // Copyright (c) Microsoft Corporation.
1359
1625
  /**
1360
1626
  * A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
@@ -1374,6 +1640,8 @@ class SpecParser {
1374
1640
  allowMultipleParameters: false,
1375
1641
  allowOauth2: false,
1376
1642
  allowMethods: ["get", "post"],
1643
+ allowConversationStarters: false,
1644
+ allowResponseSemantics: false,
1377
1645
  projectType: ProjectType.SME,
1378
1646
  };
1379
1647
  this.pathOrSpec = pathOrDoc;
@@ -1398,6 +1666,8 @@ class SpecParser {
1398
1666
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
1399
1667
  };
1400
1668
  }
1669
+ const errors = [];
1670
+ const warnings = [];
1401
1671
  if (!this.options.allowSwagger && this.isSwaggerFile) {
1402
1672
  return {
1403
1673
  status: ValidationStatus.Error,
@@ -1407,23 +1677,38 @@ class SpecParser {
1407
1677
  ],
1408
1678
  };
1409
1679
  }
1410
- if (this.options.projectType === ProjectType.SME ||
1411
- this.options.projectType === ProjectType.Copilot) {
1412
- if (this.spec.openapi >= "3.1.0") {
1413
- return {
1414
- status: ValidationStatus.Error,
1415
- warnings: [],
1416
- errors: [
1417
- {
1418
- type: ErrorType.SpecVersionNotSupported,
1419
- content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
1420
- data: this.spec.openapi,
1421
- },
1422
- ],
1423
- };
1424
- }
1680
+ // Remote reference not supported
1681
+ const refPaths = this.parser.$refs.paths();
1682
+ // refPaths [0] is the current spec file path
1683
+ if (refPaths.length > 1) {
1684
+ errors.push({
1685
+ type: ErrorType.RemoteRefNotSupported,
1686
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1687
+ data: refPaths,
1688
+ });
1425
1689
  }
1426
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1690
+ if (!!this.isSwaggerFile && this.options.allowSwagger) {
1691
+ warnings.push({
1692
+ type: WarningType.ConvertSwaggerToOpenAPI,
1693
+ content: ConstantString.ConvertSwaggerToOpenAPI,
1694
+ });
1695
+ }
1696
+ const validator = this.getValidator(this.spec);
1697
+ const validationResult = validator.validateSpec();
1698
+ warnings.push(...validationResult.warnings);
1699
+ errors.push(...validationResult.errors);
1700
+ let status = ValidationStatus.Valid;
1701
+ if (warnings.length > 0 && errors.length === 0) {
1702
+ status = ValidationStatus.Warning;
1703
+ }
1704
+ else if (errors.length > 0) {
1705
+ status = ValidationStatus.Error;
1706
+ }
1707
+ return {
1708
+ status: status,
1709
+ warnings: warnings,
1710
+ errors: errors,
1711
+ };
1427
1712
  }
1428
1713
  catch (err) {
1429
1714
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -1452,34 +1737,27 @@ class SpecParser {
1452
1737
  for (const apiKey in apiMap) {
1453
1738
  const { operation, isValid, reason } = apiMap[apiKey];
1454
1739
  const [method, path] = apiKey.split(" ");
1740
+ const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1455
1741
  const apiResult = {
1456
- api: "",
1742
+ api: apiKey,
1457
1743
  server: "",
1458
- operationId: "",
1744
+ operationId: operationId,
1459
1745
  isValid: isValid,
1460
1746
  reason: reason,
1461
1747
  };
1462
- const rootServer = spec.servers && spec.servers[0];
1463
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
1464
- const operationServer = operation.servers && operation.servers[0];
1465
- const serverUrl = operationServer || methodServer || rootServer;
1466
- if (!serverUrl) {
1467
- throw new SpecParserError(ConstantString.NoServerInformation, ErrorType.NoServerInformation);
1468
- }
1469
- apiResult.server = Utils.resolveEnv(serverUrl.url);
1470
- let operationId = operation.operationId;
1471
- if (!operationId) {
1472
- operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1473
- }
1474
- apiResult.operationId = operationId;
1475
- const authArray = Utils.getAuthArray(operation.security, spec);
1476
- for (const auths of authArray) {
1477
- if (auths.length === 1) {
1478
- apiResult.auth = auths[0];
1479
- break;
1748
+ if (isValid) {
1749
+ const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
1750
+ if (serverObj) {
1751
+ apiResult.server = Utils.resolveEnv(serverObj.url);
1752
+ }
1753
+ const authArray = Utils.getAuthArray(operation.security, spec);
1754
+ for (const auths of authArray) {
1755
+ if (auths.length === 1) {
1756
+ apiResult.auth = auths[0];
1757
+ break;
1758
+ }
1480
1759
  }
1481
1760
  }
1482
- apiResult.api = apiKey;
1483
1761
  result.APIs.push(apiResult);
1484
1762
  }
1485
1763
  result.allAPICount = result.APIs.length;
@@ -1659,12 +1937,17 @@ class SpecParser {
1659
1937
  }
1660
1938
  }
1661
1939
  getAPIs(spec) {
1662
- if (this.apiMap !== undefined) {
1663
- return this.apiMap;
1940
+ const validator = this.getValidator(spec);
1941
+ const apiMap = validator.listAPIs();
1942
+ return apiMap;
1943
+ }
1944
+ getValidator(spec) {
1945
+ if (this.validator) {
1946
+ return this.validator;
1664
1947
  }
1665
- const result = Utils.listAPIs(spec, this.options);
1666
- this.apiMap = result;
1667
- return result;
1948
+ const validator = ValidatorFactory.create(spec, this.options);
1949
+ this.validator = validator;
1950
+ return validator;
1668
1951
  }
1669
1952
  }
1670
1953