@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.42af26ca6.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";
@@ -168,221 +185,9 @@ class Utils {
168
185
  }
169
186
  return false;
170
187
  }
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
188
  static containMultipleMediaTypes(bodyObject) {
270
189
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
271
190
  }
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
191
  static isBearerTokenAuth(authScheme) {
387
192
  return authScheme.type === "http" && authScheme.scheme === "bearer";
388
193
  }
@@ -390,10 +195,9 @@ class Utils {
390
195
  return authScheme.type === "apiKey";
391
196
  }
392
197
  static isOAuthWithAuthCodeFlow(authScheme) {
393
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
394
- return true;
395
- }
396
- return false;
198
+ return !!(authScheme.type === "oauth2" &&
199
+ authScheme.flows &&
200
+ authScheme.flows.authorizationCode);
397
201
  }
398
202
  static getAuthArray(securities, spec) {
399
203
  var _a;
@@ -421,14 +225,17 @@ class Utils {
421
225
  static updateFirstLetter(str) {
422
226
  return str.charAt(0).toUpperCase() + str.slice(1);
423
227
  }
424
- static getResponseJson(operationObject, isTeamsAiProject = false) {
228
+ static getResponseJson(operationObject) {
425
229
  var _a, _b;
426
230
  let json = {};
231
+ let multipleMediaType = false;
427
232
  for (const code of ConstantString.ResponseCodeFor20X) {
428
233
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
429
234
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
235
+ multipleMediaType = false;
430
236
  json = responseObject.content["application/json"];
431
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
237
+ if (Utils.containMultipleMediaTypes(responseObject)) {
238
+ multipleMediaType = true;
432
239
  json = {};
433
240
  }
434
241
  else {
@@ -436,7 +243,7 @@ class Utils {
436
243
  }
437
244
  }
438
245
  }
439
- return json;
246
+ return { json, multipleMediaType };
440
247
  }
441
248
  static convertPathToCamelCase(path) {
442
249
  const pathSegments = path.split(/[./{]/);
@@ -456,10 +263,10 @@ class Utils {
456
263
  return undefined;
457
264
  }
458
265
  }
459
- static resolveServerUrl(url) {
266
+ static resolveEnv(str) {
460
267
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
461
- let matches = placeHolderReg.exec(url);
462
- let newUrl = url;
268
+ let matches = placeHolderReg.exec(str);
269
+ let newStr = str;
463
270
  while (matches != null) {
464
271
  const envVar = matches[1];
465
272
  const envVal = process.env[envVar];
@@ -467,17 +274,17 @@ class Utils {
467
274
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
468
275
  }
469
276
  else {
470
- newUrl = newUrl.replace(matches[0], envVal);
277
+ newStr = newStr.replace(matches[0], envVal);
471
278
  }
472
- matches = placeHolderReg.exec(url);
279
+ matches = placeHolderReg.exec(str);
473
280
  }
474
- return newUrl;
281
+ return newStr;
475
282
  }
476
283
  static checkServerUrl(servers) {
477
284
  const errors = [];
478
285
  let serverUrl;
479
286
  try {
480
- serverUrl = Utils.resolveServerUrl(servers[0].url);
287
+ serverUrl = Utils.resolveEnv(servers[0].url);
481
288
  }
482
289
  catch (err) {
483
290
  errors.push({
@@ -508,6 +315,7 @@ class Utils {
508
315
  return errors;
509
316
  }
510
317
  static validateServer(spec, options) {
318
+ var _a;
511
319
  const errors = [];
512
320
  let hasTopLevelServers = false;
513
321
  let hasPathLevelServers = false;
@@ -528,7 +336,7 @@ class Utils {
528
336
  }
529
337
  for (const method in methods) {
530
338
  const operationObject = methods[method];
531
- if (Utils.isSupportedApi(method, path, spec, options)) {
339
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
532
340
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
533
341
  hasOperationLevelServers = true;
534
342
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -655,13 +463,7 @@ class Utils {
655
463
  }
656
464
  }
657
465
  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
- }
466
+ const parameters = [...requiredParams, ...optionalParams];
665
467
  const command = {
666
468
  context: ["compose"],
667
469
  type: "query",
@@ -670,117 +472,526 @@ class Utils {
670
472
  parameters: parameters,
671
473
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
672
474
  };
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
- };
475
+ return command;
476
+ }
477
+ static format(str, ...args) {
478
+ let index = 0;
479
+ return str.replace(/%s/g, () => {
480
+ const arg = args[index++];
481
+ return arg !== undefined ? arg : "";
482
+ });
483
+ }
484
+ static getSafeRegistrationIdEnvName(authName) {
485
+ if (!authName) {
486
+ return "";
680
487
  }
681
- return [command, warning];
488
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
489
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
490
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
491
+ }
492
+ return safeRegistrationIdEnvName;
682
493
  }
683
- static listSupportedAPIs(spec, options) {
684
- const paths = spec.paths;
494
+ static getServerObject(spec, method, path) {
495
+ const pathObj = spec.paths[path];
496
+ const operationObject = pathObj[method];
497
+ const rootServer = spec.servers && spec.servers[0];
498
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
499
+ const operationServer = operationObject.servers && operationObject.servers[0];
500
+ const serverUrl = operationServer || methodServer || rootServer;
501
+ return serverUrl;
502
+ }
503
+ }
504
+
505
+ // Copyright (c) Microsoft Corporation.
506
+ class Validator {
507
+ listAPIs() {
508
+ var _a;
509
+ if (this.apiMap) {
510
+ return this.apiMap;
511
+ }
512
+ const paths = this.spec.paths;
685
513
  const result = {};
686
514
  for (const path in paths) {
687
515
  const methods = paths[path];
688
516
  for (const method in methods) {
689
- if (Utils.isSupportedApi(method, path, spec, options)) {
690
- const operationObject = methods[method];
691
- result[`${method.toUpperCase()} ${path}`] = operationObject;
517
+ const operationObject = methods[method];
518
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
519
+ const validateResult = this.validateAPI(method, path);
520
+ result[`${method.toUpperCase()} ${path}`] = {
521
+ operation: operationObject,
522
+ isValid: validateResult.isValid,
523
+ reason: validateResult.reason,
524
+ };
692
525
  }
693
526
  }
694
527
  }
528
+ this.apiMap = result;
695
529
  return result;
696
530
  }
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,
531
+ validateSpecVersion() {
532
+ const result = { errors: [], warnings: [] };
533
+ if (this.spec.openapi >= "3.1.0") {
534
+ result.errors.push({
535
+ type: ErrorType.SpecVersionNotSupported,
536
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
537
+ data: this.spec.openapi,
717
538
  });
718
539
  }
719
- // No supported API
720
- const apiMap = Utils.listSupportedAPIs(spec, options);
721
- if (Object.keys(apiMap).length === 0) {
722
- errors.push({
540
+ return result;
541
+ }
542
+ validateSpecServer() {
543
+ const result = { errors: [], warnings: [] };
544
+ const serverErrors = Utils.validateServer(this.spec, this.options);
545
+ result.errors.push(...serverErrors);
546
+ return result;
547
+ }
548
+ validateSpecNoSupportAPI() {
549
+ const result = { errors: [], warnings: [] };
550
+ const apiMap = this.listAPIs();
551
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
552
+ if (validAPIs.length === 0) {
553
+ result.errors.push({
723
554
  type: ErrorType.NoSupportedApi,
724
555
  content: ConstantString.NoSupportedApi,
725
556
  });
726
557
  }
558
+ return result;
559
+ }
560
+ validateSpecOperationId() {
561
+ const result = { errors: [], warnings: [] };
562
+ const apiMap = this.listAPIs();
727
563
  // OperationId missing
728
564
  const apisMissingOperationId = [];
729
565
  for (const key in apiMap) {
730
- const pathObjectItem = apiMap[key];
731
- if (!pathObjectItem.operationId) {
566
+ const { operation } = apiMap[key];
567
+ if (!operation.operationId) {
732
568
  apisMissingOperationId.push(key);
733
569
  }
734
570
  }
735
571
  if (apisMissingOperationId.length > 0) {
736
- warnings.push({
572
+ result.warnings.push({
737
573
  type: WarningType.OperationIdMissing,
738
574
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
739
575
  data: apisMissingOperationId,
740
576
  });
741
577
  }
742
- let status = ValidationStatus.Valid;
743
- if (warnings.length > 0 && errors.length === 0) {
744
- status = ValidationStatus.Warning;
578
+ return result;
579
+ }
580
+ validateMethodAndPath(method, path) {
581
+ const result = { isValid: true, reason: [] };
582
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
583
+ result.isValid = false;
584
+ result.reason.push(ErrorType.MethodNotAllowed);
585
+ return result;
745
586
  }
746
- else if (errors.length > 0) {
747
- status = ValidationStatus.Error;
587
+ const pathObj = this.spec.paths[path];
588
+ if (!pathObj || !pathObj[method]) {
589
+ result.isValid = false;
590
+ result.reason.push(ErrorType.UrlPathNotExist);
591
+ return result;
748
592
  }
749
- return {
750
- status,
751
- warnings,
752
- errors,
753
- };
593
+ return result;
754
594
  }
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
- });
595
+ validateResponse(method, path) {
596
+ const result = { isValid: true, reason: [] };
597
+ const operationObject = this.spec.paths[path][method];
598
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
599
+ // only support response body only contains “application/json” content type
600
+ if (multipleMediaType) {
601
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
602
+ }
603
+ else if (Object.keys(json).length === 0) {
604
+ // response body should not be empty
605
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
606
+ }
607
+ return result;
761
608
  }
762
- static getSafeRegistrationIdEnvName(authName) {
763
- if (!authName) {
764
- return "";
609
+ validateServer(method, path) {
610
+ const result = { isValid: true, reason: [] };
611
+ const serverObj = Utils.getServerObject(this.spec, method, path);
612
+ if (!serverObj) {
613
+ // should contain server URL
614
+ result.reason.push(ErrorType.NoServerInformation);
765
615
  }
766
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
767
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
768
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
616
+ else {
617
+ // server url should be absolute url with https protocol
618
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
619
+ result.reason.push(...serverValidateResult.map((item) => item.type));
769
620
  }
770
- return safeRegistrationIdEnvName;
621
+ return result;
771
622
  }
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++;
623
+ validateAuth(method, path) {
624
+ const pathObj = this.spec.paths[path];
625
+ const operationObject = pathObj[method];
626
+ const securities = operationObject.security;
627
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
628
+ if (authSchemeArray.length === 0) {
629
+ return { isValid: true, reason: [] };
630
+ }
631
+ if (this.options.allowAPIKeyAuth ||
632
+ this.options.allowOauth2 ||
633
+ this.options.allowBearerTokenAuth) {
634
+ // Currently we don't support multiple auth in one operation
635
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
636
+ return {
637
+ isValid: false,
638
+ reason: [ErrorType.MultipleAuthNotSupported],
639
+ };
640
+ }
641
+ for (const auths of authSchemeArray) {
642
+ if (auths.length === 1) {
643
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
644
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
645
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
646
+ return { isValid: true, reason: [] };
647
+ }
648
+ }
649
+ }
650
+ }
651
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
652
+ }
653
+ checkPostBodySchema(schema, isRequired = false) {
654
+ var _a;
655
+ const paramResult = {
656
+ requiredNum: 0,
657
+ optionalNum: 0,
658
+ isValid: true,
659
+ reason: [],
660
+ };
661
+ if (Object.keys(schema).length === 0) {
662
+ return paramResult;
663
+ }
664
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
665
+ const isCopilot = this.projectType === ProjectType.Copilot;
666
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
667
+ paramResult.isValid = false;
668
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
669
+ return paramResult;
670
+ }
671
+ if (schema.type === "string" ||
672
+ schema.type === "integer" ||
673
+ schema.type === "boolean" ||
674
+ schema.type === "number") {
675
+ if (isRequiredWithoutDefault) {
676
+ paramResult.requiredNum = paramResult.requiredNum + 1;
677
+ }
678
+ else {
679
+ paramResult.optionalNum = paramResult.optionalNum + 1;
680
+ }
681
+ }
682
+ else if (schema.type === "object") {
683
+ const { properties } = schema;
684
+ for (const property in properties) {
685
+ let isRequired = false;
686
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
687
+ isRequired = true;
688
+ }
689
+ const result = this.checkPostBodySchema(properties[property], isRequired);
690
+ paramResult.requiredNum += result.requiredNum;
691
+ paramResult.optionalNum += result.optionalNum;
692
+ paramResult.isValid = paramResult.isValid && result.isValid;
693
+ paramResult.reason.push(...result.reason);
694
+ }
695
+ }
696
+ else {
697
+ if (isRequiredWithoutDefault && !isCopilot) {
698
+ paramResult.isValid = false;
699
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
700
+ }
701
+ }
702
+ return paramResult;
703
+ }
704
+ checkParamSchema(paramObject) {
705
+ const paramResult = {
706
+ requiredNum: 0,
707
+ optionalNum: 0,
708
+ isValid: true,
709
+ reason: [],
710
+ };
711
+ if (!paramObject) {
712
+ return paramResult;
713
+ }
714
+ const isCopilot = this.projectType === ProjectType.Copilot;
715
+ for (let i = 0; i < paramObject.length; i++) {
716
+ const param = paramObject[i];
717
+ const schema = param.schema;
718
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
719
+ paramResult.isValid = false;
720
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
721
+ continue;
722
+ }
723
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
724
+ if (isCopilot) {
725
+ if (isRequiredWithoutDefault) {
726
+ paramResult.requiredNum = paramResult.requiredNum + 1;
727
+ }
728
+ else {
729
+ paramResult.optionalNum = paramResult.optionalNum + 1;
730
+ }
731
+ continue;
732
+ }
733
+ if (param.in === "header" || param.in === "cookie") {
734
+ if (isRequiredWithoutDefault) {
735
+ paramResult.isValid = false;
736
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
737
+ }
738
+ continue;
739
+ }
740
+ if (schema.type !== "boolean" &&
741
+ schema.type !== "string" &&
742
+ schema.type !== "number" &&
743
+ schema.type !== "integer") {
744
+ if (isRequiredWithoutDefault) {
745
+ paramResult.isValid = false;
746
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
747
+ }
748
+ continue;
749
+ }
750
+ if (param.in === "query" || param.in === "path") {
751
+ if (isRequiredWithoutDefault) {
752
+ paramResult.requiredNum = paramResult.requiredNum + 1;
753
+ }
754
+ else {
755
+ paramResult.optionalNum = paramResult.optionalNum + 1;
756
+ }
757
+ }
758
+ }
759
+ return paramResult;
760
+ }
761
+ hasNestedObjectInSchema(schema) {
762
+ if (schema.type === "object") {
763
+ for (const property in schema.properties) {
764
+ const nestedSchema = schema.properties[property];
765
+ if (nestedSchema.type === "object") {
766
+ return true;
780
767
  }
781
768
  }
782
769
  }
783
- return count;
770
+ return false;
771
+ }
772
+ }
773
+
774
+ // Copyright (c) Microsoft Corporation.
775
+ class CopilotValidator extends Validator {
776
+ constructor(spec, options) {
777
+ super();
778
+ this.projectType = ProjectType.Copilot;
779
+ this.options = options;
780
+ this.spec = spec;
781
+ }
782
+ validateSpec() {
783
+ const result = { errors: [], warnings: [] };
784
+ // validate spec version
785
+ let validationResult = this.validateSpecVersion();
786
+ result.errors.push(...validationResult.errors);
787
+ // validate spec server
788
+ validationResult = this.validateSpecServer();
789
+ result.errors.push(...validationResult.errors);
790
+ // validate no supported API
791
+ validationResult = this.validateSpecNoSupportAPI();
792
+ result.errors.push(...validationResult.errors);
793
+ // validate operationId missing
794
+ validationResult = this.validateSpecOperationId();
795
+ result.warnings.push(...validationResult.warnings);
796
+ return result;
797
+ }
798
+ validateAPI(method, path) {
799
+ const result = { isValid: true, reason: [] };
800
+ method = method.toLocaleLowerCase();
801
+ // validate method and path
802
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
803
+ if (!methodAndPathResult.isValid) {
804
+ return methodAndPathResult;
805
+ }
806
+ const operationObject = this.spec.paths[path][method];
807
+ // validate auth
808
+ const authCheckResult = this.validateAuth(method, path);
809
+ result.reason.push(...authCheckResult.reason);
810
+ // validate operationId
811
+ if (!this.options.allowMissingId && !operationObject.operationId) {
812
+ result.reason.push(ErrorType.MissingOperationId);
813
+ }
814
+ // validate server
815
+ const validateServerResult = this.validateServer(method, path);
816
+ result.reason.push(...validateServerResult.reason);
817
+ // validate response
818
+ const validateResponseResult = this.validateResponse(method, path);
819
+ result.reason.push(...validateResponseResult.reason);
820
+ // validate requestBody
821
+ const requestBody = operationObject.requestBody;
822
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
823
+ if (Utils.containMultipleMediaTypes(requestBody)) {
824
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
825
+ }
826
+ if (requestJsonBody) {
827
+ const requestBodySchema = requestJsonBody.schema;
828
+ if (requestBodySchema.type !== "object") {
829
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
830
+ }
831
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
832
+ result.reason.push(...requestBodyParamResult.reason);
833
+ }
834
+ // validate parameters
835
+ const paramObject = operationObject.parameters;
836
+ const paramResult = this.checkParamSchema(paramObject);
837
+ result.reason.push(...paramResult.reason);
838
+ if (result.reason.length > 0) {
839
+ result.isValid = false;
840
+ }
841
+ return result;
842
+ }
843
+ }
844
+
845
+ // Copyright (c) Microsoft Corporation.
846
+ class SMEValidator extends Validator {
847
+ constructor(spec, options) {
848
+ super();
849
+ this.projectType = ProjectType.SME;
850
+ this.options = options;
851
+ this.spec = spec;
852
+ }
853
+ validateSpec() {
854
+ const result = { errors: [], warnings: [] };
855
+ // validate spec version
856
+ let validationResult = this.validateSpecVersion();
857
+ result.errors.push(...validationResult.errors);
858
+ // validate spec server
859
+ validationResult = this.validateSpecServer();
860
+ result.errors.push(...validationResult.errors);
861
+ // validate no supported API
862
+ validationResult = this.validateSpecNoSupportAPI();
863
+ result.errors.push(...validationResult.errors);
864
+ // validate operationId missing
865
+ validationResult = this.validateSpecOperationId();
866
+ result.warnings.push(...validationResult.warnings);
867
+ return result;
868
+ }
869
+ validateAPI(method, path) {
870
+ const result = { isValid: true, reason: [] };
871
+ method = method.toLocaleLowerCase();
872
+ // validate method and path
873
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
874
+ if (!methodAndPathResult.isValid) {
875
+ return methodAndPathResult;
876
+ }
877
+ const operationObject = this.spec.paths[path][method];
878
+ // validate auth
879
+ const authCheckResult = this.validateAuth(method, path);
880
+ result.reason.push(...authCheckResult.reason);
881
+ // validate operationId
882
+ if (!this.options.allowMissingId && !operationObject.operationId) {
883
+ result.reason.push(ErrorType.MissingOperationId);
884
+ }
885
+ // validate server
886
+ const validateServerResult = this.validateServer(method, path);
887
+ result.reason.push(...validateServerResult.reason);
888
+ // validate response
889
+ const validateResponseResult = this.validateResponse(method, path);
890
+ result.reason.push(...validateResponseResult.reason);
891
+ let postBodyResult = {
892
+ requiredNum: 0,
893
+ optionalNum: 0,
894
+ isValid: true,
895
+ reason: [],
896
+ };
897
+ // validate requestBody
898
+ const requestBody = operationObject.requestBody;
899
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
900
+ if (Utils.containMultipleMediaTypes(requestBody)) {
901
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
902
+ }
903
+ if (requestJsonBody) {
904
+ const requestBodySchema = requestJsonBody.schema;
905
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
906
+ result.reason.push(...postBodyResult.reason);
907
+ }
908
+ // validate parameters
909
+ const paramObject = operationObject.parameters;
910
+ const paramResult = this.checkParamSchema(paramObject);
911
+ result.reason.push(...paramResult.reason);
912
+ // validate total parameters count
913
+ if (paramResult.isValid && postBodyResult.isValid) {
914
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
915
+ result.reason.push(...paramCountResult.reason);
916
+ }
917
+ if (result.reason.length > 0) {
918
+ result.isValid = false;
919
+ }
920
+ return result;
921
+ }
922
+ validateParamCount(postBodyResult, paramResult) {
923
+ const result = { isValid: true, reason: [] };
924
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
925
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
926
+ if (totalRequiredParams > 1) {
927
+ if (!this.options.allowMultipleParameters ||
928
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
929
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
930
+ }
931
+ }
932
+ else if (totalParams === 0) {
933
+ result.reason.push(ErrorType.NoParameter);
934
+ }
935
+ return result;
936
+ }
937
+ }
938
+ SMEValidator.SMERequiredParamsMaxNum = 5;
939
+
940
+ // Copyright (c) Microsoft Corporation.
941
+ class TeamsAIValidator extends Validator {
942
+ constructor(spec, options) {
943
+ super();
944
+ this.projectType = ProjectType.TeamsAi;
945
+ this.options = options;
946
+ this.spec = spec;
947
+ }
948
+ validateSpec() {
949
+ const result = { errors: [], warnings: [] };
950
+ // validate spec server
951
+ let validationResult = this.validateSpecServer();
952
+ result.errors.push(...validationResult.errors);
953
+ // validate no supported API
954
+ validationResult = this.validateSpecNoSupportAPI();
955
+ result.errors.push(...validationResult.errors);
956
+ return result;
957
+ }
958
+ validateAPI(method, path) {
959
+ const result = { isValid: true, reason: [] };
960
+ method = method.toLocaleLowerCase();
961
+ // validate method and path
962
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
963
+ if (!methodAndPathResult.isValid) {
964
+ return methodAndPathResult;
965
+ }
966
+ const operationObject = this.spec.paths[path][method];
967
+ // validate operationId
968
+ if (!this.options.allowMissingId && !operationObject.operationId) {
969
+ result.reason.push(ErrorType.MissingOperationId);
970
+ }
971
+ // validate server
972
+ const validateServerResult = this.validateServer(method, path);
973
+ result.reason.push(...validateServerResult.reason);
974
+ if (result.reason.length > 0) {
975
+ result.isValid = false;
976
+ }
977
+ return result;
978
+ }
979
+ }
980
+
981
+ class ValidatorFactory {
982
+ static create(spec, options) {
983
+ var _a;
984
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
985
+ switch (type) {
986
+ case ProjectType.SME:
987
+ return new SMEValidator(spec, options);
988
+ case ProjectType.Copilot:
989
+ return new CopilotValidator(spec, options);
990
+ case ProjectType.TeamsAi:
991
+ return new TeamsAIValidator(spec, options);
992
+ default:
993
+ throw new Error(`Invalid project type: ${type}`);
994
+ }
784
995
  }
785
996
  }
786
997
 
@@ -818,11 +1029,7 @@ class SpecParser {
818
1029
  try {
819
1030
  try {
820
1031
  await this.loadSpec();
821
- await this.parser.validate(this.spec, {
822
- validate: {
823
- schema: false,
824
- },
825
- });
1032
+ await this.parser.validate(this.spec);
826
1033
  }
827
1034
  catch (e) {
828
1035
  return {
@@ -831,16 +1038,46 @@ class SpecParser {
831
1038
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
832
1039
  };
833
1040
  }
1041
+ const errors = [];
1042
+ const warnings = [];
834
1043
  if (!this.options.allowSwagger && this.isSwaggerFile) {
835
1044
  return {
836
1045
  status: ValidationStatus.Error,
837
1046
  warnings: [],
838
1047
  errors: [
839
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1048
+ {
1049
+ type: ErrorType.SwaggerNotSupported,
1050
+ content: ConstantString.SwaggerNotSupported,
1051
+ },
840
1052
  ],
841
1053
  };
842
1054
  }
843
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1055
+ // Remote reference not supported
1056
+ const refPaths = this.parser.$refs.paths();
1057
+ // refPaths [0] is the current spec file path
1058
+ if (refPaths.length > 1) {
1059
+ errors.push({
1060
+ type: ErrorType.RemoteRefNotSupported,
1061
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1062
+ data: refPaths,
1063
+ });
1064
+ }
1065
+ const validator = this.getValidator(this.spec);
1066
+ const validationResult = validator.validateSpec();
1067
+ warnings.push(...validationResult.warnings);
1068
+ errors.push(...validationResult.errors);
1069
+ let status = ValidationStatus.Valid;
1070
+ if (warnings.length > 0 && errors.length === 0) {
1071
+ status = ValidationStatus.Warning;
1072
+ }
1073
+ else if (errors.length > 0) {
1074
+ status = ValidationStatus.Error;
1075
+ }
1076
+ return {
1077
+ status: status,
1078
+ warnings: warnings,
1079
+ errors: errors,
1080
+ };
844
1081
  }
845
1082
  catch (err) {
846
1083
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -849,17 +1086,20 @@ class SpecParser {
849
1086
  async listSupportedAPIInfo() {
850
1087
  try {
851
1088
  await this.loadSpec();
852
- const apiMap = this.getAllSupportedAPIs(this.spec);
1089
+ const apiMap = this.getAPIs(this.spec);
853
1090
  const apiInfos = [];
854
1091
  for (const key in apiMap) {
855
- const pathObjectItem = apiMap[key];
1092
+ const { operation, isValid } = apiMap[key];
1093
+ if (!isValid) {
1094
+ continue;
1095
+ }
856
1096
  const [method, path] = key.split(" ");
857
- const operationId = pathObjectItem.operationId;
1097
+ const operationId = operation.operationId;
858
1098
  // In Browser environment, this api is by default not support api without operationId
859
1099
  if (!operationId) {
860
1100
  continue;
861
1101
  }
862
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1102
+ const command = Utils.parseApiInfo(operation, this.options);
863
1103
  const apiInfo = {
864
1104
  method: method,
865
1105
  path: path,
@@ -868,9 +1108,6 @@ class SpecParser {
868
1108
  parameters: command.parameters,
869
1109
  description: command.description,
870
1110
  };
871
- if (warning) {
872
- apiInfo.warning = warning;
873
- }
874
1111
  apiInfos.push(apiInfo);
875
1112
  }
876
1113
  return apiInfos;
@@ -929,13 +1166,22 @@ class SpecParser {
929
1166
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
930
1167
  }
931
1168
  }
932
- getAllSupportedAPIs(spec) {
1169
+ getAPIs(spec) {
933
1170
  if (this.apiMap !== undefined) {
934
1171
  return this.apiMap;
935
1172
  }
936
- const result = Utils.listSupportedAPIs(spec, this.options);
937
- this.apiMap = result;
938
- return result;
1173
+ const validator = this.getValidator(spec);
1174
+ const apiMap = validator.listAPIs();
1175
+ this.apiMap = apiMap;
1176
+ return apiMap;
1177
+ }
1178
+ getValidator(spec) {
1179
+ if (this.validator) {
1180
+ return this.validator;
1181
+ }
1182
+ const validator = ValidatorFactory.create(spec, this.options);
1183
+ this.validator = validator;
1184
+ return validator;
939
1185
  }
940
1186
  }
941
1187
 
@@ -943,7 +1189,7 @@ class SpecParser {
943
1189
  class AdaptiveCardGenerator {
944
1190
  static generateAdaptiveCard(operationItem) {
945
1191
  try {
946
- const json = Utils.getResponseJson(operationItem);
1192
+ const { json } = Utils.getResponseJson(operationItem);
947
1193
  let cardBody = [];
948
1194
  let schema = json.schema;
949
1195
  let jsonPath = "$";