@microsoft/m365-spec-parser 0.1.1-alpha.4f2290daa.0 → 0.1.1-alpha.5fc8ceacd.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm2017.js +536 -339
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +596 -372
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +536 -339
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +644 -418
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +14 -0
- package/dist/src/manifestUpdater.d.ts +1 -1
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +2 -0
- package/dist/src/utils.d.ts +3 -24
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -170,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
170
170
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
171
171
|
ConstantString.CommandTitleMaxLens = 32;
|
|
172
172
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
173
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
173
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
174
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
174
175
|
|
|
175
176
|
// Copyright (c) Microsoft Corporation.
|
|
176
177
|
class Utils {
|
|
@@ -185,249 +186,9 @@ class Utils {
|
|
|
185
186
|
}
|
|
186
187
|
return false;
|
|
187
188
|
}
|
|
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
189
|
static containMultipleMediaTypes(bodyObject) {
|
|
295
190
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
296
191
|
}
|
|
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
192
|
static isBearerTokenAuth(authScheme) {
|
|
432
193
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
433
194
|
}
|
|
@@ -435,10 +196,9 @@ class Utils {
|
|
|
435
196
|
return authScheme.type === "apiKey";
|
|
436
197
|
}
|
|
437
198
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return false;
|
|
199
|
+
return !!(authScheme.type === "oauth2" &&
|
|
200
|
+
authScheme.flows &&
|
|
201
|
+
authScheme.flows.authorizationCode);
|
|
442
202
|
}
|
|
443
203
|
static getAuthArray(securities, spec) {
|
|
444
204
|
var _a;
|
|
@@ -466,7 +226,7 @@ class Utils {
|
|
|
466
226
|
static updateFirstLetter(str) {
|
|
467
227
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
468
228
|
}
|
|
469
|
-
static getResponseJson(operationObject
|
|
229
|
+
static getResponseJson(operationObject) {
|
|
470
230
|
var _a, _b;
|
|
471
231
|
let json = {};
|
|
472
232
|
let multipleMediaType = false;
|
|
@@ -477,9 +237,6 @@ class Utils {
|
|
|
477
237
|
json = responseObject.content["application/json"];
|
|
478
238
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
479
239
|
multipleMediaType = true;
|
|
480
|
-
if (isTeamsAiProject) {
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
240
|
json = {};
|
|
484
241
|
}
|
|
485
242
|
else {
|
|
@@ -718,16 +475,49 @@ class Utils {
|
|
|
718
475
|
};
|
|
719
476
|
return command;
|
|
720
477
|
}
|
|
721
|
-
static
|
|
478
|
+
static format(str, ...args) {
|
|
479
|
+
let index = 0;
|
|
480
|
+
return str.replace(/%s/g, () => {
|
|
481
|
+
const arg = args[index++];
|
|
482
|
+
return arg !== undefined ? arg : "";
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
486
|
+
if (!authName) {
|
|
487
|
+
return "";
|
|
488
|
+
}
|
|
489
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
490
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
491
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
492
|
+
}
|
|
493
|
+
return safeRegistrationIdEnvName;
|
|
494
|
+
}
|
|
495
|
+
static getServerObject(spec, method, path) {
|
|
496
|
+
const pathObj = spec.paths[path];
|
|
497
|
+
const operationObject = pathObj[method];
|
|
498
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
499
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
500
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
501
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
502
|
+
return serverUrl;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Copyright (c) Microsoft Corporation.
|
|
507
|
+
class Validator {
|
|
508
|
+
listAPIs() {
|
|
722
509
|
var _a;
|
|
723
|
-
|
|
510
|
+
if (this.apiMap) {
|
|
511
|
+
return this.apiMap;
|
|
512
|
+
}
|
|
513
|
+
const paths = this.spec.paths;
|
|
724
514
|
const result = {};
|
|
725
515
|
for (const path in paths) {
|
|
726
516
|
const methods = paths[path];
|
|
727
517
|
for (const method in methods) {
|
|
728
518
|
const operationObject = methods[method];
|
|
729
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
730
|
-
const validateResult =
|
|
519
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
520
|
+
const validateResult = this.validateAPI(method, path);
|
|
731
521
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
732
522
|
operation: operationObject,
|
|
733
523
|
isValid: validateResult.isValid,
|
|
@@ -736,38 +526,48 @@ class Utils {
|
|
|
736
526
|
}
|
|
737
527
|
}
|
|
738
528
|
}
|
|
529
|
+
this.apiMap = result;
|
|
739
530
|
return result;
|
|
740
531
|
}
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
532
|
+
validateSpecVersion() {
|
|
533
|
+
const result = { errors: [], warnings: [] };
|
|
534
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
535
|
+
result.errors.push({
|
|
536
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
537
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
538
|
+
data: this.spec.openapi,
|
|
749
539
|
});
|
|
750
540
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
// No supported API
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
validateSpecServer() {
|
|
544
|
+
const result = { errors: [], warnings: [] };
|
|
545
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
546
|
+
result.errors.push(...serverErrors);
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
validateSpecNoSupportAPI() {
|
|
550
|
+
const result = { errors: [], warnings: [] };
|
|
551
|
+
const apiMap = this.listAPIs();
|
|
764
552
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
765
553
|
if (validAPIs.length === 0) {
|
|
766
|
-
|
|
554
|
+
const data = [];
|
|
555
|
+
for (const key in apiMap) {
|
|
556
|
+
const { reason } = apiMap[key];
|
|
557
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
558
|
+
data.push(apiInvalidReason);
|
|
559
|
+
}
|
|
560
|
+
result.errors.push({
|
|
767
561
|
type: ErrorType.NoSupportedApi,
|
|
768
562
|
content: ConstantString.NoSupportedApi,
|
|
563
|
+
data,
|
|
769
564
|
});
|
|
770
565
|
}
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
validateSpecOperationId() {
|
|
569
|
+
const result = { errors: [], warnings: [] };
|
|
570
|
+
const apiMap = this.listAPIs();
|
|
771
571
|
// OperationId missing
|
|
772
572
|
const apisMissingOperationId = [];
|
|
773
573
|
for (const key in apiMap) {
|
|
@@ -777,54 +577,431 @@ class Utils {
|
|
|
777
577
|
}
|
|
778
578
|
}
|
|
779
579
|
if (apisMissingOperationId.length > 0) {
|
|
780
|
-
warnings.push({
|
|
580
|
+
result.warnings.push({
|
|
781
581
|
type: WarningType.OperationIdMissing,
|
|
782
582
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
783
583
|
data: apisMissingOperationId,
|
|
784
584
|
});
|
|
785
585
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
validateMethodAndPath(method, path) {
|
|
589
|
+
const result = { isValid: true, reason: [] };
|
|
590
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
591
|
+
result.isValid = false;
|
|
592
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
593
|
+
return result;
|
|
789
594
|
}
|
|
790
|
-
|
|
791
|
-
|
|
595
|
+
const pathObj = this.spec.paths[path];
|
|
596
|
+
if (!pathObj || !pathObj[method]) {
|
|
597
|
+
result.isValid = false;
|
|
598
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
599
|
+
return result;
|
|
792
600
|
}
|
|
793
|
-
return
|
|
794
|
-
status,
|
|
795
|
-
warnings,
|
|
796
|
-
errors,
|
|
797
|
-
};
|
|
601
|
+
return result;
|
|
798
602
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
603
|
+
validateResponse(method, path) {
|
|
604
|
+
const result = { isValid: true, reason: [] };
|
|
605
|
+
const operationObject = this.spec.paths[path][method];
|
|
606
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
607
|
+
// only support response body only contains “application/json” content type
|
|
608
|
+
if (multipleMediaType) {
|
|
609
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
610
|
+
}
|
|
611
|
+
else if (Object.keys(json).length === 0) {
|
|
612
|
+
// response body should not be empty
|
|
613
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
805
616
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
617
|
+
validateServer(method, path) {
|
|
618
|
+
const result = { isValid: true, reason: [] };
|
|
619
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
620
|
+
if (!serverObj) {
|
|
621
|
+
// should contain server URL
|
|
622
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
809
623
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
624
|
+
else {
|
|
625
|
+
// server url should be absolute url with https protocol
|
|
626
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
627
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
813
628
|
}
|
|
814
|
-
return
|
|
629
|
+
return result;
|
|
815
630
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
631
|
+
validateAuth(method, path) {
|
|
632
|
+
const pathObj = this.spec.paths[path];
|
|
633
|
+
const operationObject = pathObj[method];
|
|
634
|
+
const securities = operationObject.security;
|
|
635
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
636
|
+
if (authSchemeArray.length === 0) {
|
|
637
|
+
return { isValid: true, reason: [] };
|
|
638
|
+
}
|
|
639
|
+
if (this.options.allowAPIKeyAuth ||
|
|
640
|
+
this.options.allowOauth2 ||
|
|
641
|
+
this.options.allowBearerTokenAuth) {
|
|
642
|
+
// Currently we don't support multiple auth in one operation
|
|
643
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
644
|
+
return {
|
|
645
|
+
isValid: false,
|
|
646
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
for (const auths of authSchemeArray) {
|
|
650
|
+
if (auths.length === 1) {
|
|
651
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
652
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
653
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
654
|
+
return { isValid: true, reason: [] };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
660
|
+
}
|
|
661
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
662
|
+
var _a;
|
|
663
|
+
const paramResult = {
|
|
664
|
+
requiredNum: 0,
|
|
665
|
+
optionalNum: 0,
|
|
666
|
+
isValid: true,
|
|
667
|
+
reason: [],
|
|
668
|
+
};
|
|
669
|
+
if (Object.keys(schema).length === 0) {
|
|
670
|
+
return paramResult;
|
|
671
|
+
}
|
|
672
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
673
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
674
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
675
|
+
paramResult.isValid = false;
|
|
676
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
677
|
+
return paramResult;
|
|
678
|
+
}
|
|
679
|
+
if (schema.type === "string" ||
|
|
680
|
+
schema.type === "integer" ||
|
|
681
|
+
schema.type === "boolean" ||
|
|
682
|
+
schema.type === "number") {
|
|
683
|
+
if (isRequiredWithoutDefault) {
|
|
684
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
else if (schema.type === "object") {
|
|
691
|
+
const { properties } = schema;
|
|
692
|
+
for (const property in properties) {
|
|
693
|
+
let isRequired = false;
|
|
694
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
695
|
+
isRequired = true;
|
|
696
|
+
}
|
|
697
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
698
|
+
paramResult.requiredNum += result.requiredNum;
|
|
699
|
+
paramResult.optionalNum += result.optionalNum;
|
|
700
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
701
|
+
paramResult.reason.push(...result.reason);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
706
|
+
paramResult.isValid = false;
|
|
707
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return paramResult;
|
|
711
|
+
}
|
|
712
|
+
checkParamSchema(paramObject) {
|
|
713
|
+
const paramResult = {
|
|
714
|
+
requiredNum: 0,
|
|
715
|
+
optionalNum: 0,
|
|
716
|
+
isValid: true,
|
|
717
|
+
reason: [],
|
|
718
|
+
};
|
|
719
|
+
if (!paramObject) {
|
|
720
|
+
return paramResult;
|
|
721
|
+
}
|
|
722
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
723
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
724
|
+
const param = paramObject[i];
|
|
725
|
+
const schema = param.schema;
|
|
726
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
727
|
+
paramResult.isValid = false;
|
|
728
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
732
|
+
if (isCopilot) {
|
|
733
|
+
if (isRequiredWithoutDefault) {
|
|
734
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
738
|
+
}
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
742
|
+
if (isRequiredWithoutDefault) {
|
|
743
|
+
paramResult.isValid = false;
|
|
744
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
745
|
+
}
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (schema.type !== "boolean" &&
|
|
749
|
+
schema.type !== "string" &&
|
|
750
|
+
schema.type !== "number" &&
|
|
751
|
+
schema.type !== "integer") {
|
|
752
|
+
if (isRequiredWithoutDefault) {
|
|
753
|
+
paramResult.isValid = false;
|
|
754
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
755
|
+
}
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (param.in === "query" || param.in === "path") {
|
|
759
|
+
if (isRequiredWithoutDefault) {
|
|
760
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
824
761
|
}
|
|
762
|
+
else {
|
|
763
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return paramResult;
|
|
768
|
+
}
|
|
769
|
+
hasNestedObjectInSchema(schema) {
|
|
770
|
+
if (schema.type === "object") {
|
|
771
|
+
for (const property in schema.properties) {
|
|
772
|
+
const nestedSchema = schema.properties[property];
|
|
773
|
+
if (nestedSchema.type === "object") {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Copyright (c) Microsoft Corporation.
|
|
783
|
+
class CopilotValidator extends Validator {
|
|
784
|
+
constructor(spec, options) {
|
|
785
|
+
super();
|
|
786
|
+
this.projectType = ProjectType.Copilot;
|
|
787
|
+
this.options = options;
|
|
788
|
+
this.spec = spec;
|
|
789
|
+
}
|
|
790
|
+
validateSpec() {
|
|
791
|
+
const result = { errors: [], warnings: [] };
|
|
792
|
+
// validate spec version
|
|
793
|
+
let validationResult = this.validateSpecVersion();
|
|
794
|
+
result.errors.push(...validationResult.errors);
|
|
795
|
+
// validate spec server
|
|
796
|
+
validationResult = this.validateSpecServer();
|
|
797
|
+
result.errors.push(...validationResult.errors);
|
|
798
|
+
// validate no supported API
|
|
799
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
800
|
+
result.errors.push(...validationResult.errors);
|
|
801
|
+
// validate operationId missing
|
|
802
|
+
validationResult = this.validateSpecOperationId();
|
|
803
|
+
result.warnings.push(...validationResult.warnings);
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
validateAPI(method, path) {
|
|
807
|
+
const result = { isValid: true, reason: [] };
|
|
808
|
+
method = method.toLocaleLowerCase();
|
|
809
|
+
// validate method and path
|
|
810
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
811
|
+
if (!methodAndPathResult.isValid) {
|
|
812
|
+
return methodAndPathResult;
|
|
813
|
+
}
|
|
814
|
+
const operationObject = this.spec.paths[path][method];
|
|
815
|
+
// validate auth
|
|
816
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
817
|
+
result.reason.push(...authCheckResult.reason);
|
|
818
|
+
// validate operationId
|
|
819
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
820
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
821
|
+
}
|
|
822
|
+
// validate server
|
|
823
|
+
const validateServerResult = this.validateServer(method, path);
|
|
824
|
+
result.reason.push(...validateServerResult.reason);
|
|
825
|
+
// validate response
|
|
826
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
827
|
+
result.reason.push(...validateResponseResult.reason);
|
|
828
|
+
// validate requestBody
|
|
829
|
+
const requestBody = operationObject.requestBody;
|
|
830
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
831
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
832
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
833
|
+
}
|
|
834
|
+
if (requestJsonBody) {
|
|
835
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
836
|
+
if (requestBodySchema.type !== "object") {
|
|
837
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
825
838
|
}
|
|
839
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
840
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
841
|
+
}
|
|
842
|
+
// validate parameters
|
|
843
|
+
const paramObject = operationObject.parameters;
|
|
844
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
845
|
+
result.reason.push(...paramResult.reason);
|
|
846
|
+
if (result.reason.length > 0) {
|
|
847
|
+
result.isValid = false;
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Copyright (c) Microsoft Corporation.
|
|
854
|
+
class SMEValidator extends Validator {
|
|
855
|
+
constructor(spec, options) {
|
|
856
|
+
super();
|
|
857
|
+
this.projectType = ProjectType.SME;
|
|
858
|
+
this.options = options;
|
|
859
|
+
this.spec = spec;
|
|
860
|
+
}
|
|
861
|
+
validateSpec() {
|
|
862
|
+
const result = { errors: [], warnings: [] };
|
|
863
|
+
// validate spec version
|
|
864
|
+
let validationResult = this.validateSpecVersion();
|
|
865
|
+
result.errors.push(...validationResult.errors);
|
|
866
|
+
// validate spec server
|
|
867
|
+
validationResult = this.validateSpecServer();
|
|
868
|
+
result.errors.push(...validationResult.errors);
|
|
869
|
+
// validate no supported API
|
|
870
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
871
|
+
result.errors.push(...validationResult.errors);
|
|
872
|
+
// validate operationId missing
|
|
873
|
+
if (this.options.allowMissingId) {
|
|
874
|
+
validationResult = this.validateSpecOperationId();
|
|
875
|
+
result.warnings.push(...validationResult.warnings);
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
validateAPI(method, path) {
|
|
880
|
+
const result = { isValid: true, reason: [] };
|
|
881
|
+
method = method.toLocaleLowerCase();
|
|
882
|
+
// validate method and path
|
|
883
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
884
|
+
if (!methodAndPathResult.isValid) {
|
|
885
|
+
return methodAndPathResult;
|
|
886
|
+
}
|
|
887
|
+
const operationObject = this.spec.paths[path][method];
|
|
888
|
+
// validate auth
|
|
889
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
890
|
+
result.reason.push(...authCheckResult.reason);
|
|
891
|
+
// validate operationId
|
|
892
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
893
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
894
|
+
}
|
|
895
|
+
// validate server
|
|
896
|
+
const validateServerResult = this.validateServer(method, path);
|
|
897
|
+
result.reason.push(...validateServerResult.reason);
|
|
898
|
+
// validate response
|
|
899
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
900
|
+
result.reason.push(...validateResponseResult.reason);
|
|
901
|
+
let postBodyResult = {
|
|
902
|
+
requiredNum: 0,
|
|
903
|
+
optionalNum: 0,
|
|
904
|
+
isValid: true,
|
|
905
|
+
reason: [],
|
|
906
|
+
};
|
|
907
|
+
// validate requestBody
|
|
908
|
+
const requestBody = operationObject.requestBody;
|
|
909
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
910
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
911
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
912
|
+
}
|
|
913
|
+
if (requestJsonBody) {
|
|
914
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
915
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
916
|
+
result.reason.push(...postBodyResult.reason);
|
|
917
|
+
}
|
|
918
|
+
// validate parameters
|
|
919
|
+
const paramObject = operationObject.parameters;
|
|
920
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
921
|
+
result.reason.push(...paramResult.reason);
|
|
922
|
+
// validate total parameters count
|
|
923
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
924
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
925
|
+
result.reason.push(...paramCountResult.reason);
|
|
926
|
+
}
|
|
927
|
+
if (result.reason.length > 0) {
|
|
928
|
+
result.isValid = false;
|
|
929
|
+
}
|
|
930
|
+
return result;
|
|
931
|
+
}
|
|
932
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
933
|
+
const result = { isValid: true, reason: [] };
|
|
934
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
935
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
936
|
+
if (totalRequiredParams > 1) {
|
|
937
|
+
if (!this.options.allowMultipleParameters ||
|
|
938
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
939
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
else if (totalParams === 0) {
|
|
943
|
+
result.reason.push(ErrorType.NoParameter);
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
949
|
+
|
|
950
|
+
// Copyright (c) Microsoft Corporation.
|
|
951
|
+
class TeamsAIValidator extends Validator {
|
|
952
|
+
constructor(spec, options) {
|
|
953
|
+
super();
|
|
954
|
+
this.projectType = ProjectType.TeamsAi;
|
|
955
|
+
this.options = options;
|
|
956
|
+
this.spec = spec;
|
|
957
|
+
}
|
|
958
|
+
validateSpec() {
|
|
959
|
+
const result = { errors: [], warnings: [] };
|
|
960
|
+
// validate spec server
|
|
961
|
+
let validationResult = this.validateSpecServer();
|
|
962
|
+
result.errors.push(...validationResult.errors);
|
|
963
|
+
// validate no supported API
|
|
964
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
965
|
+
result.errors.push(...validationResult.errors);
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
validateAPI(method, path) {
|
|
969
|
+
const result = { isValid: true, reason: [] };
|
|
970
|
+
method = method.toLocaleLowerCase();
|
|
971
|
+
// validate method and path
|
|
972
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
973
|
+
if (!methodAndPathResult.isValid) {
|
|
974
|
+
return methodAndPathResult;
|
|
975
|
+
}
|
|
976
|
+
const operationObject = this.spec.paths[path][method];
|
|
977
|
+
// validate operationId
|
|
978
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
979
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
980
|
+
}
|
|
981
|
+
// validate server
|
|
982
|
+
const validateServerResult = this.validateServer(method, path);
|
|
983
|
+
result.reason.push(...validateServerResult.reason);
|
|
984
|
+
if (result.reason.length > 0) {
|
|
985
|
+
result.isValid = false;
|
|
986
|
+
}
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
class ValidatorFactory {
|
|
992
|
+
static create(spec, options) {
|
|
993
|
+
var _a;
|
|
994
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
995
|
+
switch (type) {
|
|
996
|
+
case ProjectType.SME:
|
|
997
|
+
return new SMEValidator(spec, options);
|
|
998
|
+
case ProjectType.Copilot:
|
|
999
|
+
return new CopilotValidator(spec, options);
|
|
1000
|
+
case ProjectType.TeamsAi:
|
|
1001
|
+
return new TeamsAIValidator(spec, options);
|
|
1002
|
+
default:
|
|
1003
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
826
1004
|
}
|
|
827
|
-
return count;
|
|
828
1005
|
}
|
|
829
1006
|
}
|
|
830
1007
|
|
|
@@ -862,11 +1039,7 @@ class SpecParser {
|
|
|
862
1039
|
try {
|
|
863
1040
|
try {
|
|
864
1041
|
await this.loadSpec();
|
|
865
|
-
await this.parser.validate(this.spec
|
|
866
|
-
validate: {
|
|
867
|
-
schema: false,
|
|
868
|
-
},
|
|
869
|
-
});
|
|
1042
|
+
await this.parser.validate(this.spec);
|
|
870
1043
|
}
|
|
871
1044
|
catch (e) {
|
|
872
1045
|
return {
|
|
@@ -875,16 +1048,46 @@ class SpecParser {
|
|
|
875
1048
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
876
1049
|
};
|
|
877
1050
|
}
|
|
1051
|
+
const errors = [];
|
|
1052
|
+
const warnings = [];
|
|
878
1053
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
879
1054
|
return {
|
|
880
1055
|
status: ValidationStatus.Error,
|
|
881
1056
|
warnings: [],
|
|
882
1057
|
errors: [
|
|
883
|
-
{
|
|
1058
|
+
{
|
|
1059
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1060
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1061
|
+
},
|
|
884
1062
|
],
|
|
885
1063
|
};
|
|
886
1064
|
}
|
|
887
|
-
|
|
1065
|
+
// Remote reference not supported
|
|
1066
|
+
const refPaths = this.parser.$refs.paths();
|
|
1067
|
+
// refPaths [0] is the current spec file path
|
|
1068
|
+
if (refPaths.length > 1) {
|
|
1069
|
+
errors.push({
|
|
1070
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1071
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1072
|
+
data: refPaths,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
const validator = this.getValidator(this.spec);
|
|
1076
|
+
const validationResult = validator.validateSpec();
|
|
1077
|
+
warnings.push(...validationResult.warnings);
|
|
1078
|
+
errors.push(...validationResult.errors);
|
|
1079
|
+
let status = ValidationStatus.Valid;
|
|
1080
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1081
|
+
status = ValidationStatus.Warning;
|
|
1082
|
+
}
|
|
1083
|
+
else if (errors.length > 0) {
|
|
1084
|
+
status = ValidationStatus.Error;
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
status: status,
|
|
1088
|
+
warnings: warnings,
|
|
1089
|
+
errors: errors,
|
|
1090
|
+
};
|
|
888
1091
|
}
|
|
889
1092
|
catch (err) {
|
|
890
1093
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -893,17 +1096,20 @@ class SpecParser {
|
|
|
893
1096
|
async listSupportedAPIInfo() {
|
|
894
1097
|
try {
|
|
895
1098
|
await this.loadSpec();
|
|
896
|
-
const apiMap = this.
|
|
1099
|
+
const apiMap = this.getAPIs(this.spec);
|
|
897
1100
|
const apiInfos = [];
|
|
898
1101
|
for (const key in apiMap) {
|
|
899
|
-
const
|
|
1102
|
+
const { operation, isValid } = apiMap[key];
|
|
1103
|
+
if (!isValid) {
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
900
1106
|
const [method, path] = key.split(" ");
|
|
901
|
-
const operationId =
|
|
1107
|
+
const operationId = operation.operationId;
|
|
902
1108
|
// In Browser environment, this api is by default not support api without operationId
|
|
903
1109
|
if (!operationId) {
|
|
904
1110
|
continue;
|
|
905
1111
|
}
|
|
906
|
-
const command = Utils.parseApiInfo(
|
|
1112
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
907
1113
|
const apiInfo = {
|
|
908
1114
|
method: method,
|
|
909
1115
|
path: path,
|
|
@@ -970,31 +1176,22 @@ class SpecParser {
|
|
|
970
1176
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
971
1177
|
}
|
|
972
1178
|
}
|
|
973
|
-
|
|
1179
|
+
getAPIs(spec) {
|
|
974
1180
|
if (this.apiMap !== undefined) {
|
|
975
1181
|
return this.apiMap;
|
|
976
1182
|
}
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
1183
|
+
const validator = this.getValidator(spec);
|
|
1184
|
+
const apiMap = validator.listAPIs();
|
|
1185
|
+
this.apiMap = apiMap;
|
|
1186
|
+
return apiMap;
|
|
980
1187
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
-
}
|
|
1188
|
+
getValidator(spec) {
|
|
1189
|
+
if (this.validator) {
|
|
1190
|
+
return this.validator;
|
|
996
1191
|
}
|
|
997
|
-
|
|
1192
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1193
|
+
this.validator = validator;
|
|
1194
|
+
return validator;
|
|
998
1195
|
}
|
|
999
1196
|
}
|
|
1000
1197
|
|