@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.42af26ca6.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 +571 -325
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +640 -365
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +571 -325
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +640 -365
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/interfaces.d.ts +41 -1
- package/dist/src/manifestUpdater.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +3 -1
- package/dist/src/specParser.d.ts +3 -1
- package/dist/src/utils.d.ts +8 -28
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -16,6 +16,7 @@ var ErrorType;
|
|
|
16
16
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
17
17
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
18
18
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
19
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
19
20
|
ErrorType["ListFailed"] = "list-failed";
|
|
20
21
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
21
22
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -24,6 +25,21 @@ var ErrorType;
|
|
|
24
25
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
25
26
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
26
27
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
28
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
29
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
30
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
31
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
32
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
33
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
34
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
35
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
36
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
37
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
38
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
39
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
40
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
41
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
42
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
27
43
|
ErrorType["Cancelled"] = "cancelled";
|
|
28
44
|
ErrorType["Unknown"] = "unknown";
|
|
29
45
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -79,6 +95,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
79
95
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
80
96
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
81
97
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
98
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
82
99
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
83
100
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
84
101
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -168,221 +185,9 @@ class Utils {
|
|
|
168
185
|
}
|
|
169
186
|
return false;
|
|
170
187
|
}
|
|
171
|
-
static checkParameters(paramObject, isCopilot) {
|
|
172
|
-
const paramResult = {
|
|
173
|
-
requiredNum: 0,
|
|
174
|
-
optionalNum: 0,
|
|
175
|
-
isValid: true,
|
|
176
|
-
};
|
|
177
|
-
if (!paramObject) {
|
|
178
|
-
return paramResult;
|
|
179
|
-
}
|
|
180
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
181
|
-
const param = paramObject[i];
|
|
182
|
-
const schema = param.schema;
|
|
183
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
184
|
-
paramResult.isValid = false;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
188
|
-
if (isCopilot) {
|
|
189
|
-
if (isRequiredWithoutDefault) {
|
|
190
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
194
|
-
}
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
198
|
-
if (isRequiredWithoutDefault) {
|
|
199
|
-
paramResult.isValid = false;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (schema.type !== "boolean" &&
|
|
204
|
-
schema.type !== "string" &&
|
|
205
|
-
schema.type !== "number" &&
|
|
206
|
-
schema.type !== "integer") {
|
|
207
|
-
if (isRequiredWithoutDefault) {
|
|
208
|
-
paramResult.isValid = false;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (param.in === "query" || param.in === "path") {
|
|
213
|
-
if (isRequiredWithoutDefault) {
|
|
214
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return paramResult;
|
|
222
|
-
}
|
|
223
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
224
|
-
var _a;
|
|
225
|
-
const paramResult = {
|
|
226
|
-
requiredNum: 0,
|
|
227
|
-
optionalNum: 0,
|
|
228
|
-
isValid: true,
|
|
229
|
-
};
|
|
230
|
-
if (Object.keys(schema).length === 0) {
|
|
231
|
-
return paramResult;
|
|
232
|
-
}
|
|
233
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
234
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
235
|
-
paramResult.isValid = false;
|
|
236
|
-
return paramResult;
|
|
237
|
-
}
|
|
238
|
-
if (schema.type === "string" ||
|
|
239
|
-
schema.type === "integer" ||
|
|
240
|
-
schema.type === "boolean" ||
|
|
241
|
-
schema.type === "number") {
|
|
242
|
-
if (isRequiredWithoutDefault) {
|
|
243
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
else if (schema.type === "object") {
|
|
250
|
-
const { properties } = schema;
|
|
251
|
-
for (const property in properties) {
|
|
252
|
-
let isRequired = false;
|
|
253
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
254
|
-
isRequired = true;
|
|
255
|
-
}
|
|
256
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
257
|
-
paramResult.requiredNum += result.requiredNum;
|
|
258
|
-
paramResult.optionalNum += result.optionalNum;
|
|
259
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
264
|
-
paramResult.isValid = false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return paramResult;
|
|
268
|
-
}
|
|
269
188
|
static containMultipleMediaTypes(bodyObject) {
|
|
270
189
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
271
190
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Checks if the given API is supported.
|
|
274
|
-
* @param {string} method - The HTTP method of the API.
|
|
275
|
-
* @param {string} path - The path of the API.
|
|
276
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
277
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
278
|
-
* @description The following APIs are supported:
|
|
279
|
-
* 1. only support Get/Post operation without auth property
|
|
280
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
281
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
282
|
-
* 4. request body + required parameters <= 1
|
|
283
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
284
|
-
* 6. only support request body with “application/json” content type
|
|
285
|
-
*/
|
|
286
|
-
static isSupportedApi(method, path, spec, options) {
|
|
287
|
-
var _a;
|
|
288
|
-
const pathObj = spec.paths[path];
|
|
289
|
-
method = method.toLocaleLowerCase();
|
|
290
|
-
if (pathObj) {
|
|
291
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
292
|
-
const securities = pathObj[method].security;
|
|
293
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
294
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
295
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
296
|
-
if (!isTeamsAi) {
|
|
297
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
298
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const operationObject = pathObj[method];
|
|
303
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
const paramObject = operationObject.parameters;
|
|
307
|
-
const requestBody = operationObject.requestBody;
|
|
308
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
309
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
313
|
-
if (Object.keys(responseJson).length === 0) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
// Teams AI project doesn't care about request parameters/body
|
|
317
|
-
if (isTeamsAi) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
let requestBodyParamResult = {
|
|
321
|
-
requiredNum: 0,
|
|
322
|
-
optionalNum: 0,
|
|
323
|
-
isValid: true,
|
|
324
|
-
};
|
|
325
|
-
if (requestJsonBody) {
|
|
326
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
327
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
331
|
-
}
|
|
332
|
-
if (!requestBodyParamResult.isValid) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
336
|
-
if (!paramResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
// Copilot support arbitrary parameters
|
|
340
|
-
if (isCopilot) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
344
|
-
if (options.allowMultipleParameters &&
|
|
345
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
346
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
else if (requestBodyParamResult.requiredNum +
|
|
352
|
-
requestBodyParamResult.optionalNum +
|
|
353
|
-
paramResult.requiredNum +
|
|
354
|
-
paramResult.optionalNum ===
|
|
355
|
-
0) {
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
366
|
-
if (authSchemeArray.length === 0) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
370
|
-
// Currently we don't support multiple auth in one operation
|
|
371
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
for (const auths of authSchemeArray) {
|
|
375
|
-
if (auths.length === 1) {
|
|
376
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
377
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
378
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
191
|
static isBearerTokenAuth(authScheme) {
|
|
387
192
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
388
193
|
}
|
|
@@ -390,10 +195,9 @@ class Utils {
|
|
|
390
195
|
return authScheme.type === "apiKey";
|
|
391
196
|
}
|
|
392
197
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return false;
|
|
198
|
+
return !!(authScheme.type === "oauth2" &&
|
|
199
|
+
authScheme.flows &&
|
|
200
|
+
authScheme.flows.authorizationCode);
|
|
397
201
|
}
|
|
398
202
|
static getAuthArray(securities, spec) {
|
|
399
203
|
var _a;
|
|
@@ -421,14 +225,17 @@ class Utils {
|
|
|
421
225
|
static updateFirstLetter(str) {
|
|
422
226
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
423
227
|
}
|
|
424
|
-
static getResponseJson(operationObject
|
|
228
|
+
static getResponseJson(operationObject) {
|
|
425
229
|
var _a, _b;
|
|
426
230
|
let json = {};
|
|
231
|
+
let multipleMediaType = false;
|
|
427
232
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
428
233
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
429
234
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
235
|
+
multipleMediaType = false;
|
|
430
236
|
json = responseObject.content["application/json"];
|
|
431
|
-
if (
|
|
237
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
238
|
+
multipleMediaType = true;
|
|
432
239
|
json = {};
|
|
433
240
|
}
|
|
434
241
|
else {
|
|
@@ -436,7 +243,7 @@ class Utils {
|
|
|
436
243
|
}
|
|
437
244
|
}
|
|
438
245
|
}
|
|
439
|
-
return json;
|
|
246
|
+
return { json, multipleMediaType };
|
|
440
247
|
}
|
|
441
248
|
static convertPathToCamelCase(path) {
|
|
442
249
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -456,10 +263,10 @@ class Utils {
|
|
|
456
263
|
return undefined;
|
|
457
264
|
}
|
|
458
265
|
}
|
|
459
|
-
static
|
|
266
|
+
static resolveEnv(str) {
|
|
460
267
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
461
|
-
let matches = placeHolderReg.exec(
|
|
462
|
-
let
|
|
268
|
+
let matches = placeHolderReg.exec(str);
|
|
269
|
+
let newStr = str;
|
|
463
270
|
while (matches != null) {
|
|
464
271
|
const envVar = matches[1];
|
|
465
272
|
const envVal = process.env[envVar];
|
|
@@ -467,17 +274,17 @@ class Utils {
|
|
|
467
274
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
468
275
|
}
|
|
469
276
|
else {
|
|
470
|
-
|
|
277
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
471
278
|
}
|
|
472
|
-
matches = placeHolderReg.exec(
|
|
279
|
+
matches = placeHolderReg.exec(str);
|
|
473
280
|
}
|
|
474
|
-
return
|
|
281
|
+
return newStr;
|
|
475
282
|
}
|
|
476
283
|
static checkServerUrl(servers) {
|
|
477
284
|
const errors = [];
|
|
478
285
|
let serverUrl;
|
|
479
286
|
try {
|
|
480
|
-
serverUrl = Utils.
|
|
287
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
481
288
|
}
|
|
482
289
|
catch (err) {
|
|
483
290
|
errors.push({
|
|
@@ -508,6 +315,7 @@ class Utils {
|
|
|
508
315
|
return errors;
|
|
509
316
|
}
|
|
510
317
|
static validateServer(spec, options) {
|
|
318
|
+
var _a;
|
|
511
319
|
const errors = [];
|
|
512
320
|
let hasTopLevelServers = false;
|
|
513
321
|
let hasPathLevelServers = false;
|
|
@@ -528,7 +336,7 @@ class Utils {
|
|
|
528
336
|
}
|
|
529
337
|
for (const method in methods) {
|
|
530
338
|
const operationObject = methods[method];
|
|
531
|
-
if (
|
|
339
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
532
340
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
533
341
|
hasOperationLevelServers = true;
|
|
534
342
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -655,13 +463,7 @@ class Utils {
|
|
|
655
463
|
}
|
|
656
464
|
}
|
|
657
465
|
const operationId = operationItem.operationId;
|
|
658
|
-
const parameters = [];
|
|
659
|
-
if (requiredParams.length !== 0) {
|
|
660
|
-
parameters.push(...requiredParams);
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
parameters.push(optionalParams[0]);
|
|
664
|
-
}
|
|
466
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
665
467
|
const command = {
|
|
666
468
|
context: ["compose"],
|
|
667
469
|
type: "query",
|
|
@@ -670,117 +472,526 @@ class Utils {
|
|
|
670
472
|
parameters: parameters,
|
|
671
473
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
672
474
|
};
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
475
|
+
return command;
|
|
476
|
+
}
|
|
477
|
+
static format(str, ...args) {
|
|
478
|
+
let index = 0;
|
|
479
|
+
return str.replace(/%s/g, () => {
|
|
480
|
+
const arg = args[index++];
|
|
481
|
+
return arg !== undefined ? arg : "";
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
485
|
+
if (!authName) {
|
|
486
|
+
return "";
|
|
680
487
|
}
|
|
681
|
-
|
|
488
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
489
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
490
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
491
|
+
}
|
|
492
|
+
return safeRegistrationIdEnvName;
|
|
682
493
|
}
|
|
683
|
-
static
|
|
684
|
-
const
|
|
494
|
+
static getServerObject(spec, method, path) {
|
|
495
|
+
const pathObj = spec.paths[path];
|
|
496
|
+
const operationObject = pathObj[method];
|
|
497
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
498
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
499
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
500
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
501
|
+
return serverUrl;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Copyright (c) Microsoft Corporation.
|
|
506
|
+
class Validator {
|
|
507
|
+
listAPIs() {
|
|
508
|
+
var _a;
|
|
509
|
+
if (this.apiMap) {
|
|
510
|
+
return this.apiMap;
|
|
511
|
+
}
|
|
512
|
+
const paths = this.spec.paths;
|
|
685
513
|
const result = {};
|
|
686
514
|
for (const path in paths) {
|
|
687
515
|
const methods = paths[path];
|
|
688
516
|
for (const method in methods) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
517
|
+
const operationObject = methods[method];
|
|
518
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
519
|
+
const validateResult = this.validateAPI(method, path);
|
|
520
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
521
|
+
operation: operationObject,
|
|
522
|
+
isValid: validateResult.isValid,
|
|
523
|
+
reason: validateResult.reason,
|
|
524
|
+
};
|
|
692
525
|
}
|
|
693
526
|
}
|
|
694
527
|
}
|
|
528
|
+
this.apiMap = result;
|
|
695
529
|
return result;
|
|
696
530
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
// Server validation
|
|
707
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
708
|
-
errors.push(...serverErrors);
|
|
709
|
-
// Remote reference not supported
|
|
710
|
-
const refPaths = parser.$refs.paths();
|
|
711
|
-
// refPaths [0] is the current spec file path
|
|
712
|
-
if (refPaths.length > 1) {
|
|
713
|
-
errors.push({
|
|
714
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
715
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
716
|
-
data: refPaths,
|
|
531
|
+
validateSpecVersion() {
|
|
532
|
+
const result = { errors: [], warnings: [] };
|
|
533
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
534
|
+
result.errors.push({
|
|
535
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
536
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
537
|
+
data: this.spec.openapi,
|
|
717
538
|
});
|
|
718
539
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
validateSpecServer() {
|
|
543
|
+
const result = { errors: [], warnings: [] };
|
|
544
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
545
|
+
result.errors.push(...serverErrors);
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
validateSpecNoSupportAPI() {
|
|
549
|
+
const result = { errors: [], warnings: [] };
|
|
550
|
+
const apiMap = this.listAPIs();
|
|
551
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
552
|
+
if (validAPIs.length === 0) {
|
|
553
|
+
result.errors.push({
|
|
723
554
|
type: ErrorType.NoSupportedApi,
|
|
724
555
|
content: ConstantString.NoSupportedApi,
|
|
725
556
|
});
|
|
726
557
|
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
validateSpecOperationId() {
|
|
561
|
+
const result = { errors: [], warnings: [] };
|
|
562
|
+
const apiMap = this.listAPIs();
|
|
727
563
|
// OperationId missing
|
|
728
564
|
const apisMissingOperationId = [];
|
|
729
565
|
for (const key in apiMap) {
|
|
730
|
-
const
|
|
731
|
-
if (!
|
|
566
|
+
const { operation } = apiMap[key];
|
|
567
|
+
if (!operation.operationId) {
|
|
732
568
|
apisMissingOperationId.push(key);
|
|
733
569
|
}
|
|
734
570
|
}
|
|
735
571
|
if (apisMissingOperationId.length > 0) {
|
|
736
|
-
warnings.push({
|
|
572
|
+
result.warnings.push({
|
|
737
573
|
type: WarningType.OperationIdMissing,
|
|
738
574
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
739
575
|
data: apisMissingOperationId,
|
|
740
576
|
});
|
|
741
577
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
validateMethodAndPath(method, path) {
|
|
581
|
+
const result = { isValid: true, reason: [] };
|
|
582
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
583
|
+
result.isValid = false;
|
|
584
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
585
|
+
return result;
|
|
745
586
|
}
|
|
746
|
-
|
|
747
|
-
|
|
587
|
+
const pathObj = this.spec.paths[path];
|
|
588
|
+
if (!pathObj || !pathObj[method]) {
|
|
589
|
+
result.isValid = false;
|
|
590
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
591
|
+
return result;
|
|
748
592
|
}
|
|
749
|
-
return
|
|
750
|
-
status,
|
|
751
|
-
warnings,
|
|
752
|
-
errors,
|
|
753
|
-
};
|
|
593
|
+
return result;
|
|
754
594
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
595
|
+
validateResponse(method, path) {
|
|
596
|
+
const result = { isValid: true, reason: [] };
|
|
597
|
+
const operationObject = this.spec.paths[path][method];
|
|
598
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
599
|
+
// only support response body only contains “application/json” content type
|
|
600
|
+
if (multipleMediaType) {
|
|
601
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
602
|
+
}
|
|
603
|
+
else if (Object.keys(json).length === 0) {
|
|
604
|
+
// response body should not be empty
|
|
605
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
761
608
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
609
|
+
validateServer(method, path) {
|
|
610
|
+
const result = { isValid: true, reason: [] };
|
|
611
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
612
|
+
if (!serverObj) {
|
|
613
|
+
// should contain server URL
|
|
614
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
765
615
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
616
|
+
else {
|
|
617
|
+
// server url should be absolute url with https protocol
|
|
618
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
619
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
769
620
|
}
|
|
770
|
-
return
|
|
621
|
+
return result;
|
|
771
622
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
623
|
+
validateAuth(method, path) {
|
|
624
|
+
const pathObj = this.spec.paths[path];
|
|
625
|
+
const operationObject = pathObj[method];
|
|
626
|
+
const securities = operationObject.security;
|
|
627
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
628
|
+
if (authSchemeArray.length === 0) {
|
|
629
|
+
return { isValid: true, reason: [] };
|
|
630
|
+
}
|
|
631
|
+
if (this.options.allowAPIKeyAuth ||
|
|
632
|
+
this.options.allowOauth2 ||
|
|
633
|
+
this.options.allowBearerTokenAuth) {
|
|
634
|
+
// Currently we don't support multiple auth in one operation
|
|
635
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
636
|
+
return {
|
|
637
|
+
isValid: false,
|
|
638
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
for (const auths of authSchemeArray) {
|
|
642
|
+
if (auths.length === 1) {
|
|
643
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
644
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
645
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
646
|
+
return { isValid: true, reason: [] };
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
652
|
+
}
|
|
653
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
654
|
+
var _a;
|
|
655
|
+
const paramResult = {
|
|
656
|
+
requiredNum: 0,
|
|
657
|
+
optionalNum: 0,
|
|
658
|
+
isValid: true,
|
|
659
|
+
reason: [],
|
|
660
|
+
};
|
|
661
|
+
if (Object.keys(schema).length === 0) {
|
|
662
|
+
return paramResult;
|
|
663
|
+
}
|
|
664
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
665
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
666
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
667
|
+
paramResult.isValid = false;
|
|
668
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
669
|
+
return paramResult;
|
|
670
|
+
}
|
|
671
|
+
if (schema.type === "string" ||
|
|
672
|
+
schema.type === "integer" ||
|
|
673
|
+
schema.type === "boolean" ||
|
|
674
|
+
schema.type === "number") {
|
|
675
|
+
if (isRequiredWithoutDefault) {
|
|
676
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
else if (schema.type === "object") {
|
|
683
|
+
const { properties } = schema;
|
|
684
|
+
for (const property in properties) {
|
|
685
|
+
let isRequired = false;
|
|
686
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
687
|
+
isRequired = true;
|
|
688
|
+
}
|
|
689
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
690
|
+
paramResult.requiredNum += result.requiredNum;
|
|
691
|
+
paramResult.optionalNum += result.optionalNum;
|
|
692
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
693
|
+
paramResult.reason.push(...result.reason);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
698
|
+
paramResult.isValid = false;
|
|
699
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return paramResult;
|
|
703
|
+
}
|
|
704
|
+
checkParamSchema(paramObject) {
|
|
705
|
+
const paramResult = {
|
|
706
|
+
requiredNum: 0,
|
|
707
|
+
optionalNum: 0,
|
|
708
|
+
isValid: true,
|
|
709
|
+
reason: [],
|
|
710
|
+
};
|
|
711
|
+
if (!paramObject) {
|
|
712
|
+
return paramResult;
|
|
713
|
+
}
|
|
714
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
715
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
716
|
+
const param = paramObject[i];
|
|
717
|
+
const schema = param.schema;
|
|
718
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
719
|
+
paramResult.isValid = false;
|
|
720
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
724
|
+
if (isCopilot) {
|
|
725
|
+
if (isRequiredWithoutDefault) {
|
|
726
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
730
|
+
}
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
734
|
+
if (isRequiredWithoutDefault) {
|
|
735
|
+
paramResult.isValid = false;
|
|
736
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
737
|
+
}
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (schema.type !== "boolean" &&
|
|
741
|
+
schema.type !== "string" &&
|
|
742
|
+
schema.type !== "number" &&
|
|
743
|
+
schema.type !== "integer") {
|
|
744
|
+
if (isRequiredWithoutDefault) {
|
|
745
|
+
paramResult.isValid = false;
|
|
746
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
747
|
+
}
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (param.in === "query" || param.in === "path") {
|
|
751
|
+
if (isRequiredWithoutDefault) {
|
|
752
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return paramResult;
|
|
760
|
+
}
|
|
761
|
+
hasNestedObjectInSchema(schema) {
|
|
762
|
+
if (schema.type === "object") {
|
|
763
|
+
for (const property in schema.properties) {
|
|
764
|
+
const nestedSchema = schema.properties[property];
|
|
765
|
+
if (nestedSchema.type === "object") {
|
|
766
|
+
return true;
|
|
780
767
|
}
|
|
781
768
|
}
|
|
782
769
|
}
|
|
783
|
-
return
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Copyright (c) Microsoft Corporation.
|
|
775
|
+
class CopilotValidator extends Validator {
|
|
776
|
+
constructor(spec, options) {
|
|
777
|
+
super();
|
|
778
|
+
this.projectType = ProjectType.Copilot;
|
|
779
|
+
this.options = options;
|
|
780
|
+
this.spec = spec;
|
|
781
|
+
}
|
|
782
|
+
validateSpec() {
|
|
783
|
+
const result = { errors: [], warnings: [] };
|
|
784
|
+
// validate spec version
|
|
785
|
+
let validationResult = this.validateSpecVersion();
|
|
786
|
+
result.errors.push(...validationResult.errors);
|
|
787
|
+
// validate spec server
|
|
788
|
+
validationResult = this.validateSpecServer();
|
|
789
|
+
result.errors.push(...validationResult.errors);
|
|
790
|
+
// validate no supported API
|
|
791
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
792
|
+
result.errors.push(...validationResult.errors);
|
|
793
|
+
// validate operationId missing
|
|
794
|
+
validationResult = this.validateSpecOperationId();
|
|
795
|
+
result.warnings.push(...validationResult.warnings);
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
validateAPI(method, path) {
|
|
799
|
+
const result = { isValid: true, reason: [] };
|
|
800
|
+
method = method.toLocaleLowerCase();
|
|
801
|
+
// validate method and path
|
|
802
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
803
|
+
if (!methodAndPathResult.isValid) {
|
|
804
|
+
return methodAndPathResult;
|
|
805
|
+
}
|
|
806
|
+
const operationObject = this.spec.paths[path][method];
|
|
807
|
+
// validate auth
|
|
808
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
809
|
+
result.reason.push(...authCheckResult.reason);
|
|
810
|
+
// validate operationId
|
|
811
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
812
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
813
|
+
}
|
|
814
|
+
// validate server
|
|
815
|
+
const validateServerResult = this.validateServer(method, path);
|
|
816
|
+
result.reason.push(...validateServerResult.reason);
|
|
817
|
+
// validate response
|
|
818
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
819
|
+
result.reason.push(...validateResponseResult.reason);
|
|
820
|
+
// validate requestBody
|
|
821
|
+
const requestBody = operationObject.requestBody;
|
|
822
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
823
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
824
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
825
|
+
}
|
|
826
|
+
if (requestJsonBody) {
|
|
827
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
828
|
+
if (requestBodySchema.type !== "object") {
|
|
829
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
830
|
+
}
|
|
831
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
832
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
833
|
+
}
|
|
834
|
+
// validate parameters
|
|
835
|
+
const paramObject = operationObject.parameters;
|
|
836
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
837
|
+
result.reason.push(...paramResult.reason);
|
|
838
|
+
if (result.reason.length > 0) {
|
|
839
|
+
result.isValid = false;
|
|
840
|
+
}
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Copyright (c) Microsoft Corporation.
|
|
846
|
+
class SMEValidator extends Validator {
|
|
847
|
+
constructor(spec, options) {
|
|
848
|
+
super();
|
|
849
|
+
this.projectType = ProjectType.SME;
|
|
850
|
+
this.options = options;
|
|
851
|
+
this.spec = spec;
|
|
852
|
+
}
|
|
853
|
+
validateSpec() {
|
|
854
|
+
const result = { errors: [], warnings: [] };
|
|
855
|
+
// validate spec version
|
|
856
|
+
let validationResult = this.validateSpecVersion();
|
|
857
|
+
result.errors.push(...validationResult.errors);
|
|
858
|
+
// validate spec server
|
|
859
|
+
validationResult = this.validateSpecServer();
|
|
860
|
+
result.errors.push(...validationResult.errors);
|
|
861
|
+
// validate no supported API
|
|
862
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
863
|
+
result.errors.push(...validationResult.errors);
|
|
864
|
+
// validate operationId missing
|
|
865
|
+
validationResult = this.validateSpecOperationId();
|
|
866
|
+
result.warnings.push(...validationResult.warnings);
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
validateAPI(method, path) {
|
|
870
|
+
const result = { isValid: true, reason: [] };
|
|
871
|
+
method = method.toLocaleLowerCase();
|
|
872
|
+
// validate method and path
|
|
873
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
874
|
+
if (!methodAndPathResult.isValid) {
|
|
875
|
+
return methodAndPathResult;
|
|
876
|
+
}
|
|
877
|
+
const operationObject = this.spec.paths[path][method];
|
|
878
|
+
// validate auth
|
|
879
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
880
|
+
result.reason.push(...authCheckResult.reason);
|
|
881
|
+
// validate operationId
|
|
882
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
883
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
884
|
+
}
|
|
885
|
+
// validate server
|
|
886
|
+
const validateServerResult = this.validateServer(method, path);
|
|
887
|
+
result.reason.push(...validateServerResult.reason);
|
|
888
|
+
// validate response
|
|
889
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
890
|
+
result.reason.push(...validateResponseResult.reason);
|
|
891
|
+
let postBodyResult = {
|
|
892
|
+
requiredNum: 0,
|
|
893
|
+
optionalNum: 0,
|
|
894
|
+
isValid: true,
|
|
895
|
+
reason: [],
|
|
896
|
+
};
|
|
897
|
+
// validate requestBody
|
|
898
|
+
const requestBody = operationObject.requestBody;
|
|
899
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
900
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
901
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
902
|
+
}
|
|
903
|
+
if (requestJsonBody) {
|
|
904
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
905
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
906
|
+
result.reason.push(...postBodyResult.reason);
|
|
907
|
+
}
|
|
908
|
+
// validate parameters
|
|
909
|
+
const paramObject = operationObject.parameters;
|
|
910
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
911
|
+
result.reason.push(...paramResult.reason);
|
|
912
|
+
// validate total parameters count
|
|
913
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
914
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
915
|
+
result.reason.push(...paramCountResult.reason);
|
|
916
|
+
}
|
|
917
|
+
if (result.reason.length > 0) {
|
|
918
|
+
result.isValid = false;
|
|
919
|
+
}
|
|
920
|
+
return result;
|
|
921
|
+
}
|
|
922
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
923
|
+
const result = { isValid: true, reason: [] };
|
|
924
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
925
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
926
|
+
if (totalRequiredParams > 1) {
|
|
927
|
+
if (!this.options.allowMultipleParameters ||
|
|
928
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
929
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else if (totalParams === 0) {
|
|
933
|
+
result.reason.push(ErrorType.NoParameter);
|
|
934
|
+
}
|
|
935
|
+
return result;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
939
|
+
|
|
940
|
+
// Copyright (c) Microsoft Corporation.
|
|
941
|
+
class TeamsAIValidator extends Validator {
|
|
942
|
+
constructor(spec, options) {
|
|
943
|
+
super();
|
|
944
|
+
this.projectType = ProjectType.TeamsAi;
|
|
945
|
+
this.options = options;
|
|
946
|
+
this.spec = spec;
|
|
947
|
+
}
|
|
948
|
+
validateSpec() {
|
|
949
|
+
const result = { errors: [], warnings: [] };
|
|
950
|
+
// validate spec server
|
|
951
|
+
let validationResult = this.validateSpecServer();
|
|
952
|
+
result.errors.push(...validationResult.errors);
|
|
953
|
+
// validate no supported API
|
|
954
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
955
|
+
result.errors.push(...validationResult.errors);
|
|
956
|
+
return result;
|
|
957
|
+
}
|
|
958
|
+
validateAPI(method, path) {
|
|
959
|
+
const result = { isValid: true, reason: [] };
|
|
960
|
+
method = method.toLocaleLowerCase();
|
|
961
|
+
// validate method and path
|
|
962
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
963
|
+
if (!methodAndPathResult.isValid) {
|
|
964
|
+
return methodAndPathResult;
|
|
965
|
+
}
|
|
966
|
+
const operationObject = this.spec.paths[path][method];
|
|
967
|
+
// validate operationId
|
|
968
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
969
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
970
|
+
}
|
|
971
|
+
// validate server
|
|
972
|
+
const validateServerResult = this.validateServer(method, path);
|
|
973
|
+
result.reason.push(...validateServerResult.reason);
|
|
974
|
+
if (result.reason.length > 0) {
|
|
975
|
+
result.isValid = false;
|
|
976
|
+
}
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
class ValidatorFactory {
|
|
982
|
+
static create(spec, options) {
|
|
983
|
+
var _a;
|
|
984
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
985
|
+
switch (type) {
|
|
986
|
+
case ProjectType.SME:
|
|
987
|
+
return new SMEValidator(spec, options);
|
|
988
|
+
case ProjectType.Copilot:
|
|
989
|
+
return new CopilotValidator(spec, options);
|
|
990
|
+
case ProjectType.TeamsAi:
|
|
991
|
+
return new TeamsAIValidator(spec, options);
|
|
992
|
+
default:
|
|
993
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
994
|
+
}
|
|
784
995
|
}
|
|
785
996
|
}
|
|
786
997
|
|
|
@@ -818,11 +1029,7 @@ class SpecParser {
|
|
|
818
1029
|
try {
|
|
819
1030
|
try {
|
|
820
1031
|
await this.loadSpec();
|
|
821
|
-
await this.parser.validate(this.spec
|
|
822
|
-
validate: {
|
|
823
|
-
schema: false,
|
|
824
|
-
},
|
|
825
|
-
});
|
|
1032
|
+
await this.parser.validate(this.spec);
|
|
826
1033
|
}
|
|
827
1034
|
catch (e) {
|
|
828
1035
|
return {
|
|
@@ -831,16 +1038,46 @@ class SpecParser {
|
|
|
831
1038
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
832
1039
|
};
|
|
833
1040
|
}
|
|
1041
|
+
const errors = [];
|
|
1042
|
+
const warnings = [];
|
|
834
1043
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
835
1044
|
return {
|
|
836
1045
|
status: ValidationStatus.Error,
|
|
837
1046
|
warnings: [],
|
|
838
1047
|
errors: [
|
|
839
|
-
{
|
|
1048
|
+
{
|
|
1049
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1050
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1051
|
+
},
|
|
840
1052
|
],
|
|
841
1053
|
};
|
|
842
1054
|
}
|
|
843
|
-
|
|
1055
|
+
// Remote reference not supported
|
|
1056
|
+
const refPaths = this.parser.$refs.paths();
|
|
1057
|
+
// refPaths [0] is the current spec file path
|
|
1058
|
+
if (refPaths.length > 1) {
|
|
1059
|
+
errors.push({
|
|
1060
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1061
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1062
|
+
data: refPaths,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
const validator = this.getValidator(this.spec);
|
|
1066
|
+
const validationResult = validator.validateSpec();
|
|
1067
|
+
warnings.push(...validationResult.warnings);
|
|
1068
|
+
errors.push(...validationResult.errors);
|
|
1069
|
+
let status = ValidationStatus.Valid;
|
|
1070
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1071
|
+
status = ValidationStatus.Warning;
|
|
1072
|
+
}
|
|
1073
|
+
else if (errors.length > 0) {
|
|
1074
|
+
status = ValidationStatus.Error;
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
status: status,
|
|
1078
|
+
warnings: warnings,
|
|
1079
|
+
errors: errors,
|
|
1080
|
+
};
|
|
844
1081
|
}
|
|
845
1082
|
catch (err) {
|
|
846
1083
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -849,17 +1086,20 @@ class SpecParser {
|
|
|
849
1086
|
async listSupportedAPIInfo() {
|
|
850
1087
|
try {
|
|
851
1088
|
await this.loadSpec();
|
|
852
|
-
const apiMap = this.
|
|
1089
|
+
const apiMap = this.getAPIs(this.spec);
|
|
853
1090
|
const apiInfos = [];
|
|
854
1091
|
for (const key in apiMap) {
|
|
855
|
-
const
|
|
1092
|
+
const { operation, isValid } = apiMap[key];
|
|
1093
|
+
if (!isValid) {
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
856
1096
|
const [method, path] = key.split(" ");
|
|
857
|
-
const operationId =
|
|
1097
|
+
const operationId = operation.operationId;
|
|
858
1098
|
// In Browser environment, this api is by default not support api without operationId
|
|
859
1099
|
if (!operationId) {
|
|
860
1100
|
continue;
|
|
861
1101
|
}
|
|
862
|
-
const
|
|
1102
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
863
1103
|
const apiInfo = {
|
|
864
1104
|
method: method,
|
|
865
1105
|
path: path,
|
|
@@ -868,9 +1108,6 @@ class SpecParser {
|
|
|
868
1108
|
parameters: command.parameters,
|
|
869
1109
|
description: command.description,
|
|
870
1110
|
};
|
|
871
|
-
if (warning) {
|
|
872
|
-
apiInfo.warning = warning;
|
|
873
|
-
}
|
|
874
1111
|
apiInfos.push(apiInfo);
|
|
875
1112
|
}
|
|
876
1113
|
return apiInfos;
|
|
@@ -929,13 +1166,22 @@ class SpecParser {
|
|
|
929
1166
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
930
1167
|
}
|
|
931
1168
|
}
|
|
932
|
-
|
|
1169
|
+
getAPIs(spec) {
|
|
933
1170
|
if (this.apiMap !== undefined) {
|
|
934
1171
|
return this.apiMap;
|
|
935
1172
|
}
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
1173
|
+
const validator = this.getValidator(spec);
|
|
1174
|
+
const apiMap = validator.listAPIs();
|
|
1175
|
+
this.apiMap = apiMap;
|
|
1176
|
+
return apiMap;
|
|
1177
|
+
}
|
|
1178
|
+
getValidator(spec) {
|
|
1179
|
+
if (this.validator) {
|
|
1180
|
+
return this.validator;
|
|
1181
|
+
}
|
|
1182
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1183
|
+
this.validator = validator;
|
|
1184
|
+
return validator;
|
|
939
1185
|
}
|
|
940
1186
|
}
|
|
941
1187
|
|
|
@@ -943,7 +1189,7 @@ class SpecParser {
|
|
|
943
1189
|
class AdaptiveCardGenerator {
|
|
944
1190
|
static generateAdaptiveCard(operationItem) {
|
|
945
1191
|
try {
|
|
946
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1192
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
947
1193
|
let cardBody = [];
|
|
948
1194
|
let schema = json.schema;
|
|
949
1195
|
let jsonPath = "$";
|