@microsoft/m365-spec-parser 0.1.1-alpha.4f2290daa.0 → 0.1.1-alpha.6341e718e.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.
- package/dist/index.esm2017.js +408 -283
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +414 -273
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +408 -283
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +414 -273
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/specParser.browser.d.ts +2 -2
- package/dist/src/specParser.d.ts +1 -0
- package/dist/src/utils.d.ts +3 -22
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -185,249 +185,9 @@ class Utils {
|
|
|
185
185
|
}
|
|
186
186
|
return false;
|
|
187
187
|
}
|
|
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
188
|
static containMultipleMediaTypes(bodyObject) {
|
|
295
189
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
296
190
|
}
|
|
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
191
|
static isBearerTokenAuth(authScheme) {
|
|
432
192
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
433
193
|
}
|
|
@@ -435,10 +195,9 @@ class Utils {
|
|
|
435
195
|
return authScheme.type === "apiKey";
|
|
436
196
|
}
|
|
437
197
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return false;
|
|
198
|
+
return !!(authScheme.type === "oauth2" &&
|
|
199
|
+
authScheme.flows &&
|
|
200
|
+
authScheme.flows.authorizationCode);
|
|
442
201
|
}
|
|
443
202
|
static getAuthArray(securities, spec) {
|
|
444
203
|
var _a;
|
|
@@ -466,7 +225,7 @@ class Utils {
|
|
|
466
225
|
static updateFirstLetter(str) {
|
|
467
226
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
468
227
|
}
|
|
469
|
-
static getResponseJson(operationObject
|
|
228
|
+
static getResponseJson(operationObject) {
|
|
470
229
|
var _a, _b;
|
|
471
230
|
let json = {};
|
|
472
231
|
let multipleMediaType = false;
|
|
@@ -477,9 +236,6 @@ class Utils {
|
|
|
477
236
|
json = responseObject.content["application/json"];
|
|
478
237
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
479
238
|
multipleMediaType = true;
|
|
480
|
-
if (isTeamsAiProject) {
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
239
|
json = {};
|
|
484
240
|
}
|
|
485
241
|
else {
|
|
@@ -718,30 +474,9 @@ class Utils {
|
|
|
718
474
|
};
|
|
719
475
|
return command;
|
|
720
476
|
}
|
|
721
|
-
static
|
|
722
|
-
var _a;
|
|
723
|
-
const paths = spec.paths;
|
|
724
|
-
const result = {};
|
|
725
|
-
for (const path in paths) {
|
|
726
|
-
const methods = paths[path];
|
|
727
|
-
for (const method in methods) {
|
|
728
|
-
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);
|
|
731
|
-
result[`${method.toUpperCase()} ${path}`] = {
|
|
732
|
-
operation: operationObject,
|
|
733
|
-
isValid: validateResult.isValid,
|
|
734
|
-
reason: validateResult.reason,
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
return result;
|
|
740
|
-
}
|
|
741
|
-
static validateSpec(spec, parser, isSwaggerFile, options) {
|
|
477
|
+
static validateSpec(spec, parser, apiMap, isSwaggerFile, options) {
|
|
742
478
|
const errors = [];
|
|
743
479
|
const warnings = [];
|
|
744
|
-
const apiMap = Utils.listAPIs(spec, options);
|
|
745
480
|
if (isSwaggerFile) {
|
|
746
481
|
warnings.push({
|
|
747
482
|
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
@@ -828,6 +563,389 @@ class Utils {
|
|
|
828
563
|
}
|
|
829
564
|
}
|
|
830
565
|
|
|
566
|
+
// Copyright (c) Microsoft Corporation.
|
|
567
|
+
class Validator {
|
|
568
|
+
validateMethodAndPath(method, path) {
|
|
569
|
+
const result = { isValid: true, reason: [] };
|
|
570
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
571
|
+
result.isValid = false;
|
|
572
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
const pathObj = this.spec.paths[path];
|
|
576
|
+
if (!pathObj || !pathObj[method]) {
|
|
577
|
+
result.isValid = false;
|
|
578
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
validateResponse(method, path) {
|
|
584
|
+
const result = { isValid: true, reason: [] };
|
|
585
|
+
const operationObject = this.spec.paths[path][method];
|
|
586
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
587
|
+
// only support response body only contains “application/json” content type
|
|
588
|
+
if (multipleMediaType) {
|
|
589
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
590
|
+
}
|
|
591
|
+
else if (Object.keys(json).length === 0) {
|
|
592
|
+
// response body should not be empty
|
|
593
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
validateServer(method, path) {
|
|
598
|
+
const pathObj = this.spec.paths[path];
|
|
599
|
+
const result = { isValid: true, reason: [] };
|
|
600
|
+
const operationObject = pathObj[method];
|
|
601
|
+
const rootServer = this.spec.servers && this.spec.servers[0];
|
|
602
|
+
const methodServer = this.spec.paths[path].servers && this.spec.paths[path].servers[0];
|
|
603
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
604
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
605
|
+
if (!serverUrl) {
|
|
606
|
+
// should contain server URL
|
|
607
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// server url should be absolute url with https protocol
|
|
611
|
+
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
612
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
validateAuth(method, path) {
|
|
617
|
+
const pathObj = this.spec.paths[path];
|
|
618
|
+
const operationObject = pathObj[method];
|
|
619
|
+
const securities = operationObject.security;
|
|
620
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
621
|
+
if (authSchemeArray.length === 0) {
|
|
622
|
+
return { isValid: true, reason: [] };
|
|
623
|
+
}
|
|
624
|
+
if (this.options.allowAPIKeyAuth ||
|
|
625
|
+
this.options.allowOauth2 ||
|
|
626
|
+
this.options.allowBearerTokenAuth) {
|
|
627
|
+
// Currently we don't support multiple auth in one operation
|
|
628
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
629
|
+
return {
|
|
630
|
+
isValid: false,
|
|
631
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
for (const auths of authSchemeArray) {
|
|
635
|
+
if (auths.length === 1) {
|
|
636
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
637
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
638
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
639
|
+
return { isValid: true, reason: [] };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
645
|
+
}
|
|
646
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
647
|
+
var _a;
|
|
648
|
+
const paramResult = {
|
|
649
|
+
requiredNum: 0,
|
|
650
|
+
optionalNum: 0,
|
|
651
|
+
isValid: true,
|
|
652
|
+
reason: [],
|
|
653
|
+
};
|
|
654
|
+
if (Object.keys(schema).length === 0) {
|
|
655
|
+
return paramResult;
|
|
656
|
+
}
|
|
657
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
658
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
659
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
660
|
+
paramResult.isValid = false;
|
|
661
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
662
|
+
return paramResult;
|
|
663
|
+
}
|
|
664
|
+
if (schema.type === "string" ||
|
|
665
|
+
schema.type === "integer" ||
|
|
666
|
+
schema.type === "boolean" ||
|
|
667
|
+
schema.type === "number") {
|
|
668
|
+
if (isRequiredWithoutDefault) {
|
|
669
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (schema.type === "object") {
|
|
676
|
+
const { properties } = schema;
|
|
677
|
+
for (const property in properties) {
|
|
678
|
+
let isRequired = false;
|
|
679
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
680
|
+
isRequired = true;
|
|
681
|
+
}
|
|
682
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
683
|
+
paramResult.requiredNum += result.requiredNum;
|
|
684
|
+
paramResult.optionalNum += result.optionalNum;
|
|
685
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
686
|
+
paramResult.reason.push(...result.reason);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
691
|
+
paramResult.isValid = false;
|
|
692
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return paramResult;
|
|
696
|
+
}
|
|
697
|
+
checkParamSchema(paramObject) {
|
|
698
|
+
const paramResult = {
|
|
699
|
+
requiredNum: 0,
|
|
700
|
+
optionalNum: 0,
|
|
701
|
+
isValid: true,
|
|
702
|
+
reason: [],
|
|
703
|
+
};
|
|
704
|
+
if (!paramObject) {
|
|
705
|
+
return paramResult;
|
|
706
|
+
}
|
|
707
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
708
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
709
|
+
const param = paramObject[i];
|
|
710
|
+
const schema = param.schema;
|
|
711
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
712
|
+
paramResult.isValid = false;
|
|
713
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
717
|
+
if (isCopilot) {
|
|
718
|
+
if (isRequiredWithoutDefault) {
|
|
719
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
723
|
+
}
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
727
|
+
if (isRequiredWithoutDefault) {
|
|
728
|
+
paramResult.isValid = false;
|
|
729
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
730
|
+
}
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (schema.type !== "boolean" &&
|
|
734
|
+
schema.type !== "string" &&
|
|
735
|
+
schema.type !== "number" &&
|
|
736
|
+
schema.type !== "integer") {
|
|
737
|
+
if (isRequiredWithoutDefault) {
|
|
738
|
+
paramResult.isValid = false;
|
|
739
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
740
|
+
}
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (param.in === "query" || param.in === "path") {
|
|
744
|
+
if (isRequiredWithoutDefault) {
|
|
745
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return paramResult;
|
|
753
|
+
}
|
|
754
|
+
hasNestedObjectInSchema(schema) {
|
|
755
|
+
if (schema.type === "object") {
|
|
756
|
+
for (const property in schema.properties) {
|
|
757
|
+
const nestedSchema = schema.properties[property];
|
|
758
|
+
if (nestedSchema.type === "object") {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Copyright (c) Microsoft Corporation.
|
|
768
|
+
class CopilotValidator extends Validator {
|
|
769
|
+
constructor(spec, options) {
|
|
770
|
+
super();
|
|
771
|
+
this.projectType = ProjectType.Copilot;
|
|
772
|
+
this.options = options;
|
|
773
|
+
this.spec = spec;
|
|
774
|
+
}
|
|
775
|
+
validateAPI(method, path) {
|
|
776
|
+
const result = { isValid: true, reason: [] };
|
|
777
|
+
method = method.toLocaleLowerCase();
|
|
778
|
+
// validate method and path
|
|
779
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
780
|
+
if (!methodAndPathResult.isValid) {
|
|
781
|
+
return methodAndPathResult;
|
|
782
|
+
}
|
|
783
|
+
const operationObject = this.spec.paths[path][method];
|
|
784
|
+
// validate auth
|
|
785
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
786
|
+
result.reason.push(...authCheckResult.reason);
|
|
787
|
+
// validate operationId
|
|
788
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
789
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
790
|
+
}
|
|
791
|
+
// validate server
|
|
792
|
+
const validateServerResult = this.validateServer(method, path);
|
|
793
|
+
result.reason.push(...validateServerResult.reason);
|
|
794
|
+
// validate response
|
|
795
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
796
|
+
result.reason.push(...validateResponseResult.reason);
|
|
797
|
+
// validate requestBody
|
|
798
|
+
const requestBody = operationObject.requestBody;
|
|
799
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
800
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
801
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
802
|
+
}
|
|
803
|
+
if (requestJsonBody) {
|
|
804
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
805
|
+
if (requestBodySchema.type !== "object") {
|
|
806
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
807
|
+
}
|
|
808
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
809
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
810
|
+
}
|
|
811
|
+
// validate parameters
|
|
812
|
+
const paramObject = operationObject.parameters;
|
|
813
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
814
|
+
result.reason.push(...paramResult.reason);
|
|
815
|
+
if (result.reason.length > 0) {
|
|
816
|
+
result.isValid = false;
|
|
817
|
+
}
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Copyright (c) Microsoft Corporation.
|
|
823
|
+
class SMEValidator extends Validator {
|
|
824
|
+
constructor(spec, options) {
|
|
825
|
+
super();
|
|
826
|
+
this.projectType = ProjectType.SME;
|
|
827
|
+
this.options = options;
|
|
828
|
+
this.spec = spec;
|
|
829
|
+
}
|
|
830
|
+
validateAPI(method, path) {
|
|
831
|
+
const result = { isValid: true, reason: [] };
|
|
832
|
+
method = method.toLocaleLowerCase();
|
|
833
|
+
// validate method and path
|
|
834
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
835
|
+
if (!methodAndPathResult.isValid) {
|
|
836
|
+
return methodAndPathResult;
|
|
837
|
+
}
|
|
838
|
+
const operationObject = this.spec.paths[path][method];
|
|
839
|
+
// validate auth
|
|
840
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
841
|
+
result.reason.push(...authCheckResult.reason);
|
|
842
|
+
// validate operationId
|
|
843
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
844
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
845
|
+
}
|
|
846
|
+
// validate server
|
|
847
|
+
const validateServerResult = this.validateServer(method, path);
|
|
848
|
+
result.reason.push(...validateServerResult.reason);
|
|
849
|
+
// validate response
|
|
850
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
851
|
+
result.reason.push(...validateResponseResult.reason);
|
|
852
|
+
let postBodyResult = {
|
|
853
|
+
requiredNum: 0,
|
|
854
|
+
optionalNum: 0,
|
|
855
|
+
isValid: true,
|
|
856
|
+
reason: [],
|
|
857
|
+
};
|
|
858
|
+
// validate requestBody
|
|
859
|
+
const requestBody = operationObject.requestBody;
|
|
860
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
861
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
862
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
863
|
+
}
|
|
864
|
+
if (requestJsonBody) {
|
|
865
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
866
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
867
|
+
result.reason.push(...postBodyResult.reason);
|
|
868
|
+
}
|
|
869
|
+
// validate parameters
|
|
870
|
+
const paramObject = operationObject.parameters;
|
|
871
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
872
|
+
result.reason.push(...paramResult.reason);
|
|
873
|
+
// validate total parameters count
|
|
874
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
875
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
876
|
+
result.reason.push(...paramCountResult.reason);
|
|
877
|
+
}
|
|
878
|
+
if (result.reason.length > 0) {
|
|
879
|
+
result.isValid = false;
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
884
|
+
const result = { isValid: true, reason: [] };
|
|
885
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
886
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
887
|
+
if (totalRequiredParams > 1) {
|
|
888
|
+
if (!this.options.allowMultipleParameters ||
|
|
889
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
890
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
else if (totalParams === 0) {
|
|
894
|
+
result.reason.push(ErrorType.NoParameter);
|
|
895
|
+
}
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
900
|
+
|
|
901
|
+
// Copyright (c) Microsoft Corporation.
|
|
902
|
+
class TeamsAIValidator extends Validator {
|
|
903
|
+
constructor(spec, options) {
|
|
904
|
+
super();
|
|
905
|
+
this.projectType = ProjectType.TeamsAi;
|
|
906
|
+
this.options = options;
|
|
907
|
+
this.spec = spec;
|
|
908
|
+
}
|
|
909
|
+
validateAPI(method, path) {
|
|
910
|
+
const result = { isValid: true, reason: [] };
|
|
911
|
+
method = method.toLocaleLowerCase();
|
|
912
|
+
// validate method and path
|
|
913
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
914
|
+
if (!methodAndPathResult.isValid) {
|
|
915
|
+
return methodAndPathResult;
|
|
916
|
+
}
|
|
917
|
+
const operationObject = this.spec.paths[path][method];
|
|
918
|
+
// validate operationId
|
|
919
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
920
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
921
|
+
}
|
|
922
|
+
// validate server
|
|
923
|
+
const validateServerResult = this.validateServer(method, path);
|
|
924
|
+
result.reason.push(...validateServerResult.reason);
|
|
925
|
+
if (result.reason.length > 0) {
|
|
926
|
+
result.isValid = false;
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
class ValidatorFactory {
|
|
933
|
+
static create(spec, options) {
|
|
934
|
+
var _a;
|
|
935
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
936
|
+
switch (type) {
|
|
937
|
+
case ProjectType.SME:
|
|
938
|
+
return new SMEValidator(spec, options);
|
|
939
|
+
case ProjectType.Copilot:
|
|
940
|
+
return new CopilotValidator(spec, options);
|
|
941
|
+
case ProjectType.TeamsAi:
|
|
942
|
+
return new TeamsAIValidator(spec, options);
|
|
943
|
+
default:
|
|
944
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
831
949
|
// Copyright (c) Microsoft Corporation.
|
|
832
950
|
/**
|
|
833
951
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -884,7 +1002,8 @@ class SpecParser {
|
|
|
884
1002
|
],
|
|
885
1003
|
};
|
|
886
1004
|
}
|
|
887
|
-
|
|
1005
|
+
const apiMap = this.getAPIs(this.spec);
|
|
1006
|
+
return Utils.validateSpec(this.spec, this.parser, apiMap, !!this.isSwaggerFile, this.options);
|
|
888
1007
|
}
|
|
889
1008
|
catch (err) {
|
|
890
1009
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -893,17 +1012,20 @@ class SpecParser {
|
|
|
893
1012
|
async listSupportedAPIInfo() {
|
|
894
1013
|
try {
|
|
895
1014
|
await this.loadSpec();
|
|
896
|
-
const apiMap = this.
|
|
1015
|
+
const apiMap = this.getAPIs(this.spec);
|
|
897
1016
|
const apiInfos = [];
|
|
898
1017
|
for (const key in apiMap) {
|
|
899
|
-
const
|
|
1018
|
+
const { operation, isValid } = apiMap[key];
|
|
1019
|
+
if (!isValid) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
900
1022
|
const [method, path] = key.split(" ");
|
|
901
|
-
const operationId =
|
|
1023
|
+
const operationId = operation.operationId;
|
|
902
1024
|
// In Browser environment, this api is by default not support api without operationId
|
|
903
1025
|
if (!operationId) {
|
|
904
1026
|
continue;
|
|
905
1027
|
}
|
|
906
|
-
const command = Utils.parseApiInfo(
|
|
1028
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
907
1029
|
const apiInfo = {
|
|
908
1030
|
method: method,
|
|
909
1031
|
path: path,
|
|
@@ -970,15 +1092,15 @@ class SpecParser {
|
|
|
970
1092
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
971
1093
|
}
|
|
972
1094
|
}
|
|
973
|
-
|
|
1095
|
+
getAPIs(spec) {
|
|
974
1096
|
if (this.apiMap !== undefined) {
|
|
975
1097
|
return this.apiMap;
|
|
976
1098
|
}
|
|
977
|
-
const result = this.
|
|
1099
|
+
const result = this.listAPIs(spec);
|
|
978
1100
|
this.apiMap = result;
|
|
979
1101
|
return result;
|
|
980
1102
|
}
|
|
981
|
-
|
|
1103
|
+
listAPIs(spec) {
|
|
982
1104
|
var _a;
|
|
983
1105
|
const paths = spec.paths;
|
|
984
1106
|
const result = {};
|
|
@@ -986,11 +1108,14 @@ class SpecParser {
|
|
|
986
1108
|
const methods = paths[path];
|
|
987
1109
|
for (const method in methods) {
|
|
988
1110
|
const operationObject = methods[method];
|
|
989
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1111
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1112
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1113
|
+
const validateResult = validator.validateAPI(method, path);
|
|
1114
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
1115
|
+
operation: operationObject,
|
|
1116
|
+
isValid: validateResult.isValid,
|
|
1117
|
+
reason: validateResult.reason,
|
|
1118
|
+
};
|
|
994
1119
|
}
|
|
995
1120
|
}
|
|
996
1121
|
}
|