@microsoft/m365-spec-parser 0.1.1-alpha.cf377d39f.0 → 0.1.1-alpha.fc0606a28.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.
@@ -112,6 +112,7 @@ ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multi
112
112
  ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
113
113
  ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
114
114
  ConstantString.MultipleAPIKeyNotSupported = "Multiple API keys are not supported. Please make sure that all selected APIs use the same API key.";
115
+ ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
115
116
  ConstantString.WrappedCardVersion = "devPreview";
116
117
  ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
117
118
  ConstantString.WrappedCardResponseLayout = "list";
@@ -194,7 +195,18 @@ class SpecParserError extends Error {
194
195
 
195
196
  // Copyright (c) Microsoft Corporation.
196
197
  class Utils {
197
- static checkParameters(paramObject) {
198
+ static hasNestedObjectInSchema(schema) {
199
+ if (schema.type === "object") {
200
+ for (const property in schema.properties) {
201
+ const nestedSchema = schema.properties[property];
202
+ if (nestedSchema.type === "object") {
203
+ return true;
204
+ }
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+ static checkParameters(paramObject, isCopilot) {
198
210
  const paramResult = {
199
211
  requiredNum: 0,
200
212
  optionalNum: 0,
@@ -206,7 +218,20 @@ class Utils {
206
218
  for (let i = 0; i < paramObject.length; i++) {
207
219
  const param = paramObject[i];
208
220
  const schema = param.schema;
221
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
222
+ paramResult.isValid = false;
223
+ continue;
224
+ }
209
225
  const isRequiredWithoutDefault = param.required && schema.default === undefined;
226
+ if (isCopilot) {
227
+ if (isRequiredWithoutDefault) {
228
+ paramResult.requiredNum = paramResult.requiredNum + 1;
229
+ }
230
+ else {
231
+ paramResult.optionalNum = paramResult.optionalNum + 1;
232
+ }
233
+ continue;
234
+ }
210
235
  if (param.in === "header" || param.in === "cookie") {
211
236
  if (isRequiredWithoutDefault) {
212
237
  paramResult.isValid = false;
@@ -233,7 +258,7 @@ class Utils {
233
258
  }
234
259
  return paramResult;
235
260
  }
236
- static checkPostBody(schema, isRequired = false) {
261
+ static checkPostBody(schema, isRequired = false, isCopilot = false) {
237
262
  var _a;
238
263
  const paramResult = {
239
264
  requiredNum: 0,
@@ -244,6 +269,10 @@ class Utils {
244
269
  return paramResult;
245
270
  }
246
271
  const isRequiredWithoutDefault = isRequired && schema.default === undefined;
272
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
273
+ paramResult.isValid = false;
274
+ return paramResult;
275
+ }
247
276
  if (schema.type === "string" ||
248
277
  schema.type === "integer" ||
249
278
  schema.type === "boolean" ||
@@ -262,14 +291,14 @@ class Utils {
262
291
  if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
263
292
  isRequired = true;
264
293
  }
265
- const result = Utils.checkPostBody(properties[property], isRequired);
294
+ const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
266
295
  paramResult.requiredNum += result.requiredNum;
267
296
  paramResult.optionalNum += result.optionalNum;
268
297
  paramResult.isValid = paramResult.isValid && result.isValid;
269
298
  }
270
299
  }
271
300
  else {
272
- if (isRequiredWithoutDefault) {
301
+ if (isRequiredWithoutDefault && !isCopilot) {
273
302
  paramResult.isValid = false;
274
303
  }
275
304
  }
@@ -289,7 +318,7 @@ class Utils {
289
318
  * 5. response body should be “application/json” and not empty, and response code should be 20X
290
319
  * 6. only support request body with “application/json” content type
291
320
  */
292
- static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
321
+ static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
293
322
  const pathObj = spec.paths[path];
294
323
  method = method.toLocaleLowerCase();
295
324
  if (pathObj) {
@@ -322,15 +351,22 @@ class Utils {
322
351
  };
323
352
  if (requestJsonBody) {
324
353
  const requestBodySchema = requestJsonBody.schema;
325
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
354
+ if (isCopilot && requestBodySchema.type !== "object") {
355
+ return false;
356
+ }
357
+ requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
326
358
  }
327
359
  if (!requestBodyParamResult.isValid) {
328
360
  return false;
329
361
  }
330
- const paramResult = Utils.checkParameters(paramObject);
362
+ const paramResult = Utils.checkParameters(paramObject, isCopilot);
331
363
  if (!paramResult.isValid) {
332
364
  return false;
333
365
  }
366
+ // Copilot support arbitrary parameters
367
+ if (isCopilot) {
368
+ return true;
369
+ }
334
370
  if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
335
371
  if (allowMultipleParameters &&
336
372
  requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
@@ -501,7 +537,7 @@ class Utils {
501
537
  }
502
538
  return errors;
503
539
  }
504
- static validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
540
+ static validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
505
541
  const errors = [];
506
542
  let hasTopLevelServers = false;
507
543
  let hasPathLevelServers = false;
@@ -522,7 +558,7 @@ class Utils {
522
558
  }
523
559
  for (const method in methods) {
524
560
  const operationObject = methods[method];
525
- if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
561
+ if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
526
562
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
527
563
  hasOperationLevelServers = true;
528
564
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -672,14 +708,14 @@ class Utils {
672
708
  }
673
709
  return [command, warning];
674
710
  }
675
- static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
711
+ static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
676
712
  const paths = spec.paths;
677
713
  const result = {};
678
714
  for (const path in paths) {
679
715
  const methods = paths[path];
680
716
  for (const method in methods) {
681
717
  // For developer preview, only support GET operation with only 1 parameter without auth
682
- if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
718
+ if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
683
719
  const operationObject = methods[method];
684
720
  result[`${method.toUpperCase()} ${path}`] = operationObject;
685
721
  }
@@ -687,7 +723,7 @@ class Utils {
687
723
  }
688
724
  return result;
689
725
  }
690
- static validateSpec(spec, parser, isSwaggerFile, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
726
+ static validateSpec(spec, parser, isSwaggerFile, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
691
727
  const errors = [];
692
728
  const warnings = [];
693
729
  if (isSwaggerFile) {
@@ -697,7 +733,7 @@ class Utils {
697
733
  });
698
734
  }
699
735
  // Server validation
700
- const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
736
+ const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot);
701
737
  errors.push(...serverErrors);
702
738
  // Remote reference not supported
703
739
  const refPaths = parser.$refs.paths();
@@ -710,7 +746,7 @@ class Utils {
710
746
  });
711
747
  }
712
748
  // No supported API
713
- const apiMap = Utils.listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
749
+ const apiMap = Utils.listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot);
714
750
  if (Object.keys(apiMap).length === 0) {
715
751
  errors.push({
716
752
  type: exports.ErrorType.NoSupportedApi,
@@ -766,14 +802,14 @@ class Utils {
766
802
 
767
803
  // Copyright (c) Microsoft Corporation.
768
804
  class SpecFilter {
769
- static specFilter(filter, unResolveSpec, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
805
+ static specFilter(filter, unResolveSpec, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
770
806
  try {
771
807
  const newSpec = Object.assign({}, unResolveSpec);
772
808
  const newPaths = {};
773
809
  for (const filterItem of filter) {
774
810
  const [method, path] = filterItem.split(" ");
775
811
  const methodName = method.toLowerCase();
776
- if (!Utils.isSupportedApi(methodName, path, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
812
+ if (!Utils.isSupportedApi(methodName, path, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
777
813
  continue;
778
814
  }
779
815
  if (!newPaths[path]) {
@@ -799,8 +835,126 @@ class SpecFilter {
799
835
 
800
836
  // Copyright (c) Microsoft Corporation.
801
837
  class ManifestUpdater {
802
- static updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, spec, allowMultipleParameters, auth, isMe) {
838
+ static updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec) {
839
+ return __awaiter(this, void 0, void 0, function* () {
840
+ const manifest = yield fs__default['default'].readJSON(manifestPath);
841
+ const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
842
+ manifest.apiPlugins = [
843
+ {
844
+ pluginFile: apiPluginRelativePath,
845
+ },
846
+ ];
847
+ ManifestUpdater.updateManifestDescription(manifest, spec);
848
+ const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
849
+ const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath);
850
+ return [manifest, apiPlugin];
851
+ });
852
+ }
853
+ static updateManifestDescription(manifest, spec) {
803
854
  var _a, _b;
855
+ manifest.description = {
856
+ short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
857
+ full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : manifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
858
+ };
859
+ }
860
+ static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
861
+ let parameter;
862
+ if (schema.type === "string" ||
863
+ schema.type === "boolean" ||
864
+ schema.type === "integer" ||
865
+ schema.type === "number" ||
866
+ schema.type === "array") {
867
+ parameter = schema;
868
+ }
869
+ else {
870
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), exports.ErrorType.UpdateManifestFailed);
871
+ }
872
+ return parameter;
873
+ }
874
+ static generatePluginManifestSchema(spec, specRelativePath) {
875
+ var _a, _b, _c;
876
+ const functions = [];
877
+ const functionNames = [];
878
+ const paths = spec.paths;
879
+ for (const pathUrl in paths) {
880
+ const pathItem = paths[pathUrl];
881
+ if (pathItem) {
882
+ const operations = pathItem;
883
+ for (const method in operations) {
884
+ if (ConstantString.AllOperationMethods.includes(method)) {
885
+ const operationItem = operations[method];
886
+ if (operationItem) {
887
+ const operationId = operationItem.operationId;
888
+ const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
889
+ const paramObject = operationItem.parameters;
890
+ const requestBody = operationItem.requestBody;
891
+ const parameters = {
892
+ type: "object",
893
+ properties: {},
894
+ required: [],
895
+ };
896
+ if (paramObject) {
897
+ for (let i = 0; i < paramObject.length; i++) {
898
+ const param = paramObject[i];
899
+ const schema = param.schema;
900
+ parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
901
+ if (param.required) {
902
+ parameters.required.push(param.name);
903
+ }
904
+ if (!parameters.properties[param.name].description) {
905
+ parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
906
+ }
907
+ }
908
+ }
909
+ if (requestBody) {
910
+ const requestJsonBody = requestBody.content["application/json"];
911
+ const requestBodySchema = requestJsonBody.schema;
912
+ if (requestBodySchema.type === "object") {
913
+ if (requestBodySchema.required) {
914
+ parameters.required.push(...requestBodySchema.required);
915
+ }
916
+ for (const property in requestBodySchema.properties) {
917
+ const schema = requestBodySchema.properties[property];
918
+ parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
919
+ }
920
+ }
921
+ else {
922
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
923
+ }
924
+ }
925
+ const funcObj = {
926
+ name: operationId,
927
+ description: description,
928
+ parameters: parameters,
929
+ };
930
+ functions.push(funcObj);
931
+ functionNames.push(operationId);
932
+ }
933
+ }
934
+ }
935
+ }
936
+ }
937
+ const apiPlugin = {
938
+ schema_version: "v2",
939
+ name_for_human: spec.info.title,
940
+ description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
941
+ functions: functions,
942
+ runtimes: [
943
+ {
944
+ type: "OpenApi",
945
+ auth: {
946
+ type: "none", // TODO, support auth in the future
947
+ },
948
+ spec: {
949
+ url: specRelativePath,
950
+ },
951
+ run_for_functions: functionNames,
952
+ },
953
+ ],
954
+ };
955
+ return apiPlugin;
956
+ }
957
+ static updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, spec, allowMultipleParameters, auth, isMe) {
804
958
  return __awaiter(this, void 0, void 0, function* () {
805
959
  try {
806
960
  const originalManifest = yield fs__default['default'].readJSON(manifestPath);
@@ -835,10 +989,8 @@ class ManifestUpdater {
835
989
  };
836
990
  }
837
991
  }
838
- updatedPart.description = {
839
- short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
840
- full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : originalManifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
841
- };
992
+ updatedPart.description = originalManifest.description;
993
+ ManifestUpdater.updateManifestDescription(updatedPart, spec);
842
994
  updatedPart.composeExtensions = isMe === undefined || isMe === true ? [composeExtension] : [];
843
995
  const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
844
996
  return [updatedManifest, warnings];
@@ -1153,6 +1305,7 @@ class SpecParser {
1153
1305
  allowAPIKeyAuth: false,
1154
1306
  allowMultipleParameters: false,
1155
1307
  allowOauth2: false,
1308
+ isCopilot: false,
1156
1309
  };
1157
1310
  this.pathOrSpec = pathOrDoc;
1158
1311
  this.parser = new SwaggerParser__default['default']();
@@ -1186,7 +1339,7 @@ class SpecParser {
1186
1339
  ],
1187
1340
  };
1188
1341
  }
1189
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1342
+ return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1190
1343
  }
1191
1344
  catch (err) {
1192
1345
  throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
@@ -1267,7 +1420,7 @@ class SpecParser {
1267
1420
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1268
1421
  throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
1269
1422
  }
1270
- const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1423
+ const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1271
1424
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1272
1425
  throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
1273
1426
  }
@@ -1282,6 +1435,47 @@ class SpecParser {
1282
1435
  }
1283
1436
  });
1284
1437
  }
1438
+ /**
1439
+ * Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
1440
+ * @param manifestPath A file path of the Teams app manifest file to update.
1441
+ * @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
1442
+ * @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
1443
+ * @param pluginFilePath File path of the api plugin file to generate.
1444
+ */
1445
+ generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
1446
+ return __awaiter(this, void 0, void 0, function* () {
1447
+ const result = {
1448
+ allSuccess: true,
1449
+ warnings: [],
1450
+ };
1451
+ try {
1452
+ const newSpecs = yield this.getFilteredSpecs(filter, signal);
1453
+ const newUnResolvedSpec = newSpecs[0];
1454
+ const newSpec = newSpecs[1];
1455
+ let resultStr;
1456
+ if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1457
+ resultStr = jsyaml__default['default'].dump(newUnResolvedSpec);
1458
+ }
1459
+ else {
1460
+ resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1461
+ }
1462
+ yield fs__default['default'].outputFile(outputSpecPath, resultStr);
1463
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1464
+ throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
1465
+ }
1466
+ const [updatedManifest, apiPlugin] = yield ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec);
1467
+ yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1468
+ yield fs__default['default'].outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
1469
+ }
1470
+ catch (err) {
1471
+ if (err instanceof SpecParserError) {
1472
+ throw err;
1473
+ }
1474
+ throw new SpecParserError(err.toString(), exports.ErrorType.GenerateFailed);
1475
+ }
1476
+ return result;
1477
+ });
1478
+ }
1285
1479
  /**
1286
1480
  * Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
1287
1481
  * @param manifestPath A file path of the Teams app manifest file to update.
@@ -1386,7 +1580,7 @@ class SpecParser {
1386
1580
  if (this.apiMap !== undefined) {
1387
1581
  return this.apiMap;
1388
1582
  }
1389
- const result = Utils.listSupportedAPIs(spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1583
+ const result = Utils.listSupportedAPIs(spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1390
1584
  this.apiMap = result;
1391
1585
  return result;
1392
1586
  }