@microsoft/m365-spec-parser 0.1.1-alpha.f04ac4ba4.0 → 0.1.1-alpha.f4dd51600.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 +604 -329
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1006 -601
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +604 -329
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1014 -607
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +5 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +75 -20
- package/dist/src/manifestUpdater.d.ts +4 -3
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +4 -3
- 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";
|
|
@@ -90,8 +107,10 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
90
107
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
91
108
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
92
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
110
|
+
ConstantString.ImageType = "Image";
|
|
93
111
|
ConstantString.ContainerType = "Container";
|
|
94
112
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
113
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
95
114
|
ConstantString.ResponseCodeFor20X = [
|
|
96
115
|
"200",
|
|
97
116
|
"201",
|
|
@@ -152,7 +171,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
152
171
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
153
172
|
ConstantString.CommandTitleMaxLens = 32;
|
|
154
173
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
155
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
174
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
175
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
156
176
|
|
|
157
177
|
// Copyright (c) Microsoft Corporation.
|
|
158
178
|
class Utils {
|
|
@@ -167,229 +187,19 @@ class Utils {
|
|
|
167
187
|
}
|
|
168
188
|
return false;
|
|
169
189
|
}
|
|
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;
|
|
190
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
191
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
353
192
|
}
|
|
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;
|
|
193
|
+
static isBearerTokenAuth(authScheme) {
|
|
194
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
385
195
|
}
|
|
386
|
-
static isAPIKeyAuth(
|
|
387
|
-
return
|
|
196
|
+
static isAPIKeyAuth(authScheme) {
|
|
197
|
+
return authScheme.type === "apiKey";
|
|
388
198
|
}
|
|
389
|
-
static
|
|
390
|
-
return (
|
|
391
|
-
|
|
392
|
-
|
|
199
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
200
|
+
return !!(authScheme.type === "oauth2" &&
|
|
201
|
+
authScheme.flows &&
|
|
202
|
+
authScheme.flows.authorizationCode);
|
|
393
203
|
}
|
|
394
204
|
static getAuthArray(securities, spec) {
|
|
395
205
|
var _a;
|
|
@@ -402,7 +212,7 @@ class Utils {
|
|
|
402
212
|
for (const name in security) {
|
|
403
213
|
const auth = securitySchemas[name];
|
|
404
214
|
authArray.push({
|
|
405
|
-
|
|
215
|
+
authScheme: auth,
|
|
406
216
|
name: name,
|
|
407
217
|
});
|
|
408
218
|
}
|
|
@@ -420,18 +230,22 @@ class Utils {
|
|
|
420
230
|
static getResponseJson(operationObject) {
|
|
421
231
|
var _a, _b;
|
|
422
232
|
let json = {};
|
|
233
|
+
let multipleMediaType = false;
|
|
423
234
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
424
235
|
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
236
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
237
|
+
multipleMediaType = false;
|
|
430
238
|
json = responseObject.content["application/json"];
|
|
431
|
-
|
|
239
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
240
|
+
multipleMediaType = true;
|
|
241
|
+
json = {};
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
432
246
|
}
|
|
433
247
|
}
|
|
434
|
-
return json;
|
|
248
|
+
return { json, multipleMediaType };
|
|
435
249
|
}
|
|
436
250
|
static convertPathToCamelCase(path) {
|
|
437
251
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -451,10 +265,10 @@ class Utils {
|
|
|
451
265
|
return undefined;
|
|
452
266
|
}
|
|
453
267
|
}
|
|
454
|
-
static
|
|
268
|
+
static resolveEnv(str) {
|
|
455
269
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
456
|
-
let matches = placeHolderReg.exec(
|
|
457
|
-
let
|
|
270
|
+
let matches = placeHolderReg.exec(str);
|
|
271
|
+
let newStr = str;
|
|
458
272
|
while (matches != null) {
|
|
459
273
|
const envVar = matches[1];
|
|
460
274
|
const envVal = process.env[envVar];
|
|
@@ -462,17 +276,17 @@ class Utils {
|
|
|
462
276
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
463
277
|
}
|
|
464
278
|
else {
|
|
465
|
-
|
|
279
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
466
280
|
}
|
|
467
|
-
matches = placeHolderReg.exec(
|
|
281
|
+
matches = placeHolderReg.exec(str);
|
|
468
282
|
}
|
|
469
|
-
return
|
|
283
|
+
return newStr;
|
|
470
284
|
}
|
|
471
285
|
static checkServerUrl(servers) {
|
|
472
286
|
const errors = [];
|
|
473
287
|
let serverUrl;
|
|
474
288
|
try {
|
|
475
|
-
serverUrl = Utils.
|
|
289
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
476
290
|
}
|
|
477
291
|
catch (err) {
|
|
478
292
|
errors.push({
|
|
@@ -503,6 +317,7 @@ class Utils {
|
|
|
503
317
|
return errors;
|
|
504
318
|
}
|
|
505
319
|
static validateServer(spec, options) {
|
|
320
|
+
var _a;
|
|
506
321
|
const errors = [];
|
|
507
322
|
let hasTopLevelServers = false;
|
|
508
323
|
let hasPathLevelServers = false;
|
|
@@ -523,7 +338,7 @@ class Utils {
|
|
|
523
338
|
}
|
|
524
339
|
for (const method in methods) {
|
|
525
340
|
const operationObject = methods[method];
|
|
526
|
-
if (
|
|
341
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
527
342
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
528
343
|
hasOperationLevelServers = true;
|
|
529
344
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -566,6 +381,7 @@ class Utils {
|
|
|
566
381
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
567
382
|
}
|
|
568
383
|
if (isRequired && schema.default === undefined) {
|
|
384
|
+
parameter.isRequired = true;
|
|
569
385
|
requiredParams.push(parameter);
|
|
570
386
|
}
|
|
571
387
|
else {
|
|
@@ -629,6 +445,7 @@ class Utils {
|
|
|
629
445
|
}
|
|
630
446
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
631
447
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
448
|
+
parameter.isRequired = true;
|
|
632
449
|
requiredParams.push(parameter);
|
|
633
450
|
}
|
|
634
451
|
else {
|
|
@@ -648,13 +465,7 @@ class Utils {
|
|
|
648
465
|
}
|
|
649
466
|
}
|
|
650
467
|
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
|
-
}
|
|
468
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
658
469
|
const command = {
|
|
659
470
|
context: ["compose"],
|
|
660
471
|
type: "query",
|
|
@@ -663,105 +474,535 @@ class Utils {
|
|
|
663
474
|
parameters: parameters,
|
|
664
475
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
665
476
|
};
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
477
|
+
return command;
|
|
478
|
+
}
|
|
479
|
+
static format(str, ...args) {
|
|
480
|
+
let index = 0;
|
|
481
|
+
return str.replace(/%s/g, () => {
|
|
482
|
+
const arg = args[index++];
|
|
483
|
+
return arg !== undefined ? arg : "";
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
487
|
+
if (!authName) {
|
|
488
|
+
return "";
|
|
673
489
|
}
|
|
674
|
-
|
|
490
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
491
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
492
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
493
|
+
}
|
|
494
|
+
return safeRegistrationIdEnvName;
|
|
675
495
|
}
|
|
676
|
-
static
|
|
677
|
-
const
|
|
496
|
+
static getServerObject(spec, method, path) {
|
|
497
|
+
const pathObj = spec.paths[path];
|
|
498
|
+
const operationObject = pathObj[method];
|
|
499
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
500
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
501
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
502
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
503
|
+
return serverUrl;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Copyright (c) Microsoft Corporation.
|
|
508
|
+
class Validator {
|
|
509
|
+
listAPIs() {
|
|
510
|
+
var _a;
|
|
511
|
+
if (this.apiMap) {
|
|
512
|
+
return this.apiMap;
|
|
513
|
+
}
|
|
514
|
+
const paths = this.spec.paths;
|
|
678
515
|
const result = {};
|
|
679
516
|
for (const path in paths) {
|
|
680
517
|
const methods = paths[path];
|
|
681
518
|
for (const method in methods) {
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
const
|
|
685
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
519
|
+
const operationObject = methods[method];
|
|
520
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
521
|
+
const validateResult = this.validateAPI(method, path);
|
|
522
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
523
|
+
operation: operationObject,
|
|
524
|
+
isValid: validateResult.isValid,
|
|
525
|
+
reason: validateResult.reason,
|
|
526
|
+
};
|
|
686
527
|
}
|
|
687
528
|
}
|
|
688
529
|
}
|
|
530
|
+
this.apiMap = result;
|
|
689
531
|
return result;
|
|
690
532
|
}
|
|
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,
|
|
533
|
+
validateSpecVersion() {
|
|
534
|
+
const result = { errors: [], warnings: [] };
|
|
535
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
536
|
+
result.errors.push({
|
|
537
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
538
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
539
|
+
data: this.spec.openapi,
|
|
711
540
|
});
|
|
712
541
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
validateSpecServer() {
|
|
545
|
+
const result = { errors: [], warnings: [] };
|
|
546
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
547
|
+
result.errors.push(...serverErrors);
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
550
|
+
validateSpecNoSupportAPI() {
|
|
551
|
+
const result = { errors: [], warnings: [] };
|
|
552
|
+
const apiMap = this.listAPIs();
|
|
553
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
554
|
+
if (validAPIs.length === 0) {
|
|
555
|
+
const data = [];
|
|
556
|
+
for (const key in apiMap) {
|
|
557
|
+
const { reason } = apiMap[key];
|
|
558
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
559
|
+
data.push(apiInvalidReason);
|
|
560
|
+
}
|
|
561
|
+
result.errors.push({
|
|
717
562
|
type: ErrorType.NoSupportedApi,
|
|
718
563
|
content: ConstantString.NoSupportedApi,
|
|
564
|
+
data,
|
|
719
565
|
});
|
|
720
566
|
}
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
validateSpecOperationId() {
|
|
570
|
+
const result = { errors: [], warnings: [] };
|
|
571
|
+
const apiMap = this.listAPIs();
|
|
721
572
|
// OperationId missing
|
|
722
573
|
const apisMissingOperationId = [];
|
|
723
574
|
for (const key in apiMap) {
|
|
724
|
-
const
|
|
725
|
-
if (!
|
|
575
|
+
const { operation } = apiMap[key];
|
|
576
|
+
if (!operation.operationId) {
|
|
726
577
|
apisMissingOperationId.push(key);
|
|
727
578
|
}
|
|
728
579
|
}
|
|
729
580
|
if (apisMissingOperationId.length > 0) {
|
|
730
|
-
warnings.push({
|
|
581
|
+
result.warnings.push({
|
|
731
582
|
type: WarningType.OperationIdMissing,
|
|
732
583
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
733
584
|
data: apisMissingOperationId,
|
|
734
585
|
});
|
|
735
586
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
validateMethodAndPath(method, path) {
|
|
590
|
+
const result = { isValid: true, reason: [] };
|
|
591
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
592
|
+
result.isValid = false;
|
|
593
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
const pathObj = this.spec.paths[path];
|
|
597
|
+
if (!pathObj || !pathObj[method]) {
|
|
598
|
+
result.isValid = false;
|
|
599
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
validateResponse(method, path) {
|
|
605
|
+
const result = { isValid: true, reason: [] };
|
|
606
|
+
const operationObject = this.spec.paths[path][method];
|
|
607
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
608
|
+
// only support response body only contains “application/json” content type
|
|
609
|
+
if (multipleMediaType) {
|
|
610
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
611
|
+
}
|
|
612
|
+
else if (Object.keys(json).length === 0) {
|
|
613
|
+
// response body should not be empty
|
|
614
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
615
|
+
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
validateServer(method, path) {
|
|
619
|
+
const result = { isValid: true, reason: [] };
|
|
620
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
621
|
+
if (!serverObj) {
|
|
622
|
+
// should contain server URL
|
|
623
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
// server url should be absolute url with https protocol
|
|
627
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
628
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
739
629
|
}
|
|
740
|
-
|
|
741
|
-
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
validateAuth(method, path) {
|
|
633
|
+
const pathObj = this.spec.paths[path];
|
|
634
|
+
const operationObject = pathObj[method];
|
|
635
|
+
const securities = operationObject.security;
|
|
636
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
637
|
+
if (authSchemeArray.length === 0) {
|
|
638
|
+
return { isValid: true, reason: [] };
|
|
742
639
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
640
|
+
if (this.options.allowAPIKeyAuth ||
|
|
641
|
+
this.options.allowOauth2 ||
|
|
642
|
+
this.options.allowBearerTokenAuth) {
|
|
643
|
+
// Currently we don't support multiple auth in one operation
|
|
644
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
645
|
+
return {
|
|
646
|
+
isValid: false,
|
|
647
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
for (const auths of authSchemeArray) {
|
|
651
|
+
if (auths.length === 1) {
|
|
652
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
653
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
654
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
655
|
+
return { isValid: true, reason: [] };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
661
|
+
}
|
|
662
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
663
|
+
var _a;
|
|
664
|
+
const paramResult = {
|
|
665
|
+
requiredNum: 0,
|
|
666
|
+
optionalNum: 0,
|
|
667
|
+
isValid: true,
|
|
668
|
+
reason: [],
|
|
747
669
|
};
|
|
670
|
+
if (Object.keys(schema).length === 0) {
|
|
671
|
+
return paramResult;
|
|
672
|
+
}
|
|
673
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
674
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
675
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
676
|
+
paramResult.isValid = false;
|
|
677
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
678
|
+
return paramResult;
|
|
679
|
+
}
|
|
680
|
+
if (schema.type === "string" ||
|
|
681
|
+
schema.type === "integer" ||
|
|
682
|
+
schema.type === "boolean" ||
|
|
683
|
+
schema.type === "number") {
|
|
684
|
+
if (isRequiredWithoutDefault) {
|
|
685
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else if (schema.type === "object") {
|
|
692
|
+
const { properties } = schema;
|
|
693
|
+
for (const property in properties) {
|
|
694
|
+
let isRequired = false;
|
|
695
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
696
|
+
isRequired = true;
|
|
697
|
+
}
|
|
698
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
699
|
+
paramResult.requiredNum += result.requiredNum;
|
|
700
|
+
paramResult.optionalNum += result.optionalNum;
|
|
701
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
702
|
+
paramResult.reason.push(...result.reason);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
707
|
+
paramResult.isValid = false;
|
|
708
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return paramResult;
|
|
748
712
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
713
|
+
checkParamSchema(paramObject) {
|
|
714
|
+
const paramResult = {
|
|
715
|
+
requiredNum: 0,
|
|
716
|
+
optionalNum: 0,
|
|
717
|
+
isValid: true,
|
|
718
|
+
reason: [],
|
|
719
|
+
};
|
|
720
|
+
if (!paramObject) {
|
|
721
|
+
return paramResult;
|
|
722
|
+
}
|
|
723
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
724
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
725
|
+
const param = paramObject[i];
|
|
726
|
+
const schema = param.schema;
|
|
727
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
728
|
+
paramResult.isValid = false;
|
|
729
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
733
|
+
if (isCopilot) {
|
|
734
|
+
if (isRequiredWithoutDefault) {
|
|
735
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
743
|
+
if (isRequiredWithoutDefault) {
|
|
744
|
+
paramResult.isValid = false;
|
|
745
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (schema.type !== "boolean" &&
|
|
750
|
+
schema.type !== "string" &&
|
|
751
|
+
schema.type !== "number" &&
|
|
752
|
+
schema.type !== "integer") {
|
|
753
|
+
if (isRequiredWithoutDefault) {
|
|
754
|
+
paramResult.isValid = false;
|
|
755
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
756
|
+
}
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (param.in === "query" || param.in === "path") {
|
|
760
|
+
if (isRequiredWithoutDefault) {
|
|
761
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return paramResult;
|
|
755
769
|
}
|
|
756
|
-
|
|
757
|
-
if (
|
|
758
|
-
|
|
770
|
+
hasNestedObjectInSchema(schema) {
|
|
771
|
+
if (schema.type === "object") {
|
|
772
|
+
for (const property in schema.properties) {
|
|
773
|
+
const nestedSchema = schema.properties[property];
|
|
774
|
+
if (nestedSchema.type === "object") {
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
759
778
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Copyright (c) Microsoft Corporation.
|
|
784
|
+
class CopilotValidator extends Validator {
|
|
785
|
+
constructor(spec, options) {
|
|
786
|
+
super();
|
|
787
|
+
this.projectType = ProjectType.Copilot;
|
|
788
|
+
this.options = options;
|
|
789
|
+
this.spec = spec;
|
|
790
|
+
}
|
|
791
|
+
validateSpec() {
|
|
792
|
+
const result = { errors: [], warnings: [] };
|
|
793
|
+
// validate spec version
|
|
794
|
+
let validationResult = this.validateSpecVersion();
|
|
795
|
+
result.errors.push(...validationResult.errors);
|
|
796
|
+
// validate spec server
|
|
797
|
+
validationResult = this.validateSpecServer();
|
|
798
|
+
result.errors.push(...validationResult.errors);
|
|
799
|
+
// validate no supported API
|
|
800
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
801
|
+
result.errors.push(...validationResult.errors);
|
|
802
|
+
// validate operationId missing
|
|
803
|
+
validationResult = this.validateSpecOperationId();
|
|
804
|
+
result.warnings.push(...validationResult.warnings);
|
|
805
|
+
return result;
|
|
806
|
+
}
|
|
807
|
+
validateAPI(method, path) {
|
|
808
|
+
const result = { isValid: true, reason: [] };
|
|
809
|
+
method = method.toLocaleLowerCase();
|
|
810
|
+
// validate method and path
|
|
811
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
812
|
+
if (!methodAndPathResult.isValid) {
|
|
813
|
+
return methodAndPathResult;
|
|
814
|
+
}
|
|
815
|
+
const operationObject = this.spec.paths[path][method];
|
|
816
|
+
// validate auth
|
|
817
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
818
|
+
result.reason.push(...authCheckResult.reason);
|
|
819
|
+
// validate operationId
|
|
820
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
821
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
822
|
+
}
|
|
823
|
+
// validate server
|
|
824
|
+
const validateServerResult = this.validateServer(method, path);
|
|
825
|
+
result.reason.push(...validateServerResult.reason);
|
|
826
|
+
// validate response
|
|
827
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
828
|
+
result.reason.push(...validateResponseResult.reason);
|
|
829
|
+
// validate requestBody
|
|
830
|
+
const requestBody = operationObject.requestBody;
|
|
831
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
832
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
833
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
834
|
+
}
|
|
835
|
+
if (requestJsonBody) {
|
|
836
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
837
|
+
if (requestBodySchema.type !== "object") {
|
|
838
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
839
|
+
}
|
|
840
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
841
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
842
|
+
}
|
|
843
|
+
// validate parameters
|
|
844
|
+
const paramObject = operationObject.parameters;
|
|
845
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
846
|
+
result.reason.push(...paramResult.reason);
|
|
847
|
+
if (result.reason.length > 0) {
|
|
848
|
+
result.isValid = false;
|
|
849
|
+
}
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Copyright (c) Microsoft Corporation.
|
|
855
|
+
class SMEValidator extends Validator {
|
|
856
|
+
constructor(spec, options) {
|
|
857
|
+
super();
|
|
858
|
+
this.projectType = ProjectType.SME;
|
|
859
|
+
this.options = options;
|
|
860
|
+
this.spec = spec;
|
|
861
|
+
}
|
|
862
|
+
validateSpec() {
|
|
863
|
+
const result = { errors: [], warnings: [] };
|
|
864
|
+
// validate spec version
|
|
865
|
+
let validationResult = this.validateSpecVersion();
|
|
866
|
+
result.errors.push(...validationResult.errors);
|
|
867
|
+
// validate spec server
|
|
868
|
+
validationResult = this.validateSpecServer();
|
|
869
|
+
result.errors.push(...validationResult.errors);
|
|
870
|
+
// validate no supported API
|
|
871
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
872
|
+
result.errors.push(...validationResult.errors);
|
|
873
|
+
// validate operationId missing
|
|
874
|
+
if (this.options.allowMissingId) {
|
|
875
|
+
validationResult = this.validateSpecOperationId();
|
|
876
|
+
result.warnings.push(...validationResult.warnings);
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
validateAPI(method, path) {
|
|
881
|
+
const result = { isValid: true, reason: [] };
|
|
882
|
+
method = method.toLocaleLowerCase();
|
|
883
|
+
// validate method and path
|
|
884
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
885
|
+
if (!methodAndPathResult.isValid) {
|
|
886
|
+
return methodAndPathResult;
|
|
887
|
+
}
|
|
888
|
+
const operationObject = this.spec.paths[path][method];
|
|
889
|
+
// validate auth
|
|
890
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
891
|
+
result.reason.push(...authCheckResult.reason);
|
|
892
|
+
// validate operationId
|
|
893
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
894
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
895
|
+
}
|
|
896
|
+
// validate server
|
|
897
|
+
const validateServerResult = this.validateServer(method, path);
|
|
898
|
+
result.reason.push(...validateServerResult.reason);
|
|
899
|
+
// validate response
|
|
900
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
901
|
+
result.reason.push(...validateResponseResult.reason);
|
|
902
|
+
let postBodyResult = {
|
|
903
|
+
requiredNum: 0,
|
|
904
|
+
optionalNum: 0,
|
|
905
|
+
isValid: true,
|
|
906
|
+
reason: [],
|
|
907
|
+
};
|
|
908
|
+
// validate requestBody
|
|
909
|
+
const requestBody = operationObject.requestBody;
|
|
910
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
911
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
912
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
913
|
+
}
|
|
914
|
+
if (requestJsonBody) {
|
|
915
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
916
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
917
|
+
result.reason.push(...postBodyResult.reason);
|
|
918
|
+
}
|
|
919
|
+
// validate parameters
|
|
920
|
+
const paramObject = operationObject.parameters;
|
|
921
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
922
|
+
result.reason.push(...paramResult.reason);
|
|
923
|
+
// validate total parameters count
|
|
924
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
925
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
926
|
+
result.reason.push(...paramCountResult.reason);
|
|
927
|
+
}
|
|
928
|
+
if (result.reason.length > 0) {
|
|
929
|
+
result.isValid = false;
|
|
930
|
+
}
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
934
|
+
const result = { isValid: true, reason: [] };
|
|
935
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
936
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
937
|
+
if (totalRequiredParams > 1) {
|
|
938
|
+
if (!this.options.allowMultipleParameters ||
|
|
939
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
940
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
else if (totalParams === 0) {
|
|
944
|
+
result.reason.push(ErrorType.NoParameter);
|
|
945
|
+
}
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
950
|
+
|
|
951
|
+
// Copyright (c) Microsoft Corporation.
|
|
952
|
+
class TeamsAIValidator extends Validator {
|
|
953
|
+
constructor(spec, options) {
|
|
954
|
+
super();
|
|
955
|
+
this.projectType = ProjectType.TeamsAi;
|
|
956
|
+
this.options = options;
|
|
957
|
+
this.spec = spec;
|
|
958
|
+
}
|
|
959
|
+
validateSpec() {
|
|
960
|
+
const result = { errors: [], warnings: [] };
|
|
961
|
+
// validate spec server
|
|
962
|
+
let validationResult = this.validateSpecServer();
|
|
963
|
+
result.errors.push(...validationResult.errors);
|
|
964
|
+
// validate no supported API
|
|
965
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
966
|
+
result.errors.push(...validationResult.errors);
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
validateAPI(method, path) {
|
|
970
|
+
const result = { isValid: true, reason: [] };
|
|
971
|
+
method = method.toLocaleLowerCase();
|
|
972
|
+
// validate method and path
|
|
973
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
974
|
+
if (!methodAndPathResult.isValid) {
|
|
975
|
+
return methodAndPathResult;
|
|
976
|
+
}
|
|
977
|
+
const operationObject = this.spec.paths[path][method];
|
|
978
|
+
// validate operationId
|
|
979
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
980
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
981
|
+
}
|
|
982
|
+
// validate server
|
|
983
|
+
const validateServerResult = this.validateServer(method, path);
|
|
984
|
+
result.reason.push(...validateServerResult.reason);
|
|
985
|
+
if (result.reason.length > 0) {
|
|
986
|
+
result.isValid = false;
|
|
987
|
+
}
|
|
988
|
+
return result;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
class ValidatorFactory {
|
|
993
|
+
static create(spec, options) {
|
|
994
|
+
var _a;
|
|
995
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
996
|
+
switch (type) {
|
|
997
|
+
case ProjectType.SME:
|
|
998
|
+
return new SMEValidator(spec, options);
|
|
999
|
+
case ProjectType.Copilot:
|
|
1000
|
+
return new CopilotValidator(spec, options);
|
|
1001
|
+
case ProjectType.TeamsAi:
|
|
1002
|
+
return new TeamsAIValidator(spec, options);
|
|
1003
|
+
default:
|
|
1004
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
763
1005
|
}
|
|
764
|
-
return safeRegistrationIdEnvName;
|
|
765
1006
|
}
|
|
766
1007
|
}
|
|
767
1008
|
|
|
@@ -781,8 +1022,11 @@ class SpecParser {
|
|
|
781
1022
|
allowSwagger: false,
|
|
782
1023
|
allowAPIKeyAuth: false,
|
|
783
1024
|
allowMultipleParameters: false,
|
|
1025
|
+
allowBearerTokenAuth: false,
|
|
784
1026
|
allowOauth2: false,
|
|
785
1027
|
allowMethods: ["get", "post"],
|
|
1028
|
+
allowConversationStarters: false,
|
|
1029
|
+
allowResponseSemantics: false,
|
|
786
1030
|
projectType: ProjectType.SME,
|
|
787
1031
|
};
|
|
788
1032
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -798,11 +1042,7 @@ class SpecParser {
|
|
|
798
1042
|
try {
|
|
799
1043
|
try {
|
|
800
1044
|
await this.loadSpec();
|
|
801
|
-
await this.parser.validate(this.spec
|
|
802
|
-
validate: {
|
|
803
|
-
schema: false,
|
|
804
|
-
},
|
|
805
|
-
});
|
|
1045
|
+
await this.parser.validate(this.spec);
|
|
806
1046
|
}
|
|
807
1047
|
catch (e) {
|
|
808
1048
|
return {
|
|
@@ -811,16 +1051,46 @@ class SpecParser {
|
|
|
811
1051
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
812
1052
|
};
|
|
813
1053
|
}
|
|
1054
|
+
const errors = [];
|
|
1055
|
+
const warnings = [];
|
|
814
1056
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
815
1057
|
return {
|
|
816
1058
|
status: ValidationStatus.Error,
|
|
817
1059
|
warnings: [],
|
|
818
1060
|
errors: [
|
|
819
|
-
{
|
|
1061
|
+
{
|
|
1062
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1063
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1064
|
+
},
|
|
820
1065
|
],
|
|
821
1066
|
};
|
|
822
1067
|
}
|
|
823
|
-
|
|
1068
|
+
// Remote reference not supported
|
|
1069
|
+
const refPaths = this.parser.$refs.paths();
|
|
1070
|
+
// refPaths [0] is the current spec file path
|
|
1071
|
+
if (refPaths.length > 1) {
|
|
1072
|
+
errors.push({
|
|
1073
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1074
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1075
|
+
data: refPaths,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
const validator = this.getValidator(this.spec);
|
|
1079
|
+
const validationResult = validator.validateSpec();
|
|
1080
|
+
warnings.push(...validationResult.warnings);
|
|
1081
|
+
errors.push(...validationResult.errors);
|
|
1082
|
+
let status = ValidationStatus.Valid;
|
|
1083
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1084
|
+
status = ValidationStatus.Warning;
|
|
1085
|
+
}
|
|
1086
|
+
else if (errors.length > 0) {
|
|
1087
|
+
status = ValidationStatus.Error;
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
status: status,
|
|
1091
|
+
warnings: warnings,
|
|
1092
|
+
errors: errors,
|
|
1093
|
+
};
|
|
824
1094
|
}
|
|
825
1095
|
catch (err) {
|
|
826
1096
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -829,17 +1099,20 @@ class SpecParser {
|
|
|
829
1099
|
async listSupportedAPIInfo() {
|
|
830
1100
|
try {
|
|
831
1101
|
await this.loadSpec();
|
|
832
|
-
const apiMap = this.
|
|
1102
|
+
const apiMap = this.getAPIs(this.spec);
|
|
833
1103
|
const apiInfos = [];
|
|
834
1104
|
for (const key in apiMap) {
|
|
835
|
-
const
|
|
1105
|
+
const { operation, isValid } = apiMap[key];
|
|
1106
|
+
if (!isValid) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
836
1109
|
const [method, path] = key.split(" ");
|
|
837
|
-
const operationId =
|
|
1110
|
+
const operationId = operation.operationId;
|
|
838
1111
|
// In Browser environment, this api is by default not support api without operationId
|
|
839
1112
|
if (!operationId) {
|
|
840
1113
|
continue;
|
|
841
1114
|
}
|
|
842
|
-
const
|
|
1115
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
843
1116
|
const apiInfo = {
|
|
844
1117
|
method: method,
|
|
845
1118
|
path: path,
|
|
@@ -848,9 +1121,6 @@ class SpecParser {
|
|
|
848
1121
|
parameters: command.parameters,
|
|
849
1122
|
description: command.description,
|
|
850
1123
|
};
|
|
851
|
-
if (warning) {
|
|
852
|
-
apiInfo.warning = warning;
|
|
853
|
-
}
|
|
854
1124
|
apiInfos.push(apiInfo);
|
|
855
1125
|
}
|
|
856
1126
|
return apiInfos;
|
|
@@ -909,13 +1179,18 @@ class SpecParser {
|
|
|
909
1179
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
910
1180
|
}
|
|
911
1181
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1182
|
+
getAPIs(spec) {
|
|
1183
|
+
const validator = this.getValidator(spec);
|
|
1184
|
+
const apiMap = validator.listAPIs();
|
|
1185
|
+
return apiMap;
|
|
1186
|
+
}
|
|
1187
|
+
getValidator(spec) {
|
|
1188
|
+
if (this.validator) {
|
|
1189
|
+
return this.validator;
|
|
915
1190
|
}
|
|
916
|
-
const
|
|
917
|
-
this.
|
|
918
|
-
return
|
|
1191
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1192
|
+
this.validator = validator;
|
|
1193
|
+
return validator;
|
|
919
1194
|
}
|
|
920
1195
|
}
|
|
921
1196
|
|
|
@@ -923,7 +1198,7 @@ class SpecParser {
|
|
|
923
1198
|
class AdaptiveCardGenerator {
|
|
924
1199
|
static generateAdaptiveCard(operationItem) {
|
|
925
1200
|
try {
|
|
926
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1201
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
927
1202
|
let cardBody = [];
|
|
928
1203
|
let schema = json.schema;
|
|
929
1204
|
let jsonPath = "$";
|