@microsoft/m365-spec-parser 0.1.1-alpha.b015b287e.0 → 0.1.1-alpha.b389a0e82.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 {
@@ -718,16 +501,49 @@ class Utils {
718
501
  };
719
502
  return command;
720
503
  }
721
- static listAPIs(spec, options) {
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;
518
+ }
519
+ return safeRegistrationIdEnvName;
520
+ }
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() {
722
535
  var _a;
723
- const paths = spec.paths;
536
+ if (this.apiMap) {
537
+ return this.apiMap;
538
+ }
539
+ const paths = this.spec.paths;
724
540
  const result = {};
725
541
  for (const path in paths) {
726
542
  const methods = paths[path];
727
543
  for (const method in methods) {
728
544
  const operationObject = methods[method];
729
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
730
- 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);
731
547
  result[`${method.toUpperCase()} ${path}`] = {
732
548
  operation: operationObject,
733
549
  isValid: validateResult.isValid,
@@ -736,38 +552,48 @@ class Utils {
736
552
  }
737
553
  }
738
554
  }
555
+ this.apiMap = result;
739
556
  return result;
740
557
  }
741
- static validateSpec(spec, parser, isSwaggerFile, options) {
742
- const errors = [];
743
- const warnings = [];
744
- const apiMap = Utils.listAPIs(spec, options);
745
- if (isSwaggerFile) {
746
- warnings.push({
747
- type: WarningType.ConvertSwaggerToOpenAPI,
748
- content: ConstantString.ConvertSwaggerToOpenAPI,
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,
749
565
  });
750
566
  }
751
- const serverErrors = Utils.validateServer(spec, options);
752
- errors.push(...serverErrors);
753
- // Remote reference not supported
754
- const refPaths = parser.$refs.paths();
755
- // refPaths [0] is the current spec file path
756
- if (refPaths.length > 1) {
757
- errors.push({
758
- type: ErrorType.RemoteRefNotSupported,
759
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
760
- data: refPaths,
761
- });
762
- }
763
- // 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();
764
578
  const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
765
579
  if (validAPIs.length === 0) {
766
- 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({
767
587
  type: ErrorType.NoSupportedApi,
768
588
  content: ConstantString.NoSupportedApi,
589
+ data,
769
590
  });
770
591
  }
592
+ return result;
593
+ }
594
+ validateSpecOperationId() {
595
+ const result = { errors: [], warnings: [] };
596
+ const apiMap = this.listAPIs();
771
597
  // OperationId missing
772
598
  const apisMissingOperationId = [];
773
599
  for (const key in apiMap) {
@@ -777,54 +603,430 @@ class Utils {
777
603
  }
778
604
  }
779
605
  if (apisMissingOperationId.length > 0) {
780
- warnings.push({
606
+ result.warnings.push({
781
607
  type: WarningType.OperationIdMissing,
782
608
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
783
609
  data: apisMissingOperationId,
784
610
  });
785
611
  }
786
- let status = ValidationStatus.Valid;
787
- if (warnings.length > 0 && errors.length === 0) {
788
- 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;
789
620
  }
790
- else if (errors.length > 0) {
791
- 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;
792
626
  }
793
- return {
794
- status,
795
- warnings,
796
- errors,
797
- };
627
+ return result;
798
628
  }
799
- static format(str, ...args) {
800
- let index = 0;
801
- return str.replace(/%s/g, () => {
802
- const arg = args[index++];
803
- return arg !== undefined ? arg : "";
804
- });
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;
805
644
  }
806
- static getSafeRegistrationIdEnvName(authName) {
807
- if (!authName) {
808
- 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);
809
651
  }
810
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
811
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
812
- 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));
813
656
  }
814
- return safeRegistrationIdEnvName;
657
+ return result;
815
658
  }
816
- static getAllAPICount(spec) {
817
- let count = 0;
818
- const paths = spec.paths;
819
- for (const path in paths) {
820
- const methods = paths[path];
821
- for (const method in methods) {
822
- if (ConstantString.AllOperationMethods.includes(method)) {
823
- 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;
824
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);
825
730
  }
826
731
  }
827
- return count;
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;
792
+ }
793
+ }
794
+ }
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
+ }
828
1030
  }
829
1031
  }
830
1032
 
@@ -847,7 +1049,11 @@ class SpecParser {
847
1049
  allowBearerTokenAuth: false,
848
1050
  allowOauth2: false,
849
1051
  allowMethods: ["get", "post"],
1052
+ allowConversationStarters: false,
1053
+ allowResponseSemantics: false,
1054
+ allowConfirmation: false,
850
1055
  projectType: ProjectType.SME,
1056
+ isGptPlugin: false,
851
1057
  };
852
1058
  this.pathOrSpec = pathOrDoc;
853
1059
  this.parser = new SwaggerParser();
@@ -862,11 +1068,7 @@ class SpecParser {
862
1068
  try {
863
1069
  try {
864
1070
  await this.loadSpec();
865
- await this.parser.validate(this.spec, {
866
- validate: {
867
- schema: false,
868
- },
869
- });
1071
+ await this.parser.validate(this.spec);
870
1072
  }
871
1073
  catch (e) {
872
1074
  return {
@@ -875,16 +1077,46 @@ class SpecParser {
875
1077
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
876
1078
  };
877
1079
  }
1080
+ const errors = [];
1081
+ const warnings = [];
878
1082
  if (!this.options.allowSwagger && this.isSwaggerFile) {
879
1083
  return {
880
1084
  status: ValidationStatus.Error,
881
1085
  warnings: [],
882
1086
  errors: [
883
- { type: ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
1087
+ {
1088
+ type: ErrorType.SwaggerNotSupported,
1089
+ content: ConstantString.SwaggerNotSupported,
1090
+ },
884
1091
  ],
885
1092
  };
886
1093
  }
887
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1094
+ // Remote reference not supported
1095
+ const refPaths = this.parser.$refs.paths();
1096
+ // refPaths [0] is the current spec file path
1097
+ if (refPaths.length > 1) {
1098
+ errors.push({
1099
+ type: ErrorType.RemoteRefNotSupported,
1100
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1101
+ data: refPaths,
1102
+ });
1103
+ }
1104
+ const validator = this.getValidator(this.spec);
1105
+ const validationResult = validator.validateSpec();
1106
+ warnings.push(...validationResult.warnings);
1107
+ errors.push(...validationResult.errors);
1108
+ let status = ValidationStatus.Valid;
1109
+ if (warnings.length > 0 && errors.length === 0) {
1110
+ status = ValidationStatus.Warning;
1111
+ }
1112
+ else if (errors.length > 0) {
1113
+ status = ValidationStatus.Error;
1114
+ }
1115
+ return {
1116
+ status: status,
1117
+ warnings: warnings,
1118
+ errors: errors,
1119
+ };
888
1120
  }
889
1121
  catch (err) {
890
1122
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -893,17 +1125,20 @@ class SpecParser {
893
1125
  async listSupportedAPIInfo() {
894
1126
  try {
895
1127
  await this.loadSpec();
896
- const apiMap = this.getAllSupportedAPIs(this.spec);
1128
+ const apiMap = this.getAPIs(this.spec);
897
1129
  const apiInfos = [];
898
1130
  for (const key in apiMap) {
899
- const pathObjectItem = apiMap[key];
1131
+ const { operation, isValid } = apiMap[key];
1132
+ if (!isValid) {
1133
+ continue;
1134
+ }
900
1135
  const [method, path] = key.split(" ");
901
- const operationId = pathObjectItem.operationId;
1136
+ const operationId = operation.operationId;
902
1137
  // In Browser environment, this api is by default not support api without operationId
903
1138
  if (!operationId) {
904
1139
  continue;
905
1140
  }
906
- const command = Utils.parseApiInfo(pathObjectItem, this.options);
1141
+ const command = Utils.parseApiInfo(operation, this.options);
907
1142
  const apiInfo = {
908
1143
  method: method,
909
1144
  path: path,
@@ -970,31 +1205,18 @@ class SpecParser {
970
1205
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
971
1206
  }
972
1207
  }
973
- getAllSupportedAPIs(spec) {
974
- if (this.apiMap !== undefined) {
975
- return this.apiMap;
976
- }
977
- const result = this.listSupportedAPIs(spec, this.options);
978
- this.apiMap = result;
979
- return result;
1208
+ getAPIs(spec) {
1209
+ const validator = this.getValidator(spec);
1210
+ const apiMap = validator.listAPIs();
1211
+ return apiMap;
980
1212
  }
981
- listSupportedAPIs(spec, options) {
982
- var _a;
983
- const paths = spec.paths;
984
- const result = {};
985
- for (const path in paths) {
986
- const methods = paths[path];
987
- for (const method in methods) {
988
- const operationObject = methods[method];
989
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
990
- const validateResult = Utils.isSupportedApi(method, path, spec, options);
991
- if (validateResult.isValid) {
992
- result[`${method.toUpperCase()} ${path}`] = operationObject;
993
- }
994
- }
995
- }
1213
+ getValidator(spec) {
1214
+ if (this.validator) {
1215
+ return this.validator;
996
1216
  }
997
- return result;
1217
+ const validator = ValidatorFactory.create(spec, this.options);
1218
+ this.validator = validator;
1219
+ return validator;
998
1220
  }
999
1221
  }
1000
1222