@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.ad8f60cf1.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.
@@ -29,6 +29,21 @@ var ErrorType;
29
29
  ErrorType["GenerateFailed"] = "generate-failed";
30
30
  ErrorType["ValidateFailed"] = "validate-failed";
31
31
  ErrorType["GetSpecFailed"] = "get-spec-failed";
32
+ ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
33
+ ErrorType["MissingOperationId"] = "missing-operation-id";
34
+ ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
35
+ ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
36
+ ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
37
+ ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
38
+ ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
39
+ ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
40
+ ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
41
+ ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
42
+ ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
43
+ ErrorType["NoParameter"] = "no-parameter";
44
+ ErrorType["NoAPIInfo"] = "no-api-info";
45
+ ErrorType["MethodNotAllowed"] = "method-not-allowed";
46
+ ErrorType["UrlPathNotExist"] = "url-path-not-exist";
32
47
  ErrorType["Cancelled"] = "cancelled";
33
48
  ErrorType["Unknown"] = "unknown";
34
49
  })(ErrorType || (ErrorType = {}));
@@ -68,7 +83,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
68
83
  ConstantString.MissingOperationId = "Missing operationIds: %s.";
69
84
  ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
70
85
  ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
71
- ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
86
+ ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
72
87
  ConstantString.UnknownSchema = "Unknown schema: %s.";
73
88
  ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
74
89
  ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
@@ -88,9 +103,9 @@ ConstantString.AdaptiveCardVersion = "1.5";
88
103
  ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
89
104
  ConstantString.AdaptiveCardType = "AdaptiveCard";
90
105
  ConstantString.TextBlockType = "TextBlock";
106
+ ConstantString.ImageType = "Image";
91
107
  ConstantString.ContainerType = "Container";
92
108
  ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
93
- ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
94
109
  ConstantString.ResponseCodeFor20X = [
95
110
  "200",
96
111
  "201",
@@ -151,7 +166,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
151
166
  ConstantString.ParameterDescriptionMaxLens = 128;
152
167
  ConstantString.CommandTitleMaxLens = 32;
153
168
  ConstantString.ParameterTitleMaxLens = 32;
154
- ConstantString.SMERequiredParamsMaxNum = 5;
169
+ ConstantString.SMERequiredParamsMaxNum = 5;
170
+ ConstantString.DefaultPluginId = "plugin_1";
155
171
 
156
172
  // Copyright (c) Microsoft Corporation.
157
173
  class SpecParserError extends Error {
@@ -174,221 +190,9 @@ class Utils {
174
190
  }
175
191
  return false;
176
192
  }
177
- static checkParameters(paramObject, isCopilot) {
178
- const paramResult = {
179
- requiredNum: 0,
180
- optionalNum: 0,
181
- isValid: true,
182
- };
183
- if (!paramObject) {
184
- return paramResult;
185
- }
186
- for (let i = 0; i < paramObject.length; i++) {
187
- const param = paramObject[i];
188
- const schema = param.schema;
189
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
190
- paramResult.isValid = false;
191
- continue;
192
- }
193
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
194
- if (isCopilot) {
195
- if (isRequiredWithoutDefault) {
196
- paramResult.requiredNum = paramResult.requiredNum + 1;
197
- }
198
- else {
199
- paramResult.optionalNum = paramResult.optionalNum + 1;
200
- }
201
- continue;
202
- }
203
- if (param.in === "header" || param.in === "cookie") {
204
- if (isRequiredWithoutDefault) {
205
- paramResult.isValid = false;
206
- }
207
- continue;
208
- }
209
- if (schema.type !== "boolean" &&
210
- schema.type !== "string" &&
211
- schema.type !== "number" &&
212
- schema.type !== "integer") {
213
- if (isRequiredWithoutDefault) {
214
- paramResult.isValid = false;
215
- }
216
- continue;
217
- }
218
- if (param.in === "query" || param.in === "path") {
219
- if (isRequiredWithoutDefault) {
220
- paramResult.requiredNum = paramResult.requiredNum + 1;
221
- }
222
- else {
223
- paramResult.optionalNum = paramResult.optionalNum + 1;
224
- }
225
- }
226
- }
227
- return paramResult;
228
- }
229
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
230
- var _a;
231
- const paramResult = {
232
- requiredNum: 0,
233
- optionalNum: 0,
234
- isValid: true,
235
- };
236
- if (Object.keys(schema).length === 0) {
237
- return paramResult;
238
- }
239
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
240
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
241
- paramResult.isValid = false;
242
- return paramResult;
243
- }
244
- if (schema.type === "string" ||
245
- schema.type === "integer" ||
246
- schema.type === "boolean" ||
247
- schema.type === "number") {
248
- if (isRequiredWithoutDefault) {
249
- paramResult.requiredNum = paramResult.requiredNum + 1;
250
- }
251
- else {
252
- paramResult.optionalNum = paramResult.optionalNum + 1;
253
- }
254
- }
255
- else if (schema.type === "object") {
256
- const { properties } = schema;
257
- for (const property in properties) {
258
- let isRequired = false;
259
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
260
- isRequired = true;
261
- }
262
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
263
- paramResult.requiredNum += result.requiredNum;
264
- paramResult.optionalNum += result.optionalNum;
265
- paramResult.isValid = paramResult.isValid && result.isValid;
266
- }
267
- }
268
- else {
269
- if (isRequiredWithoutDefault && !isCopilot) {
270
- paramResult.isValid = false;
271
- }
272
- }
273
- return paramResult;
274
- }
275
193
  static containMultipleMediaTypes(bodyObject) {
276
194
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
277
195
  }
278
- /**
279
- * Checks if the given API is supported.
280
- * @param {string} method - The HTTP method of the API.
281
- * @param {string} path - The path of the API.
282
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
283
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
284
- * @description The following APIs are supported:
285
- * 1. only support Get/Post operation without auth property
286
- * 2. parameter inside query or path only support string, number, boolean and integer
287
- * 3. parameter inside post body only support string, number, boolean, integer and object
288
- * 4. request body + required parameters <= 1
289
- * 5. response body should be “application/json” and not empty, and response code should be 20X
290
- * 6. only support request body with “application/json” content type
291
- */
292
- static isSupportedApi(method, path, spec, options) {
293
- var _a;
294
- const pathObj = spec.paths[path];
295
- method = method.toLocaleLowerCase();
296
- if (pathObj) {
297
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
298
- const securities = pathObj[method].security;
299
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
300
- const isCopilot = options.projectType === ProjectType.Copilot;
301
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
302
- if (!isTeamsAi) {
303
- const authArray = Utils.getAuthArray(securities, spec);
304
- if (!Utils.isSupportedAuth(authArray, options)) {
305
- return false;
306
- }
307
- }
308
- const operationObject = pathObj[method];
309
- if (!options.allowMissingId && !operationObject.operationId) {
310
- return false;
311
- }
312
- const paramObject = operationObject.parameters;
313
- const requestBody = operationObject.requestBody;
314
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
315
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
316
- return false;
317
- }
318
- const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
319
- if (Object.keys(responseJson).length === 0) {
320
- return false;
321
- }
322
- // Teams AI project doesn't care about request parameters/body
323
- if (isTeamsAi) {
324
- return true;
325
- }
326
- let requestBodyParamResult = {
327
- requiredNum: 0,
328
- optionalNum: 0,
329
- isValid: true,
330
- };
331
- if (requestJsonBody) {
332
- const requestBodySchema = requestJsonBody.schema;
333
- if (isCopilot && requestBodySchema.type !== "object") {
334
- return false;
335
- }
336
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
337
- }
338
- if (!requestBodyParamResult.isValid) {
339
- return false;
340
- }
341
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
342
- if (!paramResult.isValid) {
343
- return false;
344
- }
345
- // Copilot support arbitrary parameters
346
- if (isCopilot) {
347
- return true;
348
- }
349
- if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
350
- if (options.allowMultipleParameters &&
351
- requestBodyParamResult.requiredNum + paramResult.requiredNum <=
352
- ConstantString.SMERequiredParamsMaxNum) {
353
- return true;
354
- }
355
- return false;
356
- }
357
- else if (requestBodyParamResult.requiredNum +
358
- requestBodyParamResult.optionalNum +
359
- paramResult.requiredNum +
360
- paramResult.optionalNum ===
361
- 0) {
362
- return false;
363
- }
364
- else {
365
- return true;
366
- }
367
- }
368
- }
369
- return false;
370
- }
371
- static isSupportedAuth(authSchemeArray, options) {
372
- if (authSchemeArray.length === 0) {
373
- return true;
374
- }
375
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
376
- // Currently we don't support multiple auth in one operation
377
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
378
- return false;
379
- }
380
- for (const auths of authSchemeArray) {
381
- if (auths.length === 1) {
382
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
383
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
384
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
385
- return true;
386
- }
387
- }
388
- }
389
- }
390
- return false;
391
- }
392
196
  static isBearerTokenAuth(authScheme) {
393
197
  return authScheme.type === "http" && authScheme.scheme === "bearer";
394
198
  }
@@ -396,18 +200,18 @@ class Utils {
396
200
  return authScheme.type === "apiKey";
397
201
  }
398
202
  static isOAuthWithAuthCodeFlow(authScheme) {
399
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
400
- return true;
401
- }
402
- return false;
203
+ return !!(authScheme.type === "oauth2" &&
204
+ authScheme.flows &&
205
+ authScheme.flows.authorizationCode);
403
206
  }
404
207
  static getAuthArray(securities, spec) {
405
208
  var _a;
406
209
  const result = [];
407
210
  const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
408
- if (securities && securitySchemas) {
409
- for (let i = 0; i < securities.length; i++) {
410
- const security = securities[i];
211
+ const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
212
+ if (securitiesArr && securitySchemas) {
213
+ for (let i = 0; i < securitiesArr.length; i++) {
214
+ const security = securitiesArr[i];
411
215
  const authArray = [];
412
216
  for (const name in security) {
413
217
  const auth = securitySchemas[name];
@@ -424,17 +228,39 @@ class Utils {
424
228
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
425
229
  return result;
426
230
  }
231
+ static getAuthInfo(spec) {
232
+ let authInfo = undefined;
233
+ for (const url in spec.paths) {
234
+ for (const method in spec.paths[url]) {
235
+ const operation = spec.paths[url][method];
236
+ const authArray = Utils.getAuthArray(operation.security, spec);
237
+ if (authArray && authArray.length > 0) {
238
+ const currentAuth = authArray[0][0];
239
+ if (!authInfo) {
240
+ authInfo = authArray[0][0];
241
+ }
242
+ else if (authInfo.name !== currentAuth.name) {
243
+ throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ return authInfo;
249
+ }
427
250
  static updateFirstLetter(str) {
428
251
  return str.charAt(0).toUpperCase() + str.slice(1);
429
252
  }
430
- static getResponseJson(operationObject, isTeamsAiProject = false) {
253
+ static getResponseJson(operationObject) {
431
254
  var _a, _b;
432
255
  let json = {};
256
+ let multipleMediaType = false;
433
257
  for (const code of ConstantString.ResponseCodeFor20X) {
434
258
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
435
259
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
260
+ multipleMediaType = false;
436
261
  json = responseObject.content["application/json"];
437
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
262
+ if (Utils.containMultipleMediaTypes(responseObject)) {
263
+ multipleMediaType = true;
438
264
  json = {};
439
265
  }
440
266
  else {
@@ -442,7 +268,7 @@ class Utils {
442
268
  }
443
269
  }
444
270
  }
445
- return json;
271
+ return { json, multipleMediaType };
446
272
  }
447
273
  static convertPathToCamelCase(path) {
448
274
  const pathSegments = path.split(/[./{]/);
@@ -462,10 +288,10 @@ class Utils {
462
288
  return undefined;
463
289
  }
464
290
  }
465
- static resolveServerUrl(url) {
291
+ static resolveEnv(str) {
466
292
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
467
- let matches = placeHolderReg.exec(url);
468
- let newUrl = url;
293
+ let matches = placeHolderReg.exec(str);
294
+ let newStr = str;
469
295
  while (matches != null) {
470
296
  const envVar = matches[1];
471
297
  const envVal = process.env[envVar];
@@ -473,17 +299,17 @@ class Utils {
473
299
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
474
300
  }
475
301
  else {
476
- newUrl = newUrl.replace(matches[0], envVal);
302
+ newStr = newStr.replace(matches[0], envVal);
477
303
  }
478
- matches = placeHolderReg.exec(url);
304
+ matches = placeHolderReg.exec(str);
479
305
  }
480
- return newUrl;
306
+ return newStr;
481
307
  }
482
308
  static checkServerUrl(servers) {
483
309
  const errors = [];
484
310
  let serverUrl;
485
311
  try {
486
- serverUrl = Utils.resolveServerUrl(servers[0].url);
312
+ serverUrl = Utils.resolveEnv(servers[0].url);
487
313
  }
488
314
  catch (err) {
489
315
  errors.push({
@@ -514,6 +340,7 @@ class Utils {
514
340
  return errors;
515
341
  }
516
342
  static validateServer(spec, options) {
343
+ var _a;
517
344
  const errors = [];
518
345
  let hasTopLevelServers = false;
519
346
  let hasPathLevelServers = false;
@@ -534,7 +361,7 @@ class Utils {
534
361
  }
535
362
  for (const method in methods) {
536
363
  const operationObject = methods[method];
537
- if (Utils.isSupportedApi(method, path, spec, options)) {
364
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
538
365
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
539
366
  hasOperationLevelServers = true;
540
367
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -661,13 +488,7 @@ class Utils {
661
488
  }
662
489
  }
663
490
  const operationId = operationItem.operationId;
664
- const parameters = [];
665
- if (requiredParams.length !== 0) {
666
- parameters.push(...requiredParams);
667
- }
668
- else {
669
- parameters.push(optionalParams[0]);
670
- }
491
+ const parameters = [...requiredParams, ...optionalParams];
671
492
  const command = {
672
493
  context: ["compose"],
673
494
  type: "query",
@@ -676,166 +497,883 @@ class Utils {
676
497
  parameters: parameters,
677
498
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
678
499
  };
679
- let warning = undefined;
680
- if (requiredParams.length === 0 && optionalParams.length > 1) {
681
- warning = {
682
- type: WarningType.OperationOnlyContainsOptionalParam,
683
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
684
- data: operationId,
685
- };
500
+ return command;
501
+ }
502
+ static format(str, ...args) {
503
+ let index = 0;
504
+ return str.replace(/%s/g, () => {
505
+ const arg = args[index++];
506
+ return arg !== undefined ? arg : "";
507
+ });
508
+ }
509
+ static getSafeRegistrationIdEnvName(authName) {
510
+ if (!authName) {
511
+ return "";
512
+ }
513
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
514
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
515
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
686
516
  }
687
- return [command, warning];
517
+ return safeRegistrationIdEnvName;
688
518
  }
689
- static listSupportedAPIs(spec, options) {
690
- const paths = spec.paths;
519
+ static getServerObject(spec, method, path) {
520
+ const pathObj = spec.paths[path];
521
+ const operationObject = pathObj[method];
522
+ const rootServer = spec.servers && spec.servers[0];
523
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
524
+ const operationServer = operationObject.servers && operationObject.servers[0];
525
+ const serverUrl = operationServer || methodServer || rootServer;
526
+ return serverUrl;
527
+ }
528
+ }
529
+
530
+ // Copyright (c) Microsoft Corporation.
531
+ class Validator {
532
+ listAPIs() {
533
+ var _a;
534
+ if (this.apiMap) {
535
+ return this.apiMap;
536
+ }
537
+ const paths = this.spec.paths;
691
538
  const result = {};
692
539
  for (const path in paths) {
693
540
  const methods = paths[path];
694
541
  for (const method in methods) {
695
- if (Utils.isSupportedApi(method, path, spec, options)) {
696
- const operationObject = methods[method];
697
- result[`${method.toUpperCase()} ${path}`] = operationObject;
542
+ const operationObject = methods[method];
543
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
544
+ const validateResult = this.validateAPI(method, path);
545
+ result[`${method.toUpperCase()} ${path}`] = {
546
+ operation: operationObject,
547
+ isValid: validateResult.isValid,
548
+ reason: validateResult.reason,
549
+ };
698
550
  }
699
551
  }
700
552
  }
553
+ this.apiMap = result;
701
554
  return result;
702
555
  }
703
- static validateSpec(spec, parser, isSwaggerFile, options) {
704
- const errors = [];
705
- const warnings = [];
706
- if (isSwaggerFile) {
707
- warnings.push({
708
- type: WarningType.ConvertSwaggerToOpenAPI,
709
- content: ConstantString.ConvertSwaggerToOpenAPI,
710
- });
711
- }
712
- // Server validation
713
- const serverErrors = Utils.validateServer(spec, options);
714
- errors.push(...serverErrors);
715
- // Remote reference not supported
716
- const refPaths = parser.$refs.paths();
717
- // refPaths [0] is the current spec file path
718
- if (refPaths.length > 1) {
719
- errors.push({
720
- type: ErrorType.RemoteRefNotSupported,
721
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
722
- data: refPaths,
556
+ validateSpecVersion() {
557
+ const result = { errors: [], warnings: [] };
558
+ if (this.spec.openapi >= "3.1.0") {
559
+ result.errors.push({
560
+ type: ErrorType.SpecVersionNotSupported,
561
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
562
+ data: this.spec.openapi,
723
563
  });
724
564
  }
725
- // No supported API
726
- const apiMap = Utils.listSupportedAPIs(spec, options);
727
- if (Object.keys(apiMap).length === 0) {
728
- errors.push({
565
+ return result;
566
+ }
567
+ validateSpecServer() {
568
+ const result = { errors: [], warnings: [] };
569
+ const serverErrors = Utils.validateServer(this.spec, this.options);
570
+ result.errors.push(...serverErrors);
571
+ return result;
572
+ }
573
+ validateSpecNoSupportAPI() {
574
+ const result = { errors: [], warnings: [] };
575
+ const apiMap = this.listAPIs();
576
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
577
+ if (validAPIs.length === 0) {
578
+ const data = [];
579
+ for (const key in apiMap) {
580
+ const { reason } = apiMap[key];
581
+ const apiInvalidReason = { api: key, reason: reason };
582
+ data.push(apiInvalidReason);
583
+ }
584
+ result.errors.push({
729
585
  type: ErrorType.NoSupportedApi,
730
586
  content: ConstantString.NoSupportedApi,
587
+ data,
731
588
  });
732
589
  }
590
+ return result;
591
+ }
592
+ validateSpecOperationId() {
593
+ const result = { errors: [], warnings: [] };
594
+ const apiMap = this.listAPIs();
733
595
  // OperationId missing
734
596
  const apisMissingOperationId = [];
735
597
  for (const key in apiMap) {
736
- const pathObjectItem = apiMap[key];
737
- if (!pathObjectItem.operationId) {
598
+ const { operation } = apiMap[key];
599
+ if (!operation.operationId) {
738
600
  apisMissingOperationId.push(key);
739
601
  }
740
602
  }
741
603
  if (apisMissingOperationId.length > 0) {
742
- warnings.push({
604
+ result.warnings.push({
743
605
  type: WarningType.OperationIdMissing,
744
606
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
745
607
  data: apisMissingOperationId,
746
608
  });
747
609
  }
748
- let status = ValidationStatus.Valid;
749
- if (warnings.length > 0 && errors.length === 0) {
750
- status = ValidationStatus.Warning;
610
+ return result;
611
+ }
612
+ validateMethodAndPath(method, path) {
613
+ const result = { isValid: true, reason: [] };
614
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
615
+ result.isValid = false;
616
+ result.reason.push(ErrorType.MethodNotAllowed);
617
+ return result;
751
618
  }
752
- else if (errors.length > 0) {
753
- status = ValidationStatus.Error;
619
+ const pathObj = this.spec.paths[path];
620
+ if (!pathObj || !pathObj[method]) {
621
+ result.isValid = false;
622
+ result.reason.push(ErrorType.UrlPathNotExist);
623
+ return result;
754
624
  }
755
- return {
756
- status,
757
- warnings,
758
- errors,
759
- };
625
+ return result;
760
626
  }
761
- static format(str, ...args) {
762
- let index = 0;
763
- return str.replace(/%s/g, () => {
764
- const arg = args[index++];
765
- return arg !== undefined ? arg : "";
766
- });
627
+ validateResponse(method, path) {
628
+ const result = { isValid: true, reason: [] };
629
+ const operationObject = this.spec.paths[path][method];
630
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
631
+ if (this.options.projectType === ProjectType.SME) {
632
+ // only support response body only contains “application/json” content type
633
+ if (multipleMediaType) {
634
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
635
+ }
636
+ else if (Object.keys(json).length === 0) {
637
+ // response body should not be empty
638
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
639
+ }
640
+ }
641
+ return result;
767
642
  }
768
- static getSafeRegistrationIdEnvName(authName) {
769
- if (!authName) {
770
- return "";
643
+ validateServer(method, path) {
644
+ const result = { isValid: true, reason: [] };
645
+ const serverObj = Utils.getServerObject(this.spec, method, path);
646
+ if (!serverObj) {
647
+ // should contain server URL
648
+ result.reason.push(ErrorType.NoServerInformation);
771
649
  }
772
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
773
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
774
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
650
+ else {
651
+ // server url should be absolute url with https protocol
652
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
653
+ result.reason.push(...serverValidateResult.map((item) => item.type));
775
654
  }
776
- return safeRegistrationIdEnvName;
655
+ return result;
777
656
  }
778
- static getAllAPICount(spec) {
779
- let count = 0;
780
- const paths = spec.paths;
781
- for (const path in paths) {
782
- const methods = paths[path];
783
- for (const method in methods) {
784
- if (ConstantString.AllOperationMethods.includes(method)) {
785
- count++;
786
- }
787
- }
657
+ validateAuth(method, path) {
658
+ const pathObj = this.spec.paths[path];
659
+ const operationObject = pathObj[method];
660
+ const securities = operationObject.security;
661
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
662
+ if (authSchemeArray.length === 0) {
663
+ return { isValid: true, reason: [] };
788
664
  }
789
- return count;
790
- }
791
- }
792
-
793
- // Copyright (c) Microsoft Corporation.
794
- class SpecFilter {
795
- static specFilter(filter, unResolveSpec, resolvedSpec, options) {
796
- try {
797
- const newSpec = Object.assign({}, unResolveSpec);
798
- const newPaths = {};
799
- for (const filterItem of filter) {
800
- const [method, path] = filterItem.split(" ");
801
- const methodName = method.toLowerCase();
802
- if (!Utils.isSupportedApi(methodName, path, resolvedSpec, options)) {
803
- continue;
804
- }
805
- if (!newPaths[path]) {
806
- newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
807
- for (const m of ConstantString.AllOperationMethods) {
808
- delete newPaths[path][m];
665
+ if (this.options.allowAPIKeyAuth ||
666
+ this.options.allowOauth2 ||
667
+ this.options.allowBearerTokenAuth) {
668
+ // Currently we don't support multiple auth in one operation
669
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
670
+ return {
671
+ isValid: false,
672
+ reason: [ErrorType.MultipleAuthNotSupported],
673
+ };
674
+ }
675
+ for (const auths of authSchemeArray) {
676
+ if (auths.length === 1) {
677
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
678
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
679
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
680
+ return { isValid: true, reason: [] };
809
681
  }
810
682
  }
811
- newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
812
- // Add the operationId if missing
813
- if (!newPaths[path][methodName].operationId) {
814
- newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
815
- }
816
683
  }
817
- newSpec.paths = newPaths;
818
- return newSpec;
819
- }
820
- catch (err) {
821
- throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
822
684
  }
685
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
823
686
  }
824
- }
825
-
826
- // Copyright (c) Microsoft Corporation.
827
- class ManifestUpdater {
828
- static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
829
- const manifest = await fs.readJSON(manifestPath);
830
- const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
831
- manifest.plugins = [
832
- {
833
- pluginFile: apiPluginRelativePath,
834
- },
835
- ];
836
- ManifestUpdater.updateManifestDescription(manifest, spec);
837
- const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
838
- const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
687
+ checkPostBodySchema(schema, isRequired = false) {
688
+ var _a;
689
+ const paramResult = {
690
+ requiredNum: 0,
691
+ optionalNum: 0,
692
+ isValid: true,
693
+ reason: [],
694
+ };
695
+ if (Object.keys(schema).length === 0) {
696
+ return paramResult;
697
+ }
698
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
699
+ const isCopilot = this.projectType === ProjectType.Copilot;
700
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
701
+ paramResult.isValid = false;
702
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
703
+ return paramResult;
704
+ }
705
+ if (schema.type === "string" ||
706
+ schema.type === "integer" ||
707
+ schema.type === "boolean" ||
708
+ schema.type === "number") {
709
+ if (isRequiredWithoutDefault) {
710
+ paramResult.requiredNum = paramResult.requiredNum + 1;
711
+ }
712
+ else {
713
+ paramResult.optionalNum = paramResult.optionalNum + 1;
714
+ }
715
+ }
716
+ else if (schema.type === "object") {
717
+ const { properties } = schema;
718
+ for (const property in properties) {
719
+ let isRequired = false;
720
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
721
+ isRequired = true;
722
+ }
723
+ const result = this.checkPostBodySchema(properties[property], isRequired);
724
+ paramResult.requiredNum += result.requiredNum;
725
+ paramResult.optionalNum += result.optionalNum;
726
+ paramResult.isValid = paramResult.isValid && result.isValid;
727
+ paramResult.reason.push(...result.reason);
728
+ }
729
+ }
730
+ else {
731
+ if (isRequiredWithoutDefault && !isCopilot) {
732
+ paramResult.isValid = false;
733
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
734
+ }
735
+ }
736
+ return paramResult;
737
+ }
738
+ checkParamSchema(paramObject) {
739
+ const paramResult = {
740
+ requiredNum: 0,
741
+ optionalNum: 0,
742
+ isValid: true,
743
+ reason: [],
744
+ };
745
+ if (!paramObject) {
746
+ return paramResult;
747
+ }
748
+ const isCopilot = this.projectType === ProjectType.Copilot;
749
+ for (let i = 0; i < paramObject.length; i++) {
750
+ const param = paramObject[i];
751
+ const schema = param.schema;
752
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
753
+ paramResult.isValid = false;
754
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
755
+ continue;
756
+ }
757
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
758
+ if (isCopilot) {
759
+ if (isRequiredWithoutDefault) {
760
+ paramResult.requiredNum = paramResult.requiredNum + 1;
761
+ }
762
+ else {
763
+ paramResult.optionalNum = paramResult.optionalNum + 1;
764
+ }
765
+ continue;
766
+ }
767
+ if (param.in === "header" || param.in === "cookie") {
768
+ if (isRequiredWithoutDefault) {
769
+ paramResult.isValid = false;
770
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
771
+ }
772
+ continue;
773
+ }
774
+ if (schema.type !== "boolean" &&
775
+ schema.type !== "string" &&
776
+ schema.type !== "number" &&
777
+ schema.type !== "integer") {
778
+ if (isRequiredWithoutDefault) {
779
+ paramResult.isValid = false;
780
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
781
+ }
782
+ continue;
783
+ }
784
+ if (param.in === "query" || param.in === "path") {
785
+ if (isRequiredWithoutDefault) {
786
+ paramResult.requiredNum = paramResult.requiredNum + 1;
787
+ }
788
+ else {
789
+ paramResult.optionalNum = paramResult.optionalNum + 1;
790
+ }
791
+ }
792
+ }
793
+ return paramResult;
794
+ }
795
+ hasNestedObjectInSchema(schema) {
796
+ if (schema.type === "object") {
797
+ for (const property in schema.properties) {
798
+ const nestedSchema = schema.properties[property];
799
+ if (nestedSchema.type === "object") {
800
+ return true;
801
+ }
802
+ }
803
+ }
804
+ return false;
805
+ }
806
+ }
807
+
808
+ // Copyright (c) Microsoft Corporation.
809
+ class CopilotValidator extends Validator {
810
+ constructor(spec, options) {
811
+ super();
812
+ this.projectType = ProjectType.Copilot;
813
+ this.options = options;
814
+ this.spec = spec;
815
+ }
816
+ validateSpec() {
817
+ const result = { errors: [], warnings: [] };
818
+ // validate spec version
819
+ let validationResult = this.validateSpecVersion();
820
+ result.errors.push(...validationResult.errors);
821
+ // validate spec server
822
+ validationResult = this.validateSpecServer();
823
+ result.errors.push(...validationResult.errors);
824
+ // validate no supported API
825
+ validationResult = this.validateSpecNoSupportAPI();
826
+ result.errors.push(...validationResult.errors);
827
+ // validate operationId missing
828
+ validationResult = this.validateSpecOperationId();
829
+ result.warnings.push(...validationResult.warnings);
830
+ return result;
831
+ }
832
+ validateAPI(method, path) {
833
+ const result = { isValid: true, reason: [] };
834
+ method = method.toLocaleLowerCase();
835
+ // validate method and path
836
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
837
+ if (!methodAndPathResult.isValid) {
838
+ return methodAndPathResult;
839
+ }
840
+ const operationObject = this.spec.paths[path][method];
841
+ // validate auth
842
+ const authCheckResult = this.validateAuth(method, path);
843
+ result.reason.push(...authCheckResult.reason);
844
+ // validate operationId
845
+ if (!this.options.allowMissingId && !operationObject.operationId) {
846
+ result.reason.push(ErrorType.MissingOperationId);
847
+ }
848
+ // validate server
849
+ const validateServerResult = this.validateServer(method, path);
850
+ result.reason.push(...validateServerResult.reason);
851
+ // validate response
852
+ const validateResponseResult = this.validateResponse(method, path);
853
+ result.reason.push(...validateResponseResult.reason);
854
+ // validate requestBody
855
+ const requestBody = operationObject.requestBody;
856
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
857
+ if (requestJsonBody) {
858
+ const requestBodySchema = requestJsonBody.schema;
859
+ if (requestBodySchema.type !== "object") {
860
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
861
+ }
862
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
863
+ result.reason.push(...requestBodyParamResult.reason);
864
+ }
865
+ // validate parameters
866
+ const paramObject = operationObject.parameters;
867
+ const paramResult = this.checkParamSchema(paramObject);
868
+ result.reason.push(...paramResult.reason);
869
+ if (result.reason.length > 0) {
870
+ result.isValid = false;
871
+ }
872
+ return result;
873
+ }
874
+ }
875
+
876
+ // Copyright (c) Microsoft Corporation.
877
+ class SMEValidator extends Validator {
878
+ constructor(spec, options) {
879
+ super();
880
+ this.projectType = ProjectType.SME;
881
+ this.options = options;
882
+ this.spec = spec;
883
+ }
884
+ validateSpec() {
885
+ const result = { errors: [], warnings: [] };
886
+ // validate spec version
887
+ let validationResult = this.validateSpecVersion();
888
+ result.errors.push(...validationResult.errors);
889
+ // validate spec server
890
+ validationResult = this.validateSpecServer();
891
+ result.errors.push(...validationResult.errors);
892
+ // validate no supported API
893
+ validationResult = this.validateSpecNoSupportAPI();
894
+ result.errors.push(...validationResult.errors);
895
+ // validate operationId missing
896
+ if (this.options.allowMissingId) {
897
+ validationResult = this.validateSpecOperationId();
898
+ result.warnings.push(...validationResult.warnings);
899
+ }
900
+ return result;
901
+ }
902
+ validateAPI(method, path) {
903
+ const result = { isValid: true, reason: [] };
904
+ method = method.toLocaleLowerCase();
905
+ // validate method and path
906
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
907
+ if (!methodAndPathResult.isValid) {
908
+ return methodAndPathResult;
909
+ }
910
+ const operationObject = this.spec.paths[path][method];
911
+ // validate auth
912
+ const authCheckResult = this.validateAuth(method, path);
913
+ result.reason.push(...authCheckResult.reason);
914
+ // validate operationId
915
+ if (!this.options.allowMissingId && !operationObject.operationId) {
916
+ result.reason.push(ErrorType.MissingOperationId);
917
+ }
918
+ // validate server
919
+ const validateServerResult = this.validateServer(method, path);
920
+ result.reason.push(...validateServerResult.reason);
921
+ // validate response
922
+ const validateResponseResult = this.validateResponse(method, path);
923
+ result.reason.push(...validateResponseResult.reason);
924
+ let postBodyResult = {
925
+ requiredNum: 0,
926
+ optionalNum: 0,
927
+ isValid: true,
928
+ reason: [],
929
+ };
930
+ // validate requestBody
931
+ const requestBody = operationObject.requestBody;
932
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
933
+ if (Utils.containMultipleMediaTypes(requestBody)) {
934
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
935
+ }
936
+ if (requestJsonBody) {
937
+ const requestBodySchema = requestJsonBody.schema;
938
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
939
+ result.reason.push(...postBodyResult.reason);
940
+ }
941
+ // validate parameters
942
+ const paramObject = operationObject.parameters;
943
+ const paramResult = this.checkParamSchema(paramObject);
944
+ result.reason.push(...paramResult.reason);
945
+ // validate total parameters count
946
+ if (paramResult.isValid && postBodyResult.isValid) {
947
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
948
+ result.reason.push(...paramCountResult.reason);
949
+ }
950
+ if (result.reason.length > 0) {
951
+ result.isValid = false;
952
+ }
953
+ return result;
954
+ }
955
+ validateParamCount(postBodyResult, paramResult) {
956
+ const result = { isValid: true, reason: [] };
957
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
958
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
959
+ if (totalRequiredParams > 1) {
960
+ if (!this.options.allowMultipleParameters ||
961
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
962
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
963
+ }
964
+ }
965
+ else if (totalParams === 0) {
966
+ result.reason.push(ErrorType.NoParameter);
967
+ }
968
+ return result;
969
+ }
970
+ }
971
+ SMEValidator.SMERequiredParamsMaxNum = 5;
972
+
973
+ // Copyright (c) Microsoft Corporation.
974
+ class TeamsAIValidator extends Validator {
975
+ constructor(spec, options) {
976
+ super();
977
+ this.projectType = ProjectType.TeamsAi;
978
+ this.options = options;
979
+ this.spec = spec;
980
+ }
981
+ validateSpec() {
982
+ const result = { errors: [], warnings: [] };
983
+ // validate spec server
984
+ let validationResult = this.validateSpecServer();
985
+ result.errors.push(...validationResult.errors);
986
+ // validate no supported API
987
+ validationResult = this.validateSpecNoSupportAPI();
988
+ result.errors.push(...validationResult.errors);
989
+ return result;
990
+ }
991
+ validateAPI(method, path) {
992
+ const result = { isValid: true, reason: [] };
993
+ method = method.toLocaleLowerCase();
994
+ // validate method and path
995
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
996
+ if (!methodAndPathResult.isValid) {
997
+ return methodAndPathResult;
998
+ }
999
+ const operationObject = this.spec.paths[path][method];
1000
+ // validate operationId
1001
+ if (!this.options.allowMissingId && !operationObject.operationId) {
1002
+ result.reason.push(ErrorType.MissingOperationId);
1003
+ }
1004
+ // validate server
1005
+ const validateServerResult = this.validateServer(method, path);
1006
+ result.reason.push(...validateServerResult.reason);
1007
+ if (result.reason.length > 0) {
1008
+ result.isValid = false;
1009
+ }
1010
+ return result;
1011
+ }
1012
+ }
1013
+
1014
+ class ValidatorFactory {
1015
+ static create(spec, options) {
1016
+ var _a;
1017
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1018
+ switch (type) {
1019
+ case ProjectType.SME:
1020
+ return new SMEValidator(spec, options);
1021
+ case ProjectType.Copilot:
1022
+ return new CopilotValidator(spec, options);
1023
+ case ProjectType.TeamsAi:
1024
+ return new TeamsAIValidator(spec, options);
1025
+ default:
1026
+ throw new Error(`Invalid project type: ${type}`);
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ // Copyright (c) Microsoft Corporation.
1032
+ class SpecFilter {
1033
+ static specFilter(filter, unResolveSpec, resolvedSpec, options) {
1034
+ var _a;
1035
+ try {
1036
+ const newSpec = Object.assign({}, unResolveSpec);
1037
+ const newPaths = {};
1038
+ for (const filterItem of filter) {
1039
+ const [method, path] = filterItem.split(" ");
1040
+ const methodName = method.toLowerCase();
1041
+ const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
1042
+ if (ConstantString.AllOperationMethods.includes(methodName) &&
1043
+ pathObj &&
1044
+ pathObj[methodName]) {
1045
+ const validator = ValidatorFactory.create(resolvedSpec, options);
1046
+ const validateResult = validator.validateAPI(methodName, path);
1047
+ if (!validateResult.isValid) {
1048
+ continue;
1049
+ }
1050
+ if (!newPaths[path]) {
1051
+ newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
1052
+ for (const m of ConstantString.AllOperationMethods) {
1053
+ delete newPaths[path][m];
1054
+ }
1055
+ }
1056
+ newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
1057
+ // Add the operationId if missing
1058
+ if (!newPaths[path][methodName].operationId) {
1059
+ newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
1060
+ }
1061
+ }
1062
+ }
1063
+ newSpec.paths = newPaths;
1064
+ return newSpec;
1065
+ }
1066
+ catch (err) {
1067
+ throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ // Copyright (c) Microsoft Corporation.
1073
+ class AdaptiveCardGenerator {
1074
+ static generateAdaptiveCard(operationItem) {
1075
+ try {
1076
+ const { json } = Utils.getResponseJson(operationItem);
1077
+ let cardBody = [];
1078
+ let schema = json.schema;
1079
+ let jsonPath = "$";
1080
+ if (schema && Object.keys(schema).length > 0) {
1081
+ jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
1082
+ if (jsonPath !== "$") {
1083
+ schema = schema.properties[jsonPath];
1084
+ }
1085
+ cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
1086
+ }
1087
+ // if no schema, try to use example value
1088
+ if (cardBody.length === 0 && (json.examples || json.example)) {
1089
+ cardBody = [
1090
+ {
1091
+ type: ConstantString.TextBlockType,
1092
+ text: "${jsonStringify($root)}",
1093
+ wrap: true,
1094
+ },
1095
+ ];
1096
+ }
1097
+ // if no example value, use default success response
1098
+ if (cardBody.length === 0) {
1099
+ cardBody = [
1100
+ {
1101
+ type: ConstantString.TextBlockType,
1102
+ text: "success",
1103
+ wrap: true,
1104
+ },
1105
+ ];
1106
+ }
1107
+ const fullCard = {
1108
+ type: ConstantString.AdaptiveCardType,
1109
+ $schema: ConstantString.AdaptiveCardSchema,
1110
+ version: ConstantString.AdaptiveCardVersion,
1111
+ body: cardBody,
1112
+ };
1113
+ return [fullCard, jsonPath];
1114
+ }
1115
+ catch (err) {
1116
+ throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
1117
+ }
1118
+ }
1119
+ static generateCardFromResponse(schema, name, parentArrayName = "") {
1120
+ if (schema.type === "array") {
1121
+ // schema.items can be arbitrary object: schema { type: array, items: {} }
1122
+ if (Object.keys(schema.items).length === 0) {
1123
+ return [
1124
+ {
1125
+ type: ConstantString.TextBlockType,
1126
+ text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
1127
+ wrap: true,
1128
+ },
1129
+ ];
1130
+ }
1131
+ const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
1132
+ const template = {
1133
+ type: ConstantString.ContainerType,
1134
+ $data: name ? `\${${name}}` : "${$root}",
1135
+ items: Array(),
1136
+ };
1137
+ template.items.push(...obj);
1138
+ return [template];
1139
+ }
1140
+ // some schema may not contain type but contain properties
1141
+ if (schema.type === "object" || (!schema.type && schema.properties)) {
1142
+ const { properties } = schema;
1143
+ const result = [];
1144
+ for (const property in properties) {
1145
+ const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
1146
+ result.push(...obj);
1147
+ }
1148
+ if (schema.additionalProperties) {
1149
+ // TODO: better ways to handler warnings.
1150
+ console.warn(ConstantString.AdditionalPropertiesNotSupported);
1151
+ }
1152
+ return result;
1153
+ }
1154
+ if (schema.type === "string" ||
1155
+ schema.type === "integer" ||
1156
+ schema.type === "boolean" ||
1157
+ schema.type === "number") {
1158
+ if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
1159
+ // string in root: "ddd"
1160
+ let text = "result: ${$root}";
1161
+ if (name) {
1162
+ // object { id: "1" }
1163
+ text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
1164
+ if (parentArrayName) {
1165
+ // object types inside array: { tags: ["id": 1, "name": "name"] }
1166
+ text = `${parentArrayName}.${text}`;
1167
+ }
1168
+ }
1169
+ else if (parentArrayName) {
1170
+ // string array: photoUrls: ["1", "2"]
1171
+ text = `${parentArrayName}: ` + "${$data}";
1172
+ }
1173
+ return [
1174
+ {
1175
+ type: ConstantString.TextBlockType,
1176
+ text,
1177
+ wrap: true,
1178
+ },
1179
+ ];
1180
+ }
1181
+ else {
1182
+ if (name) {
1183
+ return [
1184
+ {
1185
+ type: "Image",
1186
+ url: `\${${name}}`,
1187
+ $when: `\${${name} != null}`,
1188
+ },
1189
+ ];
1190
+ }
1191
+ else {
1192
+ return [
1193
+ {
1194
+ type: "Image",
1195
+ url: "${$data}",
1196
+ $when: "${$data != null}",
1197
+ },
1198
+ ];
1199
+ }
1200
+ }
1201
+ }
1202
+ if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
1203
+ throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
1204
+ }
1205
+ throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
1206
+ }
1207
+ // Find the first array property in the response schema object with the well-known name
1208
+ static getResponseJsonPathFromSchema(schema) {
1209
+ if (schema.type === "object" || (!schema.type && schema.properties)) {
1210
+ const { properties } = schema;
1211
+ for (const property in properties) {
1212
+ const schema = properties[property];
1213
+ if (schema.type === "array" &&
1214
+ Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
1215
+ return property;
1216
+ }
1217
+ }
1218
+ }
1219
+ return "$";
1220
+ }
1221
+ static isImageUrlProperty(schema, name, parentArrayName) {
1222
+ const propertyName = name ? name : parentArrayName;
1223
+ return (!!propertyName &&
1224
+ schema.type === "string" &&
1225
+ Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
1226
+ (propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
1227
+ }
1228
+ }
1229
+
1230
+ // Copyright (c) Microsoft Corporation.
1231
+ function wrapAdaptiveCard(card, jsonPath) {
1232
+ const result = {
1233
+ version: ConstantString.WrappedCardVersion,
1234
+ $schema: ConstantString.WrappedCardSchema,
1235
+ jsonPath: jsonPath,
1236
+ responseLayout: ConstantString.WrappedCardResponseLayout,
1237
+ responseCardTemplate: card,
1238
+ previewCardTemplate: inferPreviewCardTemplate(card),
1239
+ };
1240
+ return result;
1241
+ }
1242
+ function wrapResponseSemantics(card, jsonPath) {
1243
+ const props = inferProperties(card);
1244
+ const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
1245
+ const result = {
1246
+ data_path: dataPath,
1247
+ };
1248
+ if (props.title || props.subtitle || props.imageUrl) {
1249
+ result.properties = {};
1250
+ if (props.title) {
1251
+ result.properties.title = "$." + props.title;
1252
+ }
1253
+ if (props.subtitle) {
1254
+ result.properties.subtitle = "$." + props.subtitle;
1255
+ }
1256
+ if (props.imageUrl) {
1257
+ result.properties.url = "$." + props.imageUrl;
1258
+ }
1259
+ }
1260
+ result.static_template = card;
1261
+ return result;
1262
+ }
1263
+ /**
1264
+ * Infers the preview card template from an Adaptive Card and a JSON path.
1265
+ * The preview card template includes a title and an optional subtitle and image.
1266
+ * It populates the preview card template with the first text block that matches
1267
+ * each well-known name, in the order of title, subtitle, and image.
1268
+ * If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
1269
+ * If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
1270
+ * @param card The Adaptive Card to infer the preview card template from.
1271
+ * @param jsonPath The JSON path to the root object in the card body.
1272
+ * @returns The inferred preview card template.
1273
+ */
1274
+ function inferPreviewCardTemplate(card) {
1275
+ const result = {
1276
+ title: "result",
1277
+ };
1278
+ const inferredProperties = inferProperties(card);
1279
+ if (inferredProperties.title) {
1280
+ result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
1281
+ }
1282
+ if (inferredProperties.subtitle) {
1283
+ result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
1284
+ }
1285
+ if (inferredProperties.imageUrl) {
1286
+ result.image = {
1287
+ url: `\${${inferredProperties.imageUrl}}`,
1288
+ alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
1289
+ $when: `\${${inferredProperties.imageUrl} != null}`,
1290
+ };
1291
+ }
1292
+ return result;
1293
+ }
1294
+ function inferProperties(card) {
1295
+ var _a;
1296
+ const result = {};
1297
+ const nameSet = new Set();
1298
+ let rootObject;
1299
+ if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
1300
+ rootObject = card.body[0].items;
1301
+ }
1302
+ else {
1303
+ rootObject = card.body;
1304
+ }
1305
+ for (const element of rootObject) {
1306
+ if (element.type === ConstantString.TextBlockType) {
1307
+ const textElement = element;
1308
+ const index = textElement.text.indexOf("${if(");
1309
+ if (index > 0) {
1310
+ const text = textElement.text.substring(index);
1311
+ const match = text.match(/\${if\(([^,]+),/);
1312
+ const property = match ? match[1] : "";
1313
+ if (property) {
1314
+ nameSet.add(property);
1315
+ }
1316
+ }
1317
+ }
1318
+ else if (element.type === ConstantString.ImageType) {
1319
+ const imageElement = element;
1320
+ const match = imageElement.url.match(/\${([^,]+)}/);
1321
+ const property = match ? match[1] : "";
1322
+ if (property) {
1323
+ nameSet.add(property);
1324
+ }
1325
+ }
1326
+ }
1327
+ for (const name of nameSet) {
1328
+ if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
1329
+ result.title = name;
1330
+ nameSet.delete(name);
1331
+ }
1332
+ else if (!result.subtitle &&
1333
+ Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
1334
+ result.subtitle = name;
1335
+ nameSet.delete(name);
1336
+ }
1337
+ else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
1338
+ result.imageUrl = name;
1339
+ nameSet.delete(name);
1340
+ }
1341
+ }
1342
+ for (const name of nameSet) {
1343
+ if (!result.title) {
1344
+ result.title = name;
1345
+ nameSet.delete(name);
1346
+ }
1347
+ else if (!result.subtitle) {
1348
+ result.subtitle = name;
1349
+ nameSet.delete(name);
1350
+ }
1351
+ }
1352
+ if (!result.title && result.subtitle) {
1353
+ result.title = result.subtitle;
1354
+ delete result.subtitle;
1355
+ }
1356
+ return result;
1357
+ }
1358
+
1359
+ // Copyright (c) Microsoft Corporation.
1360
+ class ManifestUpdater {
1361
+ static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
1362
+ const manifest = await fs.readJSON(manifestPath);
1363
+ const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
1364
+ // Insert plugins in manifest.json if it is plugin for Copilot.
1365
+ if (!options.isGptPlugin) {
1366
+ manifest.plugins = [
1367
+ {
1368
+ file: apiPluginRelativePath,
1369
+ id: ConstantString.DefaultPluginId,
1370
+ },
1371
+ ];
1372
+ ManifestUpdater.updateManifestDescription(manifest, spec);
1373
+ }
1374
+ const appName = this.removeEnvs(manifest.name.short);
1375
+ const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
1376
+ const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
839
1377
  return [manifest, apiPlugin];
840
1378
  }
841
1379
  static updateManifestDescription(manifest, spec) {
@@ -847,23 +1385,56 @@ class ManifestUpdater {
847
1385
  }
848
1386
  static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
849
1387
  let parameter;
850
- if (schema.type === "string" ||
1388
+ if (schema.type === "array") {
1389
+ const items = schema.items;
1390
+ parameter = {
1391
+ type: "array",
1392
+ items: ManifestUpdater.mapOpenAPISchemaToFuncParam(items, method, pathUrl),
1393
+ };
1394
+ }
1395
+ else if (schema.type === "string" ||
851
1396
  schema.type === "boolean" ||
852
1397
  schema.type === "integer" ||
853
- schema.type === "number" ||
854
- schema.type === "array") {
855
- parameter = schema;
1398
+ schema.type === "number") {
1399
+ parameter = {
1400
+ type: schema.type,
1401
+ };
856
1402
  }
857
1403
  else {
858
1404
  throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
859
1405
  }
1406
+ if (schema.enum) {
1407
+ parameter.enum = schema.enum;
1408
+ }
1409
+ if (schema.description) {
1410
+ parameter.description = schema.description;
1411
+ }
1412
+ if (schema.default) {
1413
+ parameter.default = schema.default;
1414
+ }
860
1415
  return parameter;
861
1416
  }
862
- static generatePluginManifestSchema(spec, specRelativePath, options) {
863
- var _a, _b, _c;
1417
+ static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
1418
+ var _a, _b, _c, _d, _e;
864
1419
  const functions = [];
865
1420
  const functionNames = [];
1421
+ const conversationStarters = [];
866
1422
  const paths = spec.paths;
1423
+ const pluginAuthObj = {
1424
+ type: "None",
1425
+ };
1426
+ if (authInfo) {
1427
+ if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
1428
+ pluginAuthObj.type = "OAuthPluginVault";
1429
+ }
1430
+ else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
1431
+ pluginAuthObj.type = "ApiKeyPluginVault";
1432
+ }
1433
+ if (pluginAuthObj.type !== "None") {
1434
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
1435
+ pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
1436
+ }
1437
+ }
867
1438
  for (const pathUrl in paths) {
868
1439
  const pathItem = paths[pathUrl];
869
1440
  if (pathItem) {
@@ -871,6 +1442,7 @@ class ManifestUpdater {
871
1442
  for (const method in operations) {
872
1443
  if (options.allowMethods.includes(method)) {
873
1444
  const operationItem = operations[method];
1445
+ const confirmationBodies = [];
874
1446
  if (operationItem) {
875
1447
  const operationId = operationItem.operationId;
876
1448
  const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
@@ -886,6 +1458,7 @@ class ManifestUpdater {
886
1458
  const param = paramObject[i];
887
1459
  const schema = param.schema;
888
1460
  parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
1461
+ confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
889
1462
  if (param.required) {
890
1463
  parameters.required.push(param.name);
891
1464
  }
@@ -904,6 +1477,7 @@ class ManifestUpdater {
904
1477
  for (const property in requestBodySchema.properties) {
905
1478
  const schema = requestBodySchema.properties[property];
906
1479
  parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
1480
+ confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
907
1481
  }
908
1482
  }
909
1483
  else {
@@ -913,33 +1487,108 @@ class ManifestUpdater {
913
1487
  const funcObj = {
914
1488
  name: operationId,
915
1489
  description: description,
916
- parameters: parameters,
917
1490
  };
1491
+ if (paramObject || requestBody) {
1492
+ funcObj.parameters = parameters;
1493
+ }
1494
+ if (options.allowResponseSemantics) {
1495
+ const { json } = Utils.getResponseJson(operationItem);
1496
+ if (json.schema) {
1497
+ const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
1498
+ const responseSemantic = wrapResponseSemantics(card, jsonPath);
1499
+ funcObj.capabilities = {
1500
+ response_semantics: responseSemantic,
1501
+ };
1502
+ }
1503
+ }
1504
+ if (options.allowConfirmation && method !== ConstantString.GetMethod) {
1505
+ if (!funcObj.capabilities) {
1506
+ funcObj.capabilities = {};
1507
+ }
1508
+ funcObj.capabilities.confirmation = {
1509
+ type: "AdaptiveCard",
1510
+ title: (_c = operationItem.summary) !== null && _c !== void 0 ? _c : description,
1511
+ };
1512
+ if (confirmationBodies.length > 0) {
1513
+ funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
1514
+ }
1515
+ }
918
1516
  functions.push(funcObj);
919
1517
  functionNames.push(operationId);
1518
+ if (description) {
1519
+ conversationStarters.push(description);
1520
+ }
920
1521
  }
921
1522
  }
922
1523
  }
923
1524
  }
924
1525
  }
925
- const apiPlugin = {
926
- schema_version: "v2",
927
- name_for_human: spec.info.title,
928
- description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
929
- functions: functions,
930
- runtimes: [
931
- {
932
- type: "OpenApi",
933
- auth: {
934
- type: "none", // TODO, support auth in the future
935
- },
936
- spec: {
937
- url: specRelativePath,
938
- },
939
- run_for_functions: functionNames,
1526
+ let apiPlugin;
1527
+ if (await fs.pathExists(apiPluginFilePath)) {
1528
+ apiPlugin = await fs.readJSON(apiPluginFilePath);
1529
+ }
1530
+ else {
1531
+ apiPlugin = {
1532
+ schema_version: "v2.1",
1533
+ name_for_human: "",
1534
+ description_for_human: "",
1535
+ namespace: "",
1536
+ functions: [],
1537
+ runtimes: [],
1538
+ };
1539
+ }
1540
+ apiPlugin.functions = apiPlugin.functions || [];
1541
+ for (const func of functions) {
1542
+ const index = (_d = apiPlugin.functions) === null || _d === void 0 ? void 0 : _d.findIndex((f) => f.name === func.name);
1543
+ if (index === -1) {
1544
+ apiPlugin.functions.push(func);
1545
+ }
1546
+ else {
1547
+ apiPlugin.functions[index] = func;
1548
+ }
1549
+ }
1550
+ apiPlugin.runtimes = apiPlugin.runtimes || [];
1551
+ const index = apiPlugin.runtimes.findIndex((runtime) => {
1552
+ var _a, _b;
1553
+ return runtime.spec.url === specRelativePath &&
1554
+ runtime.type === "OpenApi" &&
1555
+ ((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
1556
+ });
1557
+ if (index === -1) {
1558
+ apiPlugin.runtimes.push({
1559
+ type: "OpenApi",
1560
+ auth: pluginAuthObj,
1561
+ spec: {
1562
+ url: specRelativePath,
940
1563
  },
941
- ],
942
- };
1564
+ run_for_functions: functionNames,
1565
+ });
1566
+ }
1567
+ else {
1568
+ apiPlugin.runtimes[index].run_for_functions = functionNames;
1569
+ }
1570
+ if (!apiPlugin.name_for_human) {
1571
+ apiPlugin.name_for_human = appName;
1572
+ }
1573
+ if (!apiPlugin.namespace) {
1574
+ apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
1575
+ }
1576
+ if (!apiPlugin.description_for_human) {
1577
+ apiPlugin.description_for_human =
1578
+ (_e = spec.info.description) !== null && _e !== void 0 ? _e : "<Please add description of the plugin>";
1579
+ }
1580
+ if (options.allowConversationStarters && conversationStarters.length > 0) {
1581
+ if (!apiPlugin.capabilities) {
1582
+ apiPlugin.capabilities = {
1583
+ localization: {},
1584
+ };
1585
+ }
1586
+ if (!apiPlugin.capabilities.conversation_starters) {
1587
+ apiPlugin.capabilities.conversation_starters = conversationStarters
1588
+ .slice(0, 5)
1589
+ .map((text) => ({ text }));
1590
+ }
1591
+ }
943
1592
  return apiPlugin;
944
1593
  }
945
1594
  static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
@@ -959,21 +1608,21 @@ class ManifestUpdater {
959
1608
  };
960
1609
  if (authInfo) {
961
1610
  const auth = authInfo.authScheme;
1611
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
962
1612
  if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
963
1613
  const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
964
1614
  composeExtension.authorization = {
965
1615
  authType: "apiSecretServiceAuth",
966
1616
  apiSecretServiceAuthConfiguration: {
967
- apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
1617
+ apiSecretRegistrationId: `\${{${safeRegistrationIdName}}}`,
968
1618
  },
969
1619
  };
970
1620
  }
971
1621
  else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
972
- const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
973
1622
  composeExtension.authorization = {
974
1623
  authType: "oAuth2.0",
975
1624
  oAuthConfiguration: {
976
- oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
1625
+ oauthConfigurationId: `\${{${safeRegistrationIdName}}}`,
977
1626
  },
978
1627
  };
979
1628
  updatedPart.webApplicationInfo = {
@@ -1008,16 +1657,26 @@ class ManifestUpdater {
1008
1657
  if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
1009
1658
  const operationItem = operations[method];
1010
1659
  if (operationItem) {
1011
- const [command, warning] = Utils.parseApiInfo(operationItem, options);
1660
+ const command = Utils.parseApiInfo(operationItem, options);
1661
+ if (command.parameters &&
1662
+ command.parameters.length >= 1 &&
1663
+ command.parameters.some((param) => param.isRequired)) {
1664
+ command.parameters = command.parameters.filter((param) => param.isRequired);
1665
+ }
1666
+ else if (command.parameters && command.parameters.length > 0) {
1667
+ command.parameters = [command.parameters[0]];
1668
+ warnings.push({
1669
+ type: WarningType.OperationOnlyContainsOptionalParam,
1670
+ content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
1671
+ data: command.id,
1672
+ });
1673
+ }
1012
1674
  if (adaptiveCardFolder) {
1013
1675
  const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
1014
1676
  command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
1015
1677
  ? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
1016
1678
  : "";
1017
1679
  }
1018
- if (warning) {
1019
- warnings.push(warning);
1020
- }
1021
1680
  commands.push(command);
1022
1681
  }
1023
1682
  }
@@ -1031,255 +1690,21 @@ class ManifestUpdater {
1031
1690
  const relativePath = path.relative(path.dirname(from), to);
1032
1691
  return path.normalize(relativePath).replace(/\\/g, "/");
1033
1692
  }
1034
- }
1035
-
1036
- // Copyright (c) Microsoft Corporation.
1037
- class AdaptiveCardGenerator {
1038
- static generateAdaptiveCard(operationItem) {
1039
- try {
1040
- const json = Utils.getResponseJson(operationItem);
1041
- let cardBody = [];
1042
- let schema = json.schema;
1043
- let jsonPath = "$";
1044
- if (schema && Object.keys(schema).length > 0) {
1045
- jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
1046
- if (jsonPath !== "$") {
1047
- schema = schema.properties[jsonPath];
1048
- }
1049
- cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
1050
- }
1051
- // if no schema, try to use example value
1052
- if (cardBody.length === 0 && (json.examples || json.example)) {
1053
- cardBody = [
1054
- {
1055
- type: ConstantString.TextBlockType,
1056
- text: "${jsonStringify($root)}",
1057
- wrap: true,
1058
- },
1059
- ];
1060
- }
1061
- // if no example value, use default success response
1062
- if (cardBody.length === 0) {
1063
- cardBody = [
1064
- {
1065
- type: ConstantString.TextBlockType,
1066
- text: "success",
1067
- wrap: true,
1068
- },
1069
- ];
1070
- }
1071
- const fullCard = {
1072
- type: ConstantString.AdaptiveCardType,
1073
- $schema: ConstantString.AdaptiveCardSchema,
1074
- version: ConstantString.AdaptiveCardVersion,
1075
- body: cardBody,
1076
- };
1077
- return [fullCard, jsonPath];
1078
- }
1079
- catch (err) {
1080
- throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
1081
- }
1082
- }
1083
- static generateCardFromResponse(schema, name, parentArrayName = "") {
1084
- if (schema.type === "array") {
1085
- // schema.items can be arbitrary object: schema { type: array, items: {} }
1086
- if (Object.keys(schema.items).length === 0) {
1087
- return [
1088
- {
1089
- type: ConstantString.TextBlockType,
1090
- text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
1091
- wrap: true,
1092
- },
1093
- ];
1094
- }
1095
- const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
1096
- const template = {
1097
- type: ConstantString.ContainerType,
1098
- $data: name ? `\${${name}}` : "${$root}",
1099
- items: Array(),
1100
- };
1101
- template.items.push(...obj);
1102
- return [template];
1103
- }
1104
- // some schema may not contain type but contain properties
1105
- if (schema.type === "object" || (!schema.type && schema.properties)) {
1106
- const { properties } = schema;
1107
- const result = [];
1108
- for (const property in properties) {
1109
- const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
1110
- result.push(...obj);
1111
- }
1112
- if (schema.additionalProperties) {
1113
- // TODO: better ways to handler warnings.
1114
- console.warn(ConstantString.AdditionalPropertiesNotSupported);
1115
- }
1116
- return result;
1117
- }
1118
- if (schema.type === "string" ||
1119
- schema.type === "integer" ||
1120
- schema.type === "boolean" ||
1121
- schema.type === "number") {
1122
- if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
1123
- // string in root: "ddd"
1124
- let text = "result: ${$root}";
1125
- if (name) {
1126
- // object { id: "1" }
1127
- text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
1128
- if (parentArrayName) {
1129
- // object types inside array: { tags: ["id": 1, "name": "name"] }
1130
- text = `${parentArrayName}.${text}`;
1131
- }
1132
- }
1133
- else if (parentArrayName) {
1134
- // string array: photoUrls: ["1", "2"]
1135
- text = `${parentArrayName}: ` + "${$data}";
1136
- }
1137
- return [
1138
- {
1139
- type: ConstantString.TextBlockType,
1140
- text,
1141
- wrap: true,
1142
- },
1143
- ];
1144
- }
1145
- else {
1146
- if (name) {
1147
- return [
1148
- {
1149
- type: "Image",
1150
- url: `\${${name}}`,
1151
- $when: `\${${name} != null}`,
1152
- },
1153
- ];
1154
- }
1155
- else {
1156
- return [
1157
- {
1158
- type: "Image",
1159
- url: "${$data}",
1160
- $when: "${$data != null}",
1161
- },
1162
- ];
1163
- }
1164
- }
1165
- }
1166
- if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
1167
- throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
1168
- }
1169
- throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
1170
- }
1171
- // Find the first array property in the response schema object with the well-known name
1172
- static getResponseJsonPathFromSchema(schema) {
1173
- if (schema.type === "object" || (!schema.type && schema.properties)) {
1174
- const { properties } = schema;
1175
- for (const property in properties) {
1176
- const schema = properties[property];
1177
- if (schema.type === "array" &&
1178
- Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
1179
- return property;
1180
- }
1181
- }
1182
- }
1183
- return "$";
1184
- }
1185
- static isImageUrlProperty(schema, name, parentArrayName) {
1186
- const propertyName = name ? name : parentArrayName;
1187
- return (!!propertyName &&
1188
- schema.type === "string" &&
1189
- Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
1190
- (propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
1191
- }
1192
- }
1193
-
1194
- // Copyright (c) Microsoft Corporation.
1195
- function wrapAdaptiveCard(card, jsonPath) {
1196
- const result = {
1197
- version: ConstantString.WrappedCardVersion,
1198
- $schema: ConstantString.WrappedCardSchema,
1199
- jsonPath: jsonPath,
1200
- responseLayout: ConstantString.WrappedCardResponseLayout,
1201
- responseCardTemplate: card,
1202
- previewCardTemplate: inferPreviewCardTemplate(card),
1203
- };
1204
- return result;
1205
- }
1206
- /**
1207
- * Infers the preview card template from an Adaptive Card and a JSON path.
1208
- * The preview card template includes a title and an optional subtitle and image.
1209
- * It populates the preview card template with the first text block that matches
1210
- * each well-known name, in the order of title, subtitle, and image.
1211
- * If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
1212
- * If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
1213
- * @param card The Adaptive Card to infer the preview card template from.
1214
- * @param jsonPath The JSON path to the root object in the card body.
1215
- * @returns The inferred preview card template.
1216
- */
1217
- function inferPreviewCardTemplate(card) {
1218
- var _a;
1219
- const result = {
1220
- title: "",
1221
- };
1222
- const textBlockElements = new Set();
1223
- let rootObject;
1224
- if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
1225
- rootObject = card.body[0].items;
1226
- }
1227
- else {
1228
- rootObject = card.body;
1229
- }
1230
- for (const element of rootObject) {
1231
- if (element.type === ConstantString.TextBlockType) {
1232
- const textElement = element;
1233
- const index = textElement.text.indexOf("${if(");
1234
- if (index > 0) {
1235
- textElement.text = textElement.text.substring(index);
1236
- textBlockElements.add(textElement);
1237
- }
1238
- }
1239
- }
1240
- for (const element of textBlockElements) {
1241
- const text = element.text;
1242
- if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
1243
- result.title = text;
1244
- textBlockElements.delete(element);
1245
- }
1246
- else if (!result.subtitle &&
1247
- Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
1248
- result.subtitle = text;
1249
- textBlockElements.delete(element);
1250
- }
1251
- else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
1252
- const match = text.match(/\${if\(([^,]+),/);
1253
- const property = match ? match[1] : "";
1254
- if (property) {
1255
- result.image = {
1256
- url: `\${${property}}`,
1257
- alt: text,
1258
- $when: `\${${property} != null}`,
1259
- };
1260
- }
1261
- textBlockElements.delete(element);
1262
- }
1263
- }
1264
- for (const element of textBlockElements) {
1265
- const text = element.text;
1266
- if (!result.title) {
1267
- result.title = text;
1268
- textBlockElements.delete(element);
1269
- }
1270
- else if (!result.subtitle) {
1271
- result.subtitle = text;
1272
- textBlockElements.delete(element);
1693
+ static removeEnvs(str) {
1694
+ const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
1695
+ const matches = placeHolderReg.exec(str);
1696
+ let newStr = str;
1697
+ if (matches != null) {
1698
+ newStr = newStr.replace(matches[0], "");
1273
1699
  }
1700
+ return newStr;
1274
1701
  }
1275
- if (!result.title && result.subtitle) {
1276
- result.title = result.subtitle;
1277
- delete result.subtitle;
1702
+ static removeAllSpecialCharacters(str) {
1703
+ return str.toLowerCase().replace(/[^a-z0-9]/g, "");
1278
1704
  }
1279
- if (!result.title) {
1280
- result.title = "result";
1705
+ static getConfirmationBodyItem(paramName) {
1706
+ return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
1281
1707
  }
1282
- return result;
1283
1708
  }
1284
1709
 
1285
1710
  // Copyright (c) Microsoft Corporation.
@@ -1301,7 +1726,11 @@ class SpecParser {
1301
1726
  allowMultipleParameters: false,
1302
1727
  allowOauth2: false,
1303
1728
  allowMethods: ["get", "post"],
1729
+ allowConversationStarters: false,
1730
+ allowResponseSemantics: false,
1731
+ allowConfirmation: false,
1304
1732
  projectType: ProjectType.SME,
1733
+ isGptPlugin: false,
1305
1734
  };
1306
1735
  this.pathOrSpec = pathOrDoc;
1307
1736
  this.parser = new SwaggerParser();
@@ -1325,6 +1754,8 @@ class SpecParser {
1325
1754
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
1326
1755
  };
1327
1756
  }
1757
+ const errors = [];
1758
+ const warnings = [];
1328
1759
  if (!this.options.allowSwagger && this.isSwaggerFile) {
1329
1760
  return {
1330
1761
  status: ValidationStatus.Error,
@@ -1334,23 +1765,38 @@ class SpecParser {
1334
1765
  ],
1335
1766
  };
1336
1767
  }
1337
- if (this.options.projectType === ProjectType.SME ||
1338
- this.options.projectType === ProjectType.Copilot) {
1339
- if (this.spec.openapi >= "3.1.0") {
1340
- return {
1341
- status: ValidationStatus.Error,
1342
- warnings: [],
1343
- errors: [
1344
- {
1345
- type: ErrorType.SpecVersionNotSupported,
1346
- content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
1347
- data: this.spec.openapi,
1348
- },
1349
- ],
1350
- };
1351
- }
1768
+ // Remote reference not supported
1769
+ const refPaths = this.parser.$refs.paths();
1770
+ // refPaths [0] is the current spec file path
1771
+ if (refPaths.length > 1) {
1772
+ errors.push({
1773
+ type: ErrorType.RemoteRefNotSupported,
1774
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1775
+ data: refPaths,
1776
+ });
1777
+ }
1778
+ if (!!this.isSwaggerFile && this.options.allowSwagger) {
1779
+ warnings.push({
1780
+ type: WarningType.ConvertSwaggerToOpenAPI,
1781
+ content: ConstantString.ConvertSwaggerToOpenAPI,
1782
+ });
1352
1783
  }
1353
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1784
+ const validator = this.getValidator(this.spec);
1785
+ const validationResult = validator.validateSpec();
1786
+ warnings.push(...validationResult.warnings);
1787
+ errors.push(...validationResult.errors);
1788
+ let status = ValidationStatus.Valid;
1789
+ if (warnings.length > 0 && errors.length === 0) {
1790
+ status = ValidationStatus.Warning;
1791
+ }
1792
+ else if (errors.length > 0) {
1793
+ status = ValidationStatus.Error;
1794
+ }
1795
+ return {
1796
+ status: status,
1797
+ warnings: warnings,
1798
+ errors: errors,
1799
+ };
1354
1800
  }
1355
1801
  catch (err) {
1356
1802
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -1370,45 +1816,40 @@ class SpecParser {
1370
1816
  try {
1371
1817
  await this.loadSpec();
1372
1818
  const spec = this.spec;
1373
- const apiMap = this.getAllSupportedAPIs(spec);
1819
+ const apiMap = this.getAPIs(spec);
1374
1820
  const result = {
1375
- validAPIs: [],
1821
+ APIs: [],
1376
1822
  allAPICount: 0,
1377
1823
  validAPICount: 0,
1378
1824
  };
1379
1825
  for (const apiKey in apiMap) {
1826
+ const { operation, isValid, reason } = apiMap[apiKey];
1827
+ const [method, path] = apiKey.split(" ");
1828
+ const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1380
1829
  const apiResult = {
1381
- api: "",
1830
+ api: apiKey,
1382
1831
  server: "",
1383
- operationId: "",
1832
+ operationId: operationId,
1833
+ isValid: isValid,
1834
+ reason: reason,
1384
1835
  };
1385
- const [method, path] = apiKey.split(" ");
1386
- const operation = apiMap[apiKey];
1387
- const rootServer = spec.servers && spec.servers[0];
1388
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
1389
- const operationServer = operation.servers && operation.servers[0];
1390
- const serverUrl = operationServer || methodServer || rootServer;
1391
- if (!serverUrl) {
1392
- throw new SpecParserError(ConstantString.NoServerInformation, ErrorType.NoServerInformation);
1393
- }
1394
- apiResult.server = Utils.resolveServerUrl(serverUrl.url);
1395
- let operationId = operation.operationId;
1396
- if (!operationId) {
1397
- operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1398
- }
1399
- apiResult.operationId = operationId;
1400
- const authArray = Utils.getAuthArray(operation.security, spec);
1401
- for (const auths of authArray) {
1402
- if (auths.length === 1) {
1403
- apiResult.auth = auths[0];
1404
- break;
1836
+ if (isValid) {
1837
+ const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
1838
+ if (serverObj) {
1839
+ apiResult.server = Utils.resolveEnv(serverObj.url);
1840
+ }
1841
+ const authArray = Utils.getAuthArray(operation.security, spec);
1842
+ for (const auths of authArray) {
1843
+ if (auths.length === 1) {
1844
+ apiResult.auth = auths[0];
1845
+ break;
1846
+ }
1405
1847
  }
1406
1848
  }
1407
- apiResult.api = apiKey;
1408
- result.validAPIs.push(apiResult);
1849
+ result.APIs.push(apiResult);
1409
1850
  }
1410
- result.allAPICount = Utils.getAllAPICount(spec);
1411
- result.validAPICount = result.validAPIs.length;
1851
+ result.allAPICount = result.APIs.length;
1852
+ result.validAPICount = result.APIs.filter((api) => api.isValid).length;
1412
1853
  return result;
1413
1854
  }
1414
1855
  catch (err) {
@@ -1461,18 +1902,12 @@ class SpecParser {
1461
1902
  const newSpecs = await this.getFilteredSpecs(filter, signal);
1462
1903
  const newUnResolvedSpec = newSpecs[0];
1463
1904
  const newSpec = newSpecs[1];
1464
- let resultStr;
1465
- if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1466
- resultStr = jsyaml.dump(newUnResolvedSpec);
1467
- }
1468
- else {
1469
- resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1470
- }
1471
- await fs.outputFile(outputSpecPath, resultStr);
1905
+ const authInfo = Utils.getAuthInfo(newSpec);
1906
+ await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
1472
1907
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1473
1908
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1474
1909
  }
1475
- const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
1910
+ const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
1476
1911
  await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1477
1912
  await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
1478
1913
  }
@@ -1500,32 +1935,11 @@ class SpecParser {
1500
1935
  const newSpecs = await this.getFilteredSpecs(filter, signal);
1501
1936
  const newUnResolvedSpec = newSpecs[0];
1502
1937
  const newSpec = newSpecs[1];
1503
- const authSet = new Set();
1504
- let hasMultipleAuth = false;
1505
- for (const url in newSpec.paths) {
1506
- for (const method in newSpec.paths[url]) {
1507
- const operation = newSpec.paths[url][method];
1508
- const authArray = Utils.getAuthArray(operation.security, newSpec);
1509
- if (authArray && authArray.length > 0) {
1510
- authSet.add(authArray[0][0]);
1511
- if (authSet.size > 1) {
1512
- hasMultipleAuth = true;
1513
- break;
1514
- }
1515
- }
1516
- }
1517
- }
1518
- if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
1519
- throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
1520
- }
1521
- let resultStr;
1522
- if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1523
- resultStr = jsyaml.dump(newUnResolvedSpec);
1524
- }
1525
- else {
1526
- resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1938
+ let authInfo = undefined;
1939
+ if (this.options.projectType === ProjectType.SME) {
1940
+ authInfo = Utils.getAuthInfo(newSpec);
1527
1941
  }
1528
- await fs.outputFile(outputSpecPath, resultStr);
1942
+ await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
1529
1943
  if (adaptiveCardFolder) {
1530
1944
  for (const url in newSpec.paths) {
1531
1945
  for (const method in newSpec.paths[url]) {
@@ -1555,7 +1969,6 @@ class SpecParser {
1555
1969
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1556
1970
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1557
1971
  }
1558
- const authInfo = Array.from(authSet)[0];
1559
1972
  const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
1560
1973
  await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1561
1974
  result.warnings.push(...warnings);
@@ -1581,13 +1994,28 @@ class SpecParser {
1581
1994
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
1582
1995
  }
1583
1996
  }
1584
- getAllSupportedAPIs(spec) {
1585
- if (this.apiMap !== undefined) {
1586
- return this.apiMap;
1997
+ getAPIs(spec) {
1998
+ const validator = this.getValidator(spec);
1999
+ const apiMap = validator.listAPIs();
2000
+ return apiMap;
2001
+ }
2002
+ getValidator(spec) {
2003
+ if (this.validator) {
2004
+ return this.validator;
1587
2005
  }
1588
- const result = Utils.listSupportedAPIs(spec, this.options);
1589
- this.apiMap = result;
1590
- return result;
2006
+ const validator = ValidatorFactory.create(spec, this.options);
2007
+ this.validator = validator;
2008
+ return validator;
2009
+ }
2010
+ async saveFilterSpec(outputSpecPath, unResolvedSpec) {
2011
+ let resultStr;
2012
+ if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
2013
+ resultStr = jsyaml.dump(unResolvedSpec);
2014
+ }
2015
+ else {
2016
+ resultStr = JSON.stringify(unResolvedSpec, null, 2);
2017
+ }
2018
+ await fs.outputFile(outputSpecPath, resultStr);
1591
2019
  }
1592
2020
  }
1593
2021