@microsoft/m365-spec-parser 0.1.1-alpha.54a90c74e.0 → 0.1.1-alpha.5fc8ceacd.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 = {}));
@@ -155,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
155
170
  ConstantString.ParameterDescriptionMaxLens = 128;
156
171
  ConstantString.CommandTitleMaxLens = 32;
157
172
  ConstantString.ParameterTitleMaxLens = 32;
158
- ConstantString.SMERequiredParamsMaxNum = 5;
173
+ ConstantString.SMERequiredParamsMaxNum = 5;
174
+ ConstantString.DefaultPluginId = "plugin_1";
159
175
 
160
176
  // Copyright (c) Microsoft Corporation.
161
177
  class Utils {
@@ -170,221 +186,9 @@ class Utils {
170
186
  }
171
187
  return false;
172
188
  }
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
189
  static containMultipleMediaTypes(bodyObject) {
272
190
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
273
191
  }
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
192
  static isBearerTokenAuth(authScheme) {
389
193
  return authScheme.type === "http" && authScheme.scheme === "bearer";
390
194
  }
@@ -392,10 +196,9 @@ class Utils {
392
196
  return authScheme.type === "apiKey";
393
197
  }
394
198
  static isOAuthWithAuthCodeFlow(authScheme) {
395
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
396
- return true;
397
- }
398
- return false;
199
+ return !!(authScheme.type === "oauth2" &&
200
+ authScheme.flows &&
201
+ authScheme.flows.authorizationCode);
399
202
  }
400
203
  static getAuthArray(securities, spec) {
401
204
  var _a;
@@ -423,14 +226,17 @@ class Utils {
423
226
  static updateFirstLetter(str) {
424
227
  return str.charAt(0).toUpperCase() + str.slice(1);
425
228
  }
426
- static getResponseJson(operationObject, isTeamsAiProject = false) {
229
+ static getResponseJson(operationObject) {
427
230
  var _a, _b;
428
231
  let json = {};
232
+ let multipleMediaType = false;
429
233
  for (const code of ConstantString.ResponseCodeFor20X) {
430
234
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
431
235
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
236
+ multipleMediaType = false;
432
237
  json = responseObject.content["application/json"];
433
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
238
+ if (Utils.containMultipleMediaTypes(responseObject)) {
239
+ multipleMediaType = true;
434
240
  json = {};
435
241
  }
436
242
  else {
@@ -438,7 +244,7 @@ class Utils {
438
244
  }
439
245
  }
440
246
  }
441
- return json;
247
+ return { json, multipleMediaType };
442
248
  }
443
249
  static convertPathToCamelCase(path) {
444
250
  const pathSegments = path.split(/[./{]/);
@@ -458,10 +264,10 @@ class Utils {
458
264
  return undefined;
459
265
  }
460
266
  }
461
- static resolveServerUrl(url) {
267
+ static resolveEnv(str) {
462
268
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
463
- let matches = placeHolderReg.exec(url);
464
- let newUrl = url;
269
+ let matches = placeHolderReg.exec(str);
270
+ let newStr = str;
465
271
  while (matches != null) {
466
272
  const envVar = matches[1];
467
273
  const envVal = process.env[envVar];
@@ -469,17 +275,17 @@ class Utils {
469
275
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
470
276
  }
471
277
  else {
472
- newUrl = newUrl.replace(matches[0], envVal);
278
+ newStr = newStr.replace(matches[0], envVal);
473
279
  }
474
- matches = placeHolderReg.exec(url);
280
+ matches = placeHolderReg.exec(str);
475
281
  }
476
- return newUrl;
282
+ return newStr;
477
283
  }
478
284
  static checkServerUrl(servers) {
479
285
  const errors = [];
480
286
  let serverUrl;
481
287
  try {
482
- serverUrl = Utils.resolveServerUrl(servers[0].url);
288
+ serverUrl = Utils.resolveEnv(servers[0].url);
483
289
  }
484
290
  catch (err) {
485
291
  errors.push({
@@ -510,6 +316,7 @@ class Utils {
510
316
  return errors;
511
317
  }
512
318
  static validateServer(spec, options) {
319
+ var _a;
513
320
  const errors = [];
514
321
  let hasTopLevelServers = false;
515
322
  let hasPathLevelServers = false;
@@ -530,7 +337,7 @@ class Utils {
530
337
  }
531
338
  for (const method in methods) {
532
339
  const operationObject = methods[method];
533
- if (Utils.isSupportedApi(method, path, spec, options)) {
340
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
534
341
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
535
342
  hasOperationLevelServers = true;
536
343
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -657,13 +464,7 @@ class Utils {
657
464
  }
658
465
  }
659
466
  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
- }
467
+ const parameters = [...requiredParams, ...optionalParams];
667
468
  const command = {
668
469
  context: ["compose"],
669
470
  type: "query",
@@ -672,117 +473,535 @@ class Utils {
672
473
  parameters: parameters,
673
474
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
674
475
  };
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
- };
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 "";
682
488
  }
683
- 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;
684
494
  }
685
- static listSupportedAPIs(spec, options) {
686
- 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;
687
514
  const result = {};
688
515
  for (const path in paths) {
689
516
  const methods = paths[path];
690
517
  for (const method in methods) {
691
- if (Utils.isSupportedApi(method, path, spec, options)) {
692
- const operationObject = methods[method];
693
- 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
+ };
694
526
  }
695
527
  }
696
528
  }
529
+ this.apiMap = result;
697
530
  return result;
698
531
  }
699
- static validateSpec(spec, parser, isSwaggerFile, options) {
700
- const errors = [];
701
- const warnings = [];
702
- if (isSwaggerFile) {
703
- warnings.push({
704
- type: WarningType.ConvertSwaggerToOpenAPI,
705
- content: ConstantString.ConvertSwaggerToOpenAPI,
706
- });
707
- }
708
- // Server validation
709
- const serverErrors = Utils.validateServer(spec, options);
710
- errors.push(...serverErrors);
711
- // Remote reference not supported
712
- const refPaths = parser.$refs.paths();
713
- // refPaths [0] is the current spec file path
714
- if (refPaths.length > 1) {
715
- errors.push({
716
- type: ErrorType.RemoteRefNotSupported,
717
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
718
- data: refPaths,
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,
719
539
  });
720
540
  }
721
- // No supported API
722
- const apiMap = Utils.listSupportedAPIs(spec, options);
723
- if (Object.keys(apiMap).length === 0) {
724
- 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({
725
561
  type: ErrorType.NoSupportedApi,
726
562
  content: ConstantString.NoSupportedApi,
563
+ data,
727
564
  });
728
565
  }
566
+ return result;
567
+ }
568
+ validateSpecOperationId() {
569
+ const result = { errors: [], warnings: [] };
570
+ const apiMap = this.listAPIs();
729
571
  // OperationId missing
730
572
  const apisMissingOperationId = [];
731
573
  for (const key in apiMap) {
732
- const pathObjectItem = apiMap[key];
733
- if (!pathObjectItem.operationId) {
574
+ const { operation } = apiMap[key];
575
+ if (!operation.operationId) {
734
576
  apisMissingOperationId.push(key);
735
577
  }
736
578
  }
737
579
  if (apisMissingOperationId.length > 0) {
738
- warnings.push({
580
+ result.warnings.push({
739
581
  type: WarningType.OperationIdMissing,
740
582
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
741
583
  data: apisMissingOperationId,
742
584
  });
743
585
  }
744
- let status = ValidationStatus.Valid;
745
- if (warnings.length > 0 && errors.length === 0) {
746
- 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;
747
594
  }
748
- else if (errors.length > 0) {
749
- 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;
750
600
  }
751
- return {
752
- status,
753
- warnings,
754
- errors,
755
- };
601
+ return result;
756
602
  }
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
- });
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;
763
616
  }
764
- static getSafeRegistrationIdEnvName(authName) {
765
- if (!authName) {
766
- 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);
767
623
  }
768
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
769
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
770
- 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));
771
628
  }
772
- return safeRegistrationIdEnvName;
629
+ return result;
773
630
  }
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++;
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;
782
775
  }
783
776
  }
784
777
  }
785
- 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
+ }
786
1005
  }
787
1006
  }
788
1007
 
@@ -820,11 +1039,7 @@ class SpecParser {
820
1039
  try {
821
1040
  try {
822
1041
  await this.loadSpec();
823
- await this.parser.validate(this.spec, {
824
- validate: {
825
- schema: false,
826
- },
827
- });
1042
+ await this.parser.validate(this.spec);
828
1043
  }
829
1044
  catch (e) {
830
1045
  return {
@@ -833,16 +1048,46 @@ class SpecParser {
833
1048
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
834
1049
  };
835
1050
  }
1051
+ const errors = [];
1052
+ const warnings = [];
836
1053
  if (!this.options.allowSwagger && this.isSwaggerFile) {
837
1054
  return {
838
1055
  status: ValidationStatus.Error,
839
1056
  warnings: [],
840
1057
  errors: [
841
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1058
+ {
1059
+ type: ErrorType.SwaggerNotSupported,
1060
+ content: ConstantString.SwaggerNotSupported,
1061
+ },
842
1062
  ],
843
1063
  };
844
1064
  }
845
- 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
+ };
846
1091
  }
847
1092
  catch (err) {
848
1093
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -851,17 +1096,20 @@ class SpecParser {
851
1096
  async listSupportedAPIInfo() {
852
1097
  try {
853
1098
  await this.loadSpec();
854
- const apiMap = this.getAllSupportedAPIs(this.spec);
1099
+ const apiMap = this.getAPIs(this.spec);
855
1100
  const apiInfos = [];
856
1101
  for (const key in apiMap) {
857
- const pathObjectItem = apiMap[key];
1102
+ const { operation, isValid } = apiMap[key];
1103
+ if (!isValid) {
1104
+ continue;
1105
+ }
858
1106
  const [method, path] = key.split(" ");
859
- const operationId = pathObjectItem.operationId;
1107
+ const operationId = operation.operationId;
860
1108
  // In Browser environment, this api is by default not support api without operationId
861
1109
  if (!operationId) {
862
1110
  continue;
863
1111
  }
864
- const [command, warning] = Utils.parseApiInfo(pathObjectItem, this.options);
1112
+ const command = Utils.parseApiInfo(operation, this.options);
865
1113
  const apiInfo = {
866
1114
  method: method,
867
1115
  path: path,
@@ -870,9 +1118,6 @@ class SpecParser {
870
1118
  parameters: command.parameters,
871
1119
  description: command.description,
872
1120
  };
873
- if (warning) {
874
- apiInfo.warning = warning;
875
- }
876
1121
  apiInfos.push(apiInfo);
877
1122
  }
878
1123
  return apiInfos;
@@ -931,13 +1176,22 @@ class SpecParser {
931
1176
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
932
1177
  }
933
1178
  }
934
- getAllSupportedAPIs(spec) {
1179
+ getAPIs(spec) {
935
1180
  if (this.apiMap !== undefined) {
936
1181
  return this.apiMap;
937
1182
  }
938
- const result = Utils.listSupportedAPIs(spec, this.options);
939
- this.apiMap = result;
940
- 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;
941
1195
  }
942
1196
  }
943
1197
 
@@ -945,7 +1199,7 @@ class SpecParser {
945
1199
  class AdaptiveCardGenerator {
946
1200
  static generateAdaptiveCard(operationItem) {
947
1201
  try {
948
- const json = Utils.getResponseJson(operationItem);
1202
+ const { json } = Utils.getResponseJson(operationItem);
949
1203
  let cardBody = [];
950
1204
  let schema = json.schema;
951
1205
  let jsonPath = "$";