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