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