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