@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.
@@ -70,6 +70,7 @@ ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multi
70
70
  ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
71
71
  ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
72
72
  ConstantString.MultipleAPIKeyNotSupported = "Multiple API keys are not supported. Please make sure that all selected APIs use the same API key.";
73
+ ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
73
74
  ConstantString.WrappedCardVersion = "devPreview";
74
75
  ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
75
76
  ConstantString.WrappedCardResponseLayout = "list";
@@ -152,7 +153,18 @@ class SpecParserError extends Error {
152
153
 
153
154
  // Copyright (c) Microsoft Corporation.
154
155
  class Utils {
155
- static checkParameters(paramObject) {
156
+ static hasNestedObjectInSchema(schema) {
157
+ if (schema.type === "object") {
158
+ for (const property in schema.properties) {
159
+ const nestedSchema = schema.properties[property];
160
+ if (nestedSchema.type === "object") {
161
+ return true;
162
+ }
163
+ }
164
+ }
165
+ return false;
166
+ }
167
+ static checkParameters(paramObject, isCopilot) {
156
168
  const paramResult = {
157
169
  requiredNum: 0,
158
170
  optionalNum: 0,
@@ -164,7 +176,20 @@ class Utils {
164
176
  for (let i = 0; i < paramObject.length; i++) {
165
177
  const param = paramObject[i];
166
178
  const schema = param.schema;
179
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
180
+ paramResult.isValid = false;
181
+ continue;
182
+ }
167
183
  const isRequiredWithoutDefault = param.required && schema.default === undefined;
184
+ if (isCopilot) {
185
+ if (isRequiredWithoutDefault) {
186
+ paramResult.requiredNum = paramResult.requiredNum + 1;
187
+ }
188
+ else {
189
+ paramResult.optionalNum = paramResult.optionalNum + 1;
190
+ }
191
+ continue;
192
+ }
168
193
  if (param.in === "header" || param.in === "cookie") {
169
194
  if (isRequiredWithoutDefault) {
170
195
  paramResult.isValid = false;
@@ -191,7 +216,7 @@ class Utils {
191
216
  }
192
217
  return paramResult;
193
218
  }
194
- static checkPostBody(schema, isRequired = false) {
219
+ static checkPostBody(schema, isRequired = false, isCopilot = false) {
195
220
  var _a;
196
221
  const paramResult = {
197
222
  requiredNum: 0,
@@ -202,6 +227,10 @@ class Utils {
202
227
  return paramResult;
203
228
  }
204
229
  const isRequiredWithoutDefault = isRequired && schema.default === undefined;
230
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
231
+ paramResult.isValid = false;
232
+ return paramResult;
233
+ }
205
234
  if (schema.type === "string" ||
206
235
  schema.type === "integer" ||
207
236
  schema.type === "boolean" ||
@@ -220,14 +249,14 @@ class Utils {
220
249
  if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
221
250
  isRequired = true;
222
251
  }
223
- const result = Utils.checkPostBody(properties[property], isRequired);
252
+ const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
224
253
  paramResult.requiredNum += result.requiredNum;
225
254
  paramResult.optionalNum += result.optionalNum;
226
255
  paramResult.isValid = paramResult.isValid && result.isValid;
227
256
  }
228
257
  }
229
258
  else {
230
- if (isRequiredWithoutDefault) {
259
+ if (isRequiredWithoutDefault && !isCopilot) {
231
260
  paramResult.isValid = false;
232
261
  }
233
262
  }
@@ -247,7 +276,7 @@ class Utils {
247
276
  * 5. response body should be “application/json” and not empty, and response code should be 20X
248
277
  * 6. only support request body with “application/json” content type
249
278
  */
250
- static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
279
+ static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
251
280
  const pathObj = spec.paths[path];
252
281
  method = method.toLocaleLowerCase();
253
282
  if (pathObj) {
@@ -280,15 +309,22 @@ class Utils {
280
309
  };
281
310
  if (requestJsonBody) {
282
311
  const requestBodySchema = requestJsonBody.schema;
283
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
312
+ if (isCopilot && requestBodySchema.type !== "object") {
313
+ return false;
314
+ }
315
+ requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
284
316
  }
285
317
  if (!requestBodyParamResult.isValid) {
286
318
  return false;
287
319
  }
288
- const paramResult = Utils.checkParameters(paramObject);
320
+ const paramResult = Utils.checkParameters(paramObject, isCopilot);
289
321
  if (!paramResult.isValid) {
290
322
  return false;
291
323
  }
324
+ // Copilot support arbitrary parameters
325
+ if (isCopilot) {
326
+ return true;
327
+ }
292
328
  if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
293
329
  if (allowMultipleParameters &&
294
330
  requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
@@ -459,7 +495,7 @@ class Utils {
459
495
  }
460
496
  return errors;
461
497
  }
462
- static validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
498
+ static validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
463
499
  const errors = [];
464
500
  let hasTopLevelServers = false;
465
501
  let hasPathLevelServers = false;
@@ -480,7 +516,7 @@ class Utils {
480
516
  }
481
517
  for (const method in methods) {
482
518
  const operationObject = methods[method];
483
- if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
519
+ if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
484
520
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
485
521
  hasOperationLevelServers = true;
486
522
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -630,14 +666,14 @@ class Utils {
630
666
  }
631
667
  return [command, warning];
632
668
  }
633
- static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
669
+ static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
634
670
  const paths = spec.paths;
635
671
  const result = {};
636
672
  for (const path in paths) {
637
673
  const methods = paths[path];
638
674
  for (const method in methods) {
639
675
  // For developer preview, only support GET operation with only 1 parameter without auth
640
- if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
676
+ if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
641
677
  const operationObject = methods[method];
642
678
  result[`${method.toUpperCase()} ${path}`] = operationObject;
643
679
  }
@@ -645,7 +681,7 @@ class Utils {
645
681
  }
646
682
  return result;
647
683
  }
648
- static validateSpec(spec, parser, isSwaggerFile, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
684
+ static validateSpec(spec, parser, isSwaggerFile, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
649
685
  const errors = [];
650
686
  const warnings = [];
651
687
  if (isSwaggerFile) {
@@ -655,7 +691,7 @@ class Utils {
655
691
  });
656
692
  }
657
693
  // Server validation
658
- const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
694
+ const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot);
659
695
  errors.push(...serverErrors);
660
696
  // Remote reference not supported
661
697
  const refPaths = parser.$refs.paths();
@@ -668,7 +704,7 @@ class Utils {
668
704
  });
669
705
  }
670
706
  // No supported API
671
- const apiMap = Utils.listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
707
+ const apiMap = Utils.listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot);
672
708
  if (Object.keys(apiMap).length === 0) {
673
709
  errors.push({
674
710
  type: ErrorType.NoSupportedApi,
@@ -724,14 +760,14 @@ class Utils {
724
760
 
725
761
  // Copyright (c) Microsoft Corporation.
726
762
  class SpecFilter {
727
- static specFilter(filter, unResolveSpec, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
763
+ static specFilter(filter, unResolveSpec, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
728
764
  try {
729
765
  const newSpec = Object.assign({}, unResolveSpec);
730
766
  const newPaths = {};
731
767
  for (const filterItem of filter) {
732
768
  const [method, path] = filterItem.split(" ");
733
769
  const methodName = method.toLowerCase();
734
- if (!Utils.isSupportedApi(methodName, path, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
770
+ if (!Utils.isSupportedApi(methodName, path, resolvedSpec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot)) {
735
771
  continue;
736
772
  }
737
773
  if (!newPaths[path]) {
@@ -757,8 +793,124 @@ class SpecFilter {
757
793
 
758
794
  // Copyright (c) Microsoft Corporation.
759
795
  class ManifestUpdater {
760
- static async updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, spec, allowMultipleParameters, auth, isMe) {
796
+ static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec) {
797
+ const manifest = await fs.readJSON(manifestPath);
798
+ const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
799
+ manifest.apiPlugins = [
800
+ {
801
+ pluginFile: apiPluginRelativePath,
802
+ },
803
+ ];
804
+ ManifestUpdater.updateManifestDescription(manifest, spec);
805
+ const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
806
+ const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath);
807
+ return [manifest, apiPlugin];
808
+ }
809
+ static updateManifestDescription(manifest, spec) {
761
810
  var _a, _b;
811
+ manifest.description = {
812
+ short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
813
+ 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),
814
+ };
815
+ }
816
+ static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
817
+ let parameter;
818
+ if (schema.type === "string" ||
819
+ schema.type === "boolean" ||
820
+ schema.type === "integer" ||
821
+ schema.type === "number" ||
822
+ schema.type === "array") {
823
+ parameter = schema;
824
+ }
825
+ else {
826
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
827
+ }
828
+ return parameter;
829
+ }
830
+ static generatePluginManifestSchema(spec, specRelativePath) {
831
+ var _a, _b, _c;
832
+ const functions = [];
833
+ const functionNames = [];
834
+ const paths = spec.paths;
835
+ for (const pathUrl in paths) {
836
+ const pathItem = paths[pathUrl];
837
+ if (pathItem) {
838
+ const operations = pathItem;
839
+ for (const method in operations) {
840
+ if (ConstantString.AllOperationMethods.includes(method)) {
841
+ const operationItem = operations[method];
842
+ if (operationItem) {
843
+ const operationId = operationItem.operationId;
844
+ const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
845
+ const paramObject = operationItem.parameters;
846
+ const requestBody = operationItem.requestBody;
847
+ const parameters = {
848
+ type: "object",
849
+ properties: {},
850
+ required: [],
851
+ };
852
+ if (paramObject) {
853
+ for (let i = 0; i < paramObject.length; i++) {
854
+ const param = paramObject[i];
855
+ const schema = param.schema;
856
+ parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
857
+ if (param.required) {
858
+ parameters.required.push(param.name);
859
+ }
860
+ if (!parameters.properties[param.name].description) {
861
+ parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
862
+ }
863
+ }
864
+ }
865
+ if (requestBody) {
866
+ const requestJsonBody = requestBody.content["application/json"];
867
+ const requestBodySchema = requestJsonBody.schema;
868
+ if (requestBodySchema.type === "object") {
869
+ if (requestBodySchema.required) {
870
+ parameters.required.push(...requestBodySchema.required);
871
+ }
872
+ for (const property in requestBodySchema.properties) {
873
+ const schema = requestBodySchema.properties[property];
874
+ parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
875
+ }
876
+ }
877
+ else {
878
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
879
+ }
880
+ }
881
+ const funcObj = {
882
+ name: operationId,
883
+ description: description,
884
+ parameters: parameters,
885
+ };
886
+ functions.push(funcObj);
887
+ functionNames.push(operationId);
888
+ }
889
+ }
890
+ }
891
+ }
892
+ }
893
+ const apiPlugin = {
894
+ schema_version: "v2",
895
+ name_for_human: spec.info.title,
896
+ description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
897
+ functions: functions,
898
+ runtimes: [
899
+ {
900
+ type: "OpenApi",
901
+ auth: {
902
+ type: "none", // TODO, support auth in the future
903
+ },
904
+ spec: {
905
+ url: specRelativePath,
906
+ },
907
+ run_for_functions: functionNames,
908
+ },
909
+ ],
910
+ };
911
+ return apiPlugin;
912
+ }
913
+ static async updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, spec, allowMultipleParameters, auth, isMe) {
762
914
  try {
763
915
  const originalManifest = await fs.readJSON(manifestPath);
764
916
  const updatedPart = {};
@@ -792,10 +944,8 @@ class ManifestUpdater {
792
944
  };
793
945
  }
794
946
  }
795
- updatedPart.description = {
796
- short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
797
- 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),
798
- };
947
+ updatedPart.description = originalManifest.description;
948
+ ManifestUpdater.updateManifestDescription(updatedPart, spec);
799
949
  updatedPart.composeExtensions = isMe === undefined || isMe === true ? [composeExtension] : [];
800
950
  const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
801
951
  return [updatedManifest, warnings];
@@ -1107,6 +1257,7 @@ class SpecParser {
1107
1257
  allowAPIKeyAuth: false,
1108
1258
  allowMultipleParameters: false,
1109
1259
  allowOauth2: false,
1260
+ isCopilot: false,
1110
1261
  };
1111
1262
  this.pathOrSpec = pathOrDoc;
1112
1263
  this.parser = new SwaggerParser();
@@ -1139,7 +1290,7 @@ class SpecParser {
1139
1290
  ],
1140
1291
  };
1141
1292
  }
1142
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1293
+ return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1143
1294
  }
1144
1295
  catch (err) {
1145
1296
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -1214,7 +1365,7 @@ class SpecParser {
1214
1365
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1215
1366
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1216
1367
  }
1217
- const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1368
+ const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1218
1369
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1219
1370
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1220
1371
  }
@@ -1228,6 +1379,45 @@ class SpecParser {
1228
1379
  throw new SpecParserError(err.toString(), ErrorType.GetSpecFailed);
1229
1380
  }
1230
1381
  }
1382
+ /**
1383
+ * Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
1384
+ * @param manifestPath A file path of the Teams app manifest file to update.
1385
+ * @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
1386
+ * @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
1387
+ * @param pluginFilePath File path of the api plugin file to generate.
1388
+ */
1389
+ async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
1390
+ const result = {
1391
+ allSuccess: true,
1392
+ warnings: [],
1393
+ };
1394
+ try {
1395
+ const newSpecs = await this.getFilteredSpecs(filter, signal);
1396
+ const newUnResolvedSpec = newSpecs[0];
1397
+ const newSpec = newSpecs[1];
1398
+ let resultStr;
1399
+ if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1400
+ resultStr = jsyaml.dump(newUnResolvedSpec);
1401
+ }
1402
+ else {
1403
+ resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1404
+ }
1405
+ await fs.outputFile(outputSpecPath, resultStr);
1406
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1407
+ throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1408
+ }
1409
+ const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec);
1410
+ await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1411
+ await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
1412
+ }
1413
+ catch (err) {
1414
+ if (err instanceof SpecParserError) {
1415
+ throw err;
1416
+ }
1417
+ throw new SpecParserError(err.toString(), ErrorType.GenerateFailed);
1418
+ }
1419
+ return result;
1420
+ }
1231
1421
  /**
1232
1422
  * Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
1233
1423
  * @param manifestPath A file path of the Teams app manifest file to update.
@@ -1328,7 +1518,7 @@ class SpecParser {
1328
1518
  if (this.apiMap !== undefined) {
1329
1519
  return this.apiMap;
1330
1520
  }
1331
- const result = Utils.listSupportedAPIs(spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2);
1521
+ const result = Utils.listSupportedAPIs(spec, this.options.allowMissingId, this.options.allowAPIKeyAuth, this.options.allowMultipleParameters, this.options.allowOauth2, this.options.isCopilot);
1332
1522
  this.apiMap = result;
1333
1523
  return result;
1334
1524
  }