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