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