@microsoft/m365-spec-parser 0.1.1-alpha.be832d7f6.0 → 0.1.1-alpha.c3fe84399.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;
@@ -420,17 +224,39 @@ class Utils {
420
224
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
421
225
  return result;
422
226
  }
227
+ static getAuthInfo(spec) {
228
+ let authInfo = undefined;
229
+ for (const url in spec.paths) {
230
+ for (const method in spec.paths[url]) {
231
+ const operation = spec.paths[url][method];
232
+ const authArray = Utils.getAuthArray(operation.security, spec);
233
+ if (authArray && authArray.length > 0) {
234
+ const currentAuth = authArray[0][0];
235
+ if (!authInfo) {
236
+ authInfo = authArray[0][0];
237
+ }
238
+ else if (authInfo.name !== currentAuth.name) {
239
+ throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return authInfo;
245
+ }
423
246
  static updateFirstLetter(str) {
424
247
  return str.charAt(0).toUpperCase() + str.slice(1);
425
248
  }
426
- static getResponseJson(operationObject, isTeamsAiProject = false) {
249
+ static getResponseJson(operationObject) {
427
250
  var _a, _b;
428
251
  let json = {};
252
+ let multipleMediaType = false;
429
253
  for (const code of ConstantString.ResponseCodeFor20X) {
430
254
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
431
255
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
256
+ multipleMediaType = false;
432
257
  json = responseObject.content["application/json"];
433
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
258
+ if (Utils.containMultipleMediaTypes(responseObject)) {
259
+ multipleMediaType = true;
434
260
  json = {};
435
261
  }
436
262
  else {
@@ -438,7 +264,7 @@ class Utils {
438
264
  }
439
265
  }
440
266
  }
441
- return json;
267
+ return { json, multipleMediaType };
442
268
  }
443
269
  static convertPathToCamelCase(path) {
444
270
  const pathSegments = path.split(/[./{]/);
@@ -458,10 +284,10 @@ class Utils {
458
284
  return undefined;
459
285
  }
460
286
  }
461
- static resolveServerUrl(url) {
287
+ static resolveEnv(str) {
462
288
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
463
- let matches = placeHolderReg.exec(url);
464
- let newUrl = url;
289
+ let matches = placeHolderReg.exec(str);
290
+ let newStr = str;
465
291
  while (matches != null) {
466
292
  const envVar = matches[1];
467
293
  const envVal = process.env[envVar];
@@ -469,17 +295,17 @@ class Utils {
469
295
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
470
296
  }
471
297
  else {
472
- newUrl = newUrl.replace(matches[0], envVal);
298
+ newStr = newStr.replace(matches[0], envVal);
473
299
  }
474
- matches = placeHolderReg.exec(url);
300
+ matches = placeHolderReg.exec(str);
475
301
  }
476
- return newUrl;
302
+ return newStr;
477
303
  }
478
304
  static checkServerUrl(servers) {
479
305
  const errors = [];
480
306
  let serverUrl;
481
307
  try {
482
- serverUrl = Utils.resolveServerUrl(servers[0].url);
308
+ serverUrl = Utils.resolveEnv(servers[0].url);
483
309
  }
484
310
  catch (err) {
485
311
  errors.push({
@@ -510,6 +336,7 @@ class Utils {
510
336
  return errors;
511
337
  }
512
338
  static validateServer(spec, options) {
339
+ var _a;
513
340
  const errors = [];
514
341
  let hasTopLevelServers = false;
515
342
  let hasPathLevelServers = false;
@@ -530,7 +357,7 @@ class Utils {
530
357
  }
531
358
  for (const method in methods) {
532
359
  const operationObject = methods[method];
533
- if (Utils.isSupportedApi(method, path, spec, options)) {
360
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
534
361
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
535
362
  hasOperationLevelServers = true;
536
363
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -657,13 +484,7 @@ class Utils {
657
484
  }
658
485
  }
659
486
  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
- }
487
+ const parameters = [...requiredParams, ...optionalParams];
667
488
  const command = {
668
489
  context: ["compose"],
669
490
  type: "query",
@@ -672,117 +493,534 @@ class Utils {
672
493
  parameters: parameters,
673
494
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
674
495
  };
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
- };
496
+ return command;
497
+ }
498
+ static format(str, ...args) {
499
+ let index = 0;
500
+ return str.replace(/%s/g, () => {
501
+ const arg = args[index++];
502
+ return arg !== undefined ? arg : "";
503
+ });
504
+ }
505
+ static getSafeRegistrationIdEnvName(authName) {
506
+ if (!authName) {
507
+ return "";
508
+ }
509
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
510
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
511
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
682
512
  }
683
- return [command, warning];
513
+ return safeRegistrationIdEnvName;
684
514
  }
685
- static listSupportedAPIs(spec, options) {
686
- const paths = spec.paths;
515
+ static getServerObject(spec, method, path) {
516
+ const pathObj = spec.paths[path];
517
+ const operationObject = pathObj[method];
518
+ const rootServer = spec.servers && spec.servers[0];
519
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
520
+ const operationServer = operationObject.servers && operationObject.servers[0];
521
+ const serverUrl = operationServer || methodServer || rootServer;
522
+ return serverUrl;
523
+ }
524
+ }
525
+
526
+ // Copyright (c) Microsoft Corporation.
527
+ class Validator {
528
+ listAPIs() {
529
+ var _a;
530
+ if (this.apiMap) {
531
+ return this.apiMap;
532
+ }
533
+ const paths = this.spec.paths;
687
534
  const result = {};
688
535
  for (const path in paths) {
689
536
  const methods = paths[path];
690
537
  for (const method in methods) {
691
- if (Utils.isSupportedApi(method, path, spec, options)) {
692
- const operationObject = methods[method];
693
- result[`${method.toUpperCase()} ${path}`] = operationObject;
538
+ const operationObject = methods[method];
539
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
540
+ const validateResult = this.validateAPI(method, path);
541
+ result[`${method.toUpperCase()} ${path}`] = {
542
+ operation: operationObject,
543
+ isValid: validateResult.isValid,
544
+ reason: validateResult.reason,
545
+ };
694
546
  }
695
547
  }
696
548
  }
549
+ this.apiMap = result;
697
550
  return result;
698
551
  }
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,
552
+ validateSpecVersion() {
553
+ const result = { errors: [], warnings: [] };
554
+ if (this.spec.openapi >= "3.1.0") {
555
+ result.errors.push({
556
+ type: ErrorType.SpecVersionNotSupported,
557
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
558
+ data: this.spec.openapi,
706
559
  });
707
560
  }
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,
719
- });
720
- }
721
- // No supported API
722
- const apiMap = Utils.listSupportedAPIs(spec, options);
723
- if (Object.keys(apiMap).length === 0) {
724
- errors.push({
561
+ return result;
562
+ }
563
+ validateSpecServer() {
564
+ const result = { errors: [], warnings: [] };
565
+ const serverErrors = Utils.validateServer(this.spec, this.options);
566
+ result.errors.push(...serverErrors);
567
+ return result;
568
+ }
569
+ validateSpecNoSupportAPI() {
570
+ const result = { errors: [], warnings: [] };
571
+ const apiMap = this.listAPIs();
572
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
573
+ if (validAPIs.length === 0) {
574
+ const data = [];
575
+ for (const key in apiMap) {
576
+ const { reason } = apiMap[key];
577
+ const apiInvalidReason = { api: key, reason: reason };
578
+ data.push(apiInvalidReason);
579
+ }
580
+ result.errors.push({
725
581
  type: ErrorType.NoSupportedApi,
726
582
  content: ConstantString.NoSupportedApi,
583
+ data,
727
584
  });
728
585
  }
586
+ return result;
587
+ }
588
+ validateSpecOperationId() {
589
+ const result = { errors: [], warnings: [] };
590
+ const apiMap = this.listAPIs();
729
591
  // OperationId missing
730
592
  const apisMissingOperationId = [];
731
593
  for (const key in apiMap) {
732
- const pathObjectItem = apiMap[key];
733
- if (!pathObjectItem.operationId) {
594
+ const { operation } = apiMap[key];
595
+ if (!operation.operationId) {
734
596
  apisMissingOperationId.push(key);
735
597
  }
736
598
  }
737
599
  if (apisMissingOperationId.length > 0) {
738
- warnings.push({
600
+ result.warnings.push({
739
601
  type: WarningType.OperationIdMissing,
740
602
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
741
603
  data: apisMissingOperationId,
742
604
  });
743
605
  }
744
- let status = ValidationStatus.Valid;
745
- if (warnings.length > 0 && errors.length === 0) {
746
- status = ValidationStatus.Warning;
606
+ return result;
607
+ }
608
+ validateMethodAndPath(method, path) {
609
+ const result = { isValid: true, reason: [] };
610
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
611
+ result.isValid = false;
612
+ result.reason.push(ErrorType.MethodNotAllowed);
613
+ return result;
747
614
  }
748
- else if (errors.length > 0) {
749
- status = ValidationStatus.Error;
615
+ const pathObj = this.spec.paths[path];
616
+ if (!pathObj || !pathObj[method]) {
617
+ result.isValid = false;
618
+ result.reason.push(ErrorType.UrlPathNotExist);
619
+ return result;
750
620
  }
751
- return {
752
- status,
753
- warnings,
754
- errors,
755
- };
621
+ return result;
756
622
  }
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
- });
623
+ validateResponse(method, path) {
624
+ const result = { isValid: true, reason: [] };
625
+ const operationObject = this.spec.paths[path][method];
626
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
627
+ if (this.options.projectType === ProjectType.SME) {
628
+ // only support response body only contains “application/json” content type
629
+ if (multipleMediaType) {
630
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
631
+ }
632
+ else if (Object.keys(json).length === 0) {
633
+ // response body should not be empty
634
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
635
+ }
636
+ }
637
+ return result;
763
638
  }
764
- static getSafeRegistrationIdEnvName(authName) {
765
- if (!authName) {
766
- return "";
639
+ validateServer(method, path) {
640
+ const result = { isValid: true, reason: [] };
641
+ const serverObj = Utils.getServerObject(this.spec, method, path);
642
+ if (!serverObj) {
643
+ // should contain server URL
644
+ result.reason.push(ErrorType.NoServerInformation);
767
645
  }
768
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
769
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
770
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
646
+ else {
647
+ // server url should be absolute url with https protocol
648
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
649
+ result.reason.push(...serverValidateResult.map((item) => item.type));
771
650
  }
772
- return safeRegistrationIdEnvName;
651
+ return result;
773
652
  }
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++;
653
+ validateAuth(method, path) {
654
+ const pathObj = this.spec.paths[path];
655
+ const operationObject = pathObj[method];
656
+ const securities = operationObject.security;
657
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
658
+ if (authSchemeArray.length === 0) {
659
+ return { isValid: true, reason: [] };
660
+ }
661
+ if (this.options.allowAPIKeyAuth ||
662
+ this.options.allowOauth2 ||
663
+ this.options.allowBearerTokenAuth) {
664
+ // Currently we don't support multiple auth in one operation
665
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
666
+ return {
667
+ isValid: false,
668
+ reason: [ErrorType.MultipleAuthNotSupported],
669
+ };
670
+ }
671
+ for (const auths of authSchemeArray) {
672
+ if (auths.length === 1) {
673
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
674
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
675
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
676
+ return { isValid: true, reason: [] };
677
+ }
678
+ }
679
+ }
680
+ }
681
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
682
+ }
683
+ checkPostBodySchema(schema, isRequired = false) {
684
+ var _a;
685
+ const paramResult = {
686
+ requiredNum: 0,
687
+ optionalNum: 0,
688
+ isValid: true,
689
+ reason: [],
690
+ };
691
+ if (Object.keys(schema).length === 0) {
692
+ return paramResult;
693
+ }
694
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
695
+ const isCopilot = this.projectType === ProjectType.Copilot;
696
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
697
+ paramResult.isValid = false;
698
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
699
+ return paramResult;
700
+ }
701
+ if (schema.type === "string" ||
702
+ schema.type === "integer" ||
703
+ schema.type === "boolean" ||
704
+ schema.type === "number") {
705
+ if (isRequiredWithoutDefault) {
706
+ paramResult.requiredNum = paramResult.requiredNum + 1;
707
+ }
708
+ else {
709
+ paramResult.optionalNum = paramResult.optionalNum + 1;
710
+ }
711
+ }
712
+ else if (schema.type === "object") {
713
+ const { properties } = schema;
714
+ for (const property in properties) {
715
+ let isRequired = false;
716
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
717
+ isRequired = true;
782
718
  }
719
+ const result = this.checkPostBodySchema(properties[property], isRequired);
720
+ paramResult.requiredNum += result.requiredNum;
721
+ paramResult.optionalNum += result.optionalNum;
722
+ paramResult.isValid = paramResult.isValid && result.isValid;
723
+ paramResult.reason.push(...result.reason);
783
724
  }
784
725
  }
785
- return count;
726
+ else {
727
+ if (isRequiredWithoutDefault && !isCopilot) {
728
+ paramResult.isValid = false;
729
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
730
+ }
731
+ }
732
+ return paramResult;
733
+ }
734
+ checkParamSchema(paramObject) {
735
+ const paramResult = {
736
+ requiredNum: 0,
737
+ optionalNum: 0,
738
+ isValid: true,
739
+ reason: [],
740
+ };
741
+ if (!paramObject) {
742
+ return paramResult;
743
+ }
744
+ const isCopilot = this.projectType === ProjectType.Copilot;
745
+ for (let i = 0; i < paramObject.length; i++) {
746
+ const param = paramObject[i];
747
+ const schema = param.schema;
748
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
749
+ paramResult.isValid = false;
750
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
751
+ continue;
752
+ }
753
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
754
+ if (isCopilot) {
755
+ if (isRequiredWithoutDefault) {
756
+ paramResult.requiredNum = paramResult.requiredNum + 1;
757
+ }
758
+ else {
759
+ paramResult.optionalNum = paramResult.optionalNum + 1;
760
+ }
761
+ continue;
762
+ }
763
+ if (param.in === "header" || param.in === "cookie") {
764
+ if (isRequiredWithoutDefault) {
765
+ paramResult.isValid = false;
766
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
767
+ }
768
+ continue;
769
+ }
770
+ if (schema.type !== "boolean" &&
771
+ schema.type !== "string" &&
772
+ schema.type !== "number" &&
773
+ schema.type !== "integer") {
774
+ if (isRequiredWithoutDefault) {
775
+ paramResult.isValid = false;
776
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
777
+ }
778
+ continue;
779
+ }
780
+ if (param.in === "query" || param.in === "path") {
781
+ if (isRequiredWithoutDefault) {
782
+ paramResult.requiredNum = paramResult.requiredNum + 1;
783
+ }
784
+ else {
785
+ paramResult.optionalNum = paramResult.optionalNum + 1;
786
+ }
787
+ }
788
+ }
789
+ return paramResult;
790
+ }
791
+ hasNestedObjectInSchema(schema) {
792
+ if (schema.type === "object") {
793
+ for (const property in schema.properties) {
794
+ const nestedSchema = schema.properties[property];
795
+ if (nestedSchema.type === "object") {
796
+ return true;
797
+ }
798
+ }
799
+ }
800
+ return false;
801
+ }
802
+ }
803
+
804
+ // Copyright (c) Microsoft Corporation.
805
+ class CopilotValidator extends Validator {
806
+ constructor(spec, options) {
807
+ super();
808
+ this.projectType = ProjectType.Copilot;
809
+ this.options = options;
810
+ this.spec = spec;
811
+ }
812
+ validateSpec() {
813
+ const result = { errors: [], warnings: [] };
814
+ // validate spec version
815
+ let validationResult = this.validateSpecVersion();
816
+ result.errors.push(...validationResult.errors);
817
+ // validate spec server
818
+ validationResult = this.validateSpecServer();
819
+ result.errors.push(...validationResult.errors);
820
+ // validate no supported API
821
+ validationResult = this.validateSpecNoSupportAPI();
822
+ result.errors.push(...validationResult.errors);
823
+ // validate operationId missing
824
+ validationResult = this.validateSpecOperationId();
825
+ result.warnings.push(...validationResult.warnings);
826
+ return result;
827
+ }
828
+ validateAPI(method, path) {
829
+ const result = { isValid: true, reason: [] };
830
+ method = method.toLocaleLowerCase();
831
+ // validate method and path
832
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
833
+ if (!methodAndPathResult.isValid) {
834
+ return methodAndPathResult;
835
+ }
836
+ const operationObject = this.spec.paths[path][method];
837
+ // validate auth
838
+ const authCheckResult = this.validateAuth(method, path);
839
+ result.reason.push(...authCheckResult.reason);
840
+ // validate operationId
841
+ if (!this.options.allowMissingId && !operationObject.operationId) {
842
+ result.reason.push(ErrorType.MissingOperationId);
843
+ }
844
+ // validate server
845
+ const validateServerResult = this.validateServer(method, path);
846
+ result.reason.push(...validateServerResult.reason);
847
+ // validate response
848
+ const validateResponseResult = this.validateResponse(method, path);
849
+ result.reason.push(...validateResponseResult.reason);
850
+ // validate requestBody
851
+ const requestBody = operationObject.requestBody;
852
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
853
+ if (requestJsonBody) {
854
+ const requestBodySchema = requestJsonBody.schema;
855
+ if (requestBodySchema.type !== "object") {
856
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
857
+ }
858
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
859
+ result.reason.push(...requestBodyParamResult.reason);
860
+ }
861
+ // validate parameters
862
+ const paramObject = operationObject.parameters;
863
+ const paramResult = this.checkParamSchema(paramObject);
864
+ result.reason.push(...paramResult.reason);
865
+ if (result.reason.length > 0) {
866
+ result.isValid = false;
867
+ }
868
+ return result;
869
+ }
870
+ }
871
+
872
+ // Copyright (c) Microsoft Corporation.
873
+ class SMEValidator extends Validator {
874
+ constructor(spec, options) {
875
+ super();
876
+ this.projectType = ProjectType.SME;
877
+ this.options = options;
878
+ this.spec = spec;
879
+ }
880
+ validateSpec() {
881
+ const result = { errors: [], warnings: [] };
882
+ // validate spec version
883
+ let validationResult = this.validateSpecVersion();
884
+ result.errors.push(...validationResult.errors);
885
+ // validate spec server
886
+ validationResult = this.validateSpecServer();
887
+ result.errors.push(...validationResult.errors);
888
+ // validate no supported API
889
+ validationResult = this.validateSpecNoSupportAPI();
890
+ result.errors.push(...validationResult.errors);
891
+ // validate operationId missing
892
+ if (this.options.allowMissingId) {
893
+ validationResult = this.validateSpecOperationId();
894
+ result.warnings.push(...validationResult.warnings);
895
+ }
896
+ return result;
897
+ }
898
+ validateAPI(method, path) {
899
+ const result = { isValid: true, reason: [] };
900
+ method = method.toLocaleLowerCase();
901
+ // validate method and path
902
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
903
+ if (!methodAndPathResult.isValid) {
904
+ return methodAndPathResult;
905
+ }
906
+ const operationObject = this.spec.paths[path][method];
907
+ // validate auth
908
+ const authCheckResult = this.validateAuth(method, path);
909
+ result.reason.push(...authCheckResult.reason);
910
+ // validate operationId
911
+ if (!this.options.allowMissingId && !operationObject.operationId) {
912
+ result.reason.push(ErrorType.MissingOperationId);
913
+ }
914
+ // validate server
915
+ const validateServerResult = this.validateServer(method, path);
916
+ result.reason.push(...validateServerResult.reason);
917
+ // validate response
918
+ const validateResponseResult = this.validateResponse(method, path);
919
+ result.reason.push(...validateResponseResult.reason);
920
+ let postBodyResult = {
921
+ requiredNum: 0,
922
+ optionalNum: 0,
923
+ isValid: true,
924
+ reason: [],
925
+ };
926
+ // validate requestBody
927
+ const requestBody = operationObject.requestBody;
928
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
929
+ if (Utils.containMultipleMediaTypes(requestBody)) {
930
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
931
+ }
932
+ if (requestJsonBody) {
933
+ const requestBodySchema = requestJsonBody.schema;
934
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
935
+ result.reason.push(...postBodyResult.reason);
936
+ }
937
+ // validate parameters
938
+ const paramObject = operationObject.parameters;
939
+ const paramResult = this.checkParamSchema(paramObject);
940
+ result.reason.push(...paramResult.reason);
941
+ // validate total parameters count
942
+ if (paramResult.isValid && postBodyResult.isValid) {
943
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
944
+ result.reason.push(...paramCountResult.reason);
945
+ }
946
+ if (result.reason.length > 0) {
947
+ result.isValid = false;
948
+ }
949
+ return result;
950
+ }
951
+ validateParamCount(postBodyResult, paramResult) {
952
+ const result = { isValid: true, reason: [] };
953
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
954
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
955
+ if (totalRequiredParams > 1) {
956
+ if (!this.options.allowMultipleParameters ||
957
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
958
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
959
+ }
960
+ }
961
+ else if (totalParams === 0) {
962
+ result.reason.push(ErrorType.NoParameter);
963
+ }
964
+ return result;
965
+ }
966
+ }
967
+ SMEValidator.SMERequiredParamsMaxNum = 5;
968
+
969
+ // Copyright (c) Microsoft Corporation.
970
+ class TeamsAIValidator extends Validator {
971
+ constructor(spec, options) {
972
+ super();
973
+ this.projectType = ProjectType.TeamsAi;
974
+ this.options = options;
975
+ this.spec = spec;
976
+ }
977
+ validateSpec() {
978
+ const result = { errors: [], warnings: [] };
979
+ // validate spec server
980
+ let validationResult = this.validateSpecServer();
981
+ result.errors.push(...validationResult.errors);
982
+ // validate no supported API
983
+ validationResult = this.validateSpecNoSupportAPI();
984
+ result.errors.push(...validationResult.errors);
985
+ return result;
986
+ }
987
+ validateAPI(method, path) {
988
+ const result = { isValid: true, reason: [] };
989
+ method = method.toLocaleLowerCase();
990
+ // validate method and path
991
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
992
+ if (!methodAndPathResult.isValid) {
993
+ return methodAndPathResult;
994
+ }
995
+ const operationObject = this.spec.paths[path][method];
996
+ // validate operationId
997
+ if (!this.options.allowMissingId && !operationObject.operationId) {
998
+ result.reason.push(ErrorType.MissingOperationId);
999
+ }
1000
+ // validate server
1001
+ const validateServerResult = this.validateServer(method, path);
1002
+ result.reason.push(...validateServerResult.reason);
1003
+ if (result.reason.length > 0) {
1004
+ result.isValid = false;
1005
+ }
1006
+ return result;
1007
+ }
1008
+ }
1009
+
1010
+ class ValidatorFactory {
1011
+ static create(spec, options) {
1012
+ var _a;
1013
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1014
+ switch (type) {
1015
+ case ProjectType.SME:
1016
+ return new SMEValidator(spec, options);
1017
+ case ProjectType.Copilot:
1018
+ return new CopilotValidator(spec, options);
1019
+ case ProjectType.TeamsAi:
1020
+ return new TeamsAIValidator(spec, options);
1021
+ default:
1022
+ throw new Error(`Invalid project type: ${type}`);
1023
+ }
786
1024
  }
787
1025
  }
788
1026
 
@@ -805,6 +1043,8 @@ class SpecParser {
805
1043
  allowBearerTokenAuth: false,
806
1044
  allowOauth2: false,
807
1045
  allowMethods: ["get", "post"],
1046
+ allowConversationStarters: false,
1047
+ allowResponseSemantics: false,
808
1048
  projectType: ProjectType.SME,
809
1049
  };
810
1050
  this.pathOrSpec = pathOrDoc;
@@ -820,11 +1060,7 @@ class SpecParser {
820
1060
  try {
821
1061
  try {
822
1062
  await this.loadSpec();
823
- await this.parser.validate(this.spec, {
824
- validate: {
825
- schema: false,
826
- },
827
- });
1063
+ await this.parser.validate(this.spec);
828
1064
  }
829
1065
  catch (e) {
830
1066
  return {
@@ -833,16 +1069,46 @@ class SpecParser {
833
1069
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
834
1070
  };
835
1071
  }
1072
+ const errors = [];
1073
+ const warnings = [];
836
1074
  if (!this.options.allowSwagger && this.isSwaggerFile) {
837
1075
  return {
838
1076
  status: ValidationStatus.Error,
839
1077
  warnings: [],
840
1078
  errors: [
841
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1079
+ {
1080
+ type: ErrorType.SwaggerNotSupported,
1081
+ content: ConstantString.SwaggerNotSupported,
1082
+ },
842
1083
  ],
843
1084
  };
844
1085
  }
845
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1086
+ // Remote reference not supported
1087
+ const refPaths = this.parser.$refs.paths();
1088
+ // refPaths [0] is the current spec file path
1089
+ if (refPaths.length > 1) {
1090
+ errors.push({
1091
+ type: ErrorType.RemoteRefNotSupported,
1092
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1093
+ data: refPaths,
1094
+ });
1095
+ }
1096
+ const validator = this.getValidator(this.spec);
1097
+ const validationResult = validator.validateSpec();
1098
+ warnings.push(...validationResult.warnings);
1099
+ errors.push(...validationResult.errors);
1100
+ let status = ValidationStatus.Valid;
1101
+ if (warnings.length > 0 && errors.length === 0) {
1102
+ status = ValidationStatus.Warning;
1103
+ }
1104
+ else if (errors.length > 0) {
1105
+ status = ValidationStatus.Error;
1106
+ }
1107
+ return {
1108
+ status: status,
1109
+ warnings: warnings,
1110
+ errors: errors,
1111
+ };
846
1112
  }
847
1113
  catch (err) {
848
1114
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -851,17 +1117,20 @@ class SpecParser {
851
1117
  async listSupportedAPIInfo() {
852
1118
  try {
853
1119
  await this.loadSpec();
854
- const apiMap = this.getAllSupportedAPIs(this.spec);
1120
+ const apiMap = this.getAPIs(this.spec);
855
1121
  const apiInfos = [];
856
1122
  for (const key in apiMap) {
857
- const pathObjectItem = apiMap[key];
1123
+ const { operation, isValid } = apiMap[key];
1124
+ if (!isValid) {
1125
+ continue;
1126
+ }
858
1127
  const [method, path] = key.split(" ");
859
- const operationId = pathObjectItem.operationId;
1128
+ const operationId = operation.operationId;
860
1129
  // In Browser environment, this api is by default not support api without operationId
861
1130
  if (!operationId) {
862
1131
  continue;
863
1132
  }
864
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1133
+ const command = Utils.parseApiInfo(operation, this.options);
865
1134
  const apiInfo = {
866
1135
  method: method,
867
1136
  path: path,
@@ -870,9 +1139,6 @@ class SpecParser {
870
1139
  parameters: command.parameters,
871
1140
  description: command.description,
872
1141
  };
873
- if (warning) {
874
- apiInfo.warning = warning;
875
- }
876
1142
  apiInfos.push(apiInfo);
877
1143
  }
878
1144
  return apiInfos;
@@ -931,13 +1197,18 @@ class SpecParser {
931
1197
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
932
1198
  }
933
1199
  }
934
- getAllSupportedAPIs(spec) {
935
- if (this.apiMap !== undefined) {
936
- return this.apiMap;
1200
+ getAPIs(spec) {
1201
+ const validator = this.getValidator(spec);
1202
+ const apiMap = validator.listAPIs();
1203
+ return apiMap;
1204
+ }
1205
+ getValidator(spec) {
1206
+ if (this.validator) {
1207
+ return this.validator;
937
1208
  }
938
- const result = Utils.listSupportedAPIs(spec, this.options);
939
- this.apiMap = result;
940
- return result;
1209
+ const validator = ValidatorFactory.create(spec, this.options);
1210
+ this.validator = validator;
1211
+ return validator;
941
1212
  }
942
1213
  }
943
1214
 
@@ -945,7 +1216,7 @@ class SpecParser {
945
1216
  class AdaptiveCardGenerator {
946
1217
  static generateAdaptiveCard(operationItem) {
947
1218
  try {
948
- const json = Utils.getResponseJson(operationItem);
1219
+ const { json } = Utils.getResponseJson(operationItem);
949
1220
  let cardBody = [];
950
1221
  let schema = json.schema;
951
1222
  let jsonPath = "$";