@microsoft/m365-spec-parser 0.1.1-alpha.169a2b37a.0 → 0.1.1-alpha.18377bc6e.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 +592 -326
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +680 -378
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +592 -326
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +680 -378
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +3 -1
- package/dist/src/interfaces.d.ts +58 -20
- package/dist/src/manifestUpdater.d.ts +4 -3
- package/dist/src/specParser.browser.d.ts +3 -1
- package/dist/src/specParser.d.ts +4 -2
- package/dist/src/utils.d.ts +16 -33
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -15,7 +15,8 @@ var ErrorType;
|
|
|
15
15
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
16
16
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
17
17
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
18
|
-
ErrorType["
|
|
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,7 +95,8 @@ 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.";
|
|
82
|
-
ConstantString.
|
|
98
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
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";
|
|
85
102
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
@@ -92,6 +109,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
|
92
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
93
110
|
ConstantString.ContainerType = "Container";
|
|
94
111
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
112
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
95
113
|
ConstantString.ResponseCodeFor20X = [
|
|
96
114
|
"200",
|
|
97
115
|
"201",
|
|
@@ -167,229 +185,19 @@ class Utils {
|
|
|
167
185
|
}
|
|
168
186
|
return false;
|
|
169
187
|
}
|
|
170
|
-
static
|
|
171
|
-
|
|
172
|
-
requiredNum: 0,
|
|
173
|
-
optionalNum: 0,
|
|
174
|
-
isValid: true,
|
|
175
|
-
};
|
|
176
|
-
if (!paramObject) {
|
|
177
|
-
return paramResult;
|
|
178
|
-
}
|
|
179
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
180
|
-
const param = paramObject[i];
|
|
181
|
-
const schema = param.schema;
|
|
182
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
183
|
-
paramResult.isValid = false;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
187
|
-
if (isCopilot) {
|
|
188
|
-
if (isRequiredWithoutDefault) {
|
|
189
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
193
|
-
}
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
197
|
-
if (isRequiredWithoutDefault) {
|
|
198
|
-
paramResult.isValid = false;
|
|
199
|
-
}
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
if (schema.type !== "boolean" &&
|
|
203
|
-
schema.type !== "string" &&
|
|
204
|
-
schema.type !== "number" &&
|
|
205
|
-
schema.type !== "integer") {
|
|
206
|
-
if (isRequiredWithoutDefault) {
|
|
207
|
-
paramResult.isValid = false;
|
|
208
|
-
}
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (param.in === "query" || param.in === "path") {
|
|
212
|
-
if (isRequiredWithoutDefault) {
|
|
213
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return paramResult;
|
|
221
|
-
}
|
|
222
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
223
|
-
var _a;
|
|
224
|
-
const paramResult = {
|
|
225
|
-
requiredNum: 0,
|
|
226
|
-
optionalNum: 0,
|
|
227
|
-
isValid: true,
|
|
228
|
-
};
|
|
229
|
-
if (Object.keys(schema).length === 0) {
|
|
230
|
-
return paramResult;
|
|
231
|
-
}
|
|
232
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
233
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
234
|
-
paramResult.isValid = false;
|
|
235
|
-
return paramResult;
|
|
236
|
-
}
|
|
237
|
-
if (schema.type === "string" ||
|
|
238
|
-
schema.type === "integer" ||
|
|
239
|
-
schema.type === "boolean" ||
|
|
240
|
-
schema.type === "number") {
|
|
241
|
-
if (isRequiredWithoutDefault) {
|
|
242
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else if (schema.type === "object") {
|
|
249
|
-
const { properties } = schema;
|
|
250
|
-
for (const property in properties) {
|
|
251
|
-
let isRequired = false;
|
|
252
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
253
|
-
isRequired = true;
|
|
254
|
-
}
|
|
255
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
256
|
-
paramResult.requiredNum += result.requiredNum;
|
|
257
|
-
paramResult.optionalNum += result.optionalNum;
|
|
258
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
263
|
-
paramResult.isValid = false;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return paramResult;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Checks if the given API is supported.
|
|
270
|
-
* @param {string} method - The HTTP method of the API.
|
|
271
|
-
* @param {string} path - The path of the API.
|
|
272
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
273
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
274
|
-
* @description The following APIs are supported:
|
|
275
|
-
* 1. only support Get/Post operation without auth property
|
|
276
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
277
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
278
|
-
* 4. request body + required parameters <= 1
|
|
279
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
280
|
-
* 6. only support request body with “application/json” content type
|
|
281
|
-
*/
|
|
282
|
-
static isSupportedApi(method, path, spec, options) {
|
|
283
|
-
var _a;
|
|
284
|
-
const pathObj = spec.paths[path];
|
|
285
|
-
method = method.toLocaleLowerCase();
|
|
286
|
-
if (pathObj) {
|
|
287
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
288
|
-
const securities = pathObj[method].security;
|
|
289
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
290
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
const operationObject = pathObj[method];
|
|
294
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
const paramObject = operationObject.parameters;
|
|
298
|
-
const requestBody = operationObject.requestBody;
|
|
299
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
300
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
301
|
-
if (mediaTypesCount > 1) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
305
|
-
if (Object.keys(responseJson).length === 0) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
let requestBodyParamResult = {
|
|
309
|
-
requiredNum: 0,
|
|
310
|
-
optionalNum: 0,
|
|
311
|
-
isValid: true,
|
|
312
|
-
};
|
|
313
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
314
|
-
if (requestJsonBody) {
|
|
315
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
316
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
320
|
-
}
|
|
321
|
-
if (!requestBodyParamResult.isValid) {
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
325
|
-
if (!paramResult.isValid) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
// Copilot support arbitrary parameters
|
|
329
|
-
if (isCopilot) {
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
333
|
-
if (options.allowMultipleParameters &&
|
|
334
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
335
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
else if (requestBodyParamResult.requiredNum +
|
|
341
|
-
requestBodyParamResult.optionalNum +
|
|
342
|
-
paramResult.requiredNum +
|
|
343
|
-
paramResult.optionalNum ===
|
|
344
|
-
0) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return false;
|
|
188
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
189
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
353
190
|
}
|
|
354
|
-
static
|
|
355
|
-
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
359
|
-
// Currently we don't support multiple auth in one operation
|
|
360
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
for (const auths of authSchemaArray) {
|
|
364
|
-
if (auths.length === 1) {
|
|
365
|
-
if (!options.allowOauth2 &&
|
|
366
|
-
options.allowAPIKeyAuth &&
|
|
367
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
368
|
-
return true;
|
|
369
|
-
}
|
|
370
|
-
else if (!options.allowAPIKeyAuth &&
|
|
371
|
-
options.allowOauth2 &&
|
|
372
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
else if (options.allowAPIKeyAuth &&
|
|
376
|
-
options.allowOauth2 &&
|
|
377
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
378
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
191
|
+
static isBearerTokenAuth(authScheme) {
|
|
192
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
385
193
|
}
|
|
386
|
-
static isAPIKeyAuth(
|
|
387
|
-
return
|
|
194
|
+
static isAPIKeyAuth(authScheme) {
|
|
195
|
+
return authScheme.type === "apiKey";
|
|
388
196
|
}
|
|
389
|
-
static
|
|
390
|
-
return (
|
|
391
|
-
|
|
392
|
-
|
|
197
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
198
|
+
return !!(authScheme.type === "oauth2" &&
|
|
199
|
+
authScheme.flows &&
|
|
200
|
+
authScheme.flows.authorizationCode);
|
|
393
201
|
}
|
|
394
202
|
static getAuthArray(securities, spec) {
|
|
395
203
|
var _a;
|
|
@@ -402,7 +210,7 @@ class Utils {
|
|
|
402
210
|
for (const name in security) {
|
|
403
211
|
const auth = securitySchemas[name];
|
|
404
212
|
authArray.push({
|
|
405
|
-
|
|
213
|
+
authScheme: auth,
|
|
406
214
|
name: name,
|
|
407
215
|
});
|
|
408
216
|
}
|
|
@@ -420,18 +228,22 @@ class Utils {
|
|
|
420
228
|
static getResponseJson(operationObject) {
|
|
421
229
|
var _a, _b;
|
|
422
230
|
let json = {};
|
|
231
|
+
let multipleMediaType = false;
|
|
423
232
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
424
233
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
425
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
426
|
-
if (mediaTypesCount > 1) {
|
|
427
|
-
return {};
|
|
428
|
-
}
|
|
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
|
-
|
|
237
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
238
|
+
multipleMediaType = true;
|
|
239
|
+
json = {};
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
432
244
|
}
|
|
433
245
|
}
|
|
434
|
-
return json;
|
|
246
|
+
return { json, multipleMediaType };
|
|
435
247
|
}
|
|
436
248
|
static convertPathToCamelCase(path) {
|
|
437
249
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -451,10 +263,10 @@ class Utils {
|
|
|
451
263
|
return undefined;
|
|
452
264
|
}
|
|
453
265
|
}
|
|
454
|
-
static
|
|
266
|
+
static resolveEnv(str) {
|
|
455
267
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
456
|
-
let matches = placeHolderReg.exec(
|
|
457
|
-
let
|
|
268
|
+
let matches = placeHolderReg.exec(str);
|
|
269
|
+
let newStr = str;
|
|
458
270
|
while (matches != null) {
|
|
459
271
|
const envVar = matches[1];
|
|
460
272
|
const envVal = process.env[envVar];
|
|
@@ -462,17 +274,17 @@ class Utils {
|
|
|
462
274
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
463
275
|
}
|
|
464
276
|
else {
|
|
465
|
-
|
|
277
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
466
278
|
}
|
|
467
|
-
matches = placeHolderReg.exec(
|
|
279
|
+
matches = placeHolderReg.exec(str);
|
|
468
280
|
}
|
|
469
|
-
return
|
|
281
|
+
return newStr;
|
|
470
282
|
}
|
|
471
283
|
static checkServerUrl(servers) {
|
|
472
284
|
const errors = [];
|
|
473
285
|
let serverUrl;
|
|
474
286
|
try {
|
|
475
|
-
serverUrl = Utils.
|
|
287
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
476
288
|
}
|
|
477
289
|
catch (err) {
|
|
478
290
|
errors.push({
|
|
@@ -503,6 +315,7 @@ class Utils {
|
|
|
503
315
|
return errors;
|
|
504
316
|
}
|
|
505
317
|
static validateServer(spec, options) {
|
|
318
|
+
var _a;
|
|
506
319
|
const errors = [];
|
|
507
320
|
let hasTopLevelServers = false;
|
|
508
321
|
let hasPathLevelServers = false;
|
|
@@ -523,7 +336,7 @@ class Utils {
|
|
|
523
336
|
}
|
|
524
337
|
for (const method in methods) {
|
|
525
338
|
const operationObject = methods[method];
|
|
526
|
-
if (
|
|
339
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
527
340
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
528
341
|
hasOperationLevelServers = true;
|
|
529
342
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -566,6 +379,7 @@ class Utils {
|
|
|
566
379
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
567
380
|
}
|
|
568
381
|
if (isRequired && schema.default === undefined) {
|
|
382
|
+
parameter.isRequired = true;
|
|
569
383
|
requiredParams.push(parameter);
|
|
570
384
|
}
|
|
571
385
|
else {
|
|
@@ -629,6 +443,7 @@ class Utils {
|
|
|
629
443
|
}
|
|
630
444
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
631
445
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
446
|
+
parameter.isRequired = true;
|
|
632
447
|
requiredParams.push(parameter);
|
|
633
448
|
}
|
|
634
449
|
else {
|
|
@@ -648,13 +463,7 @@ class Utils {
|
|
|
648
463
|
}
|
|
649
464
|
}
|
|
650
465
|
const operationId = operationItem.operationId;
|
|
651
|
-
const parameters = [];
|
|
652
|
-
if (requiredParams.length !== 0) {
|
|
653
|
-
parameters.push(...requiredParams);
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
parameters.push(optionalParams[0]);
|
|
657
|
-
}
|
|
466
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
658
467
|
const command = {
|
|
659
468
|
context: ["compose"],
|
|
660
469
|
type: "query",
|
|
@@ -663,105 +472,526 @@ class Utils {
|
|
|
663
472
|
parameters: parameters,
|
|
664
473
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
665
474
|
};
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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 "";
|
|
487
|
+
}
|
|
488
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
489
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
490
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
673
491
|
}
|
|
674
|
-
return
|
|
492
|
+
return safeRegistrationIdEnvName;
|
|
675
493
|
}
|
|
676
|
-
static
|
|
677
|
-
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;
|
|
678
513
|
const result = {};
|
|
679
514
|
for (const path in paths) {
|
|
680
515
|
const methods = paths[path];
|
|
681
516
|
for (const method in methods) {
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
const
|
|
685
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
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
|
+
};
|
|
686
525
|
}
|
|
687
526
|
}
|
|
688
527
|
}
|
|
528
|
+
this.apiMap = result;
|
|
689
529
|
return result;
|
|
690
530
|
}
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
// Server validation
|
|
701
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
702
|
-
errors.push(...serverErrors);
|
|
703
|
-
// Remote reference not supported
|
|
704
|
-
const refPaths = parser.$refs.paths();
|
|
705
|
-
// refPaths [0] is the current spec file path
|
|
706
|
-
if (refPaths.length > 1) {
|
|
707
|
-
errors.push({
|
|
708
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
709
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
710
|
-
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,
|
|
711
538
|
});
|
|
712
539
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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({
|
|
717
554
|
type: ErrorType.NoSupportedApi,
|
|
718
555
|
content: ConstantString.NoSupportedApi,
|
|
719
556
|
});
|
|
720
557
|
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
validateSpecOperationId() {
|
|
561
|
+
const result = { errors: [], warnings: [] };
|
|
562
|
+
const apiMap = this.listAPIs();
|
|
721
563
|
// OperationId missing
|
|
722
564
|
const apisMissingOperationId = [];
|
|
723
565
|
for (const key in apiMap) {
|
|
724
|
-
const
|
|
725
|
-
if (!
|
|
566
|
+
const { operation } = apiMap[key];
|
|
567
|
+
if (!operation.operationId) {
|
|
726
568
|
apisMissingOperationId.push(key);
|
|
727
569
|
}
|
|
728
570
|
}
|
|
729
571
|
if (apisMissingOperationId.length > 0) {
|
|
730
|
-
warnings.push({
|
|
572
|
+
result.warnings.push({
|
|
731
573
|
type: WarningType.OperationIdMissing,
|
|
732
574
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
733
575
|
data: apisMissingOperationId,
|
|
734
576
|
});
|
|
735
577
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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;
|
|
586
|
+
}
|
|
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;
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
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;
|
|
608
|
+
}
|
|
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);
|
|
615
|
+
}
|
|
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));
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
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;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
739
769
|
}
|
|
740
|
-
|
|
741
|
-
|
|
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);
|
|
742
884
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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: [],
|
|
747
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;
|
|
748
921
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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;
|
|
755
936
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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;
|
|
759
965
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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}`);
|
|
763
994
|
}
|
|
764
|
-
return safeRegistrationIdEnvName;
|
|
765
995
|
}
|
|
766
996
|
}
|
|
767
997
|
|
|
@@ -781,6 +1011,7 @@ class SpecParser {
|
|
|
781
1011
|
allowSwagger: false,
|
|
782
1012
|
allowAPIKeyAuth: false,
|
|
783
1013
|
allowMultipleParameters: false,
|
|
1014
|
+
allowBearerTokenAuth: false,
|
|
784
1015
|
allowOauth2: false,
|
|
785
1016
|
allowMethods: ["get", "post"],
|
|
786
1017
|
projectType: ProjectType.SME,
|
|
@@ -798,11 +1029,7 @@ class SpecParser {
|
|
|
798
1029
|
try {
|
|
799
1030
|
try {
|
|
800
1031
|
await this.loadSpec();
|
|
801
|
-
await this.parser.validate(this.spec
|
|
802
|
-
validate: {
|
|
803
|
-
schema: false,
|
|
804
|
-
},
|
|
805
|
-
});
|
|
1032
|
+
await this.parser.validate(this.spec);
|
|
806
1033
|
}
|
|
807
1034
|
catch (e) {
|
|
808
1035
|
return {
|
|
@@ -811,16 +1038,46 @@ class SpecParser {
|
|
|
811
1038
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
812
1039
|
};
|
|
813
1040
|
}
|
|
1041
|
+
const errors = [];
|
|
1042
|
+
const warnings = [];
|
|
814
1043
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
815
1044
|
return {
|
|
816
1045
|
status: ValidationStatus.Error,
|
|
817
1046
|
warnings: [],
|
|
818
1047
|
errors: [
|
|
819
|
-
{
|
|
1048
|
+
{
|
|
1049
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1050
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1051
|
+
},
|
|
820
1052
|
],
|
|
821
1053
|
};
|
|
822
1054
|
}
|
|
823
|
-
|
|
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
|
+
};
|
|
824
1081
|
}
|
|
825
1082
|
catch (err) {
|
|
826
1083
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -829,17 +1086,20 @@ class SpecParser {
|
|
|
829
1086
|
async listSupportedAPIInfo() {
|
|
830
1087
|
try {
|
|
831
1088
|
await this.loadSpec();
|
|
832
|
-
const apiMap = this.
|
|
1089
|
+
const apiMap = this.getAPIs(this.spec);
|
|
833
1090
|
const apiInfos = [];
|
|
834
1091
|
for (const key in apiMap) {
|
|
835
|
-
const
|
|
1092
|
+
const { operation, isValid } = apiMap[key];
|
|
1093
|
+
if (!isValid) {
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
836
1096
|
const [method, path] = key.split(" ");
|
|
837
|
-
const operationId =
|
|
1097
|
+
const operationId = operation.operationId;
|
|
838
1098
|
// In Browser environment, this api is by default not support api without operationId
|
|
839
1099
|
if (!operationId) {
|
|
840
1100
|
continue;
|
|
841
1101
|
}
|
|
842
|
-
const
|
|
1102
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
843
1103
|
const apiInfo = {
|
|
844
1104
|
method: method,
|
|
845
1105
|
path: path,
|
|
@@ -848,9 +1108,6 @@ class SpecParser {
|
|
|
848
1108
|
parameters: command.parameters,
|
|
849
1109
|
description: command.description,
|
|
850
1110
|
};
|
|
851
|
-
if (warning) {
|
|
852
|
-
apiInfo.warning = warning;
|
|
853
|
-
}
|
|
854
1111
|
apiInfos.push(apiInfo);
|
|
855
1112
|
}
|
|
856
1113
|
return apiInfos;
|
|
@@ -909,13 +1166,22 @@ class SpecParser {
|
|
|
909
1166
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
910
1167
|
}
|
|
911
1168
|
}
|
|
912
|
-
|
|
1169
|
+
getAPIs(spec) {
|
|
913
1170
|
if (this.apiMap !== undefined) {
|
|
914
1171
|
return this.apiMap;
|
|
915
1172
|
}
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
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;
|
|
919
1185
|
}
|
|
920
1186
|
}
|
|
921
1187
|
|
|
@@ -923,7 +1189,7 @@ class SpecParser {
|
|
|
923
1189
|
class AdaptiveCardGenerator {
|
|
924
1190
|
static generateAdaptiveCard(operationItem) {
|
|
925
1191
|
try {
|
|
926
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1192
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
927
1193
|
let cardBody = [];
|
|
928
1194
|
let schema = json.schema;
|
|
929
1195
|
let jsonPath = "$";
|