@microsoft/m365-spec-parser 0.1.1-alpha.a372ccf67.0 → 0.1.1-alpha.ad8f60cf1.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 +620 -337
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1174 -715
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +620 -337
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1109 -646
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +4 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +79 -16
- 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 +15 -32
- 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 = {}));
|
|
@@ -71,7 +87,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
71
87
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
72
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.";
|
|
73
89
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
74
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
90
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
75
91
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
76
92
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
77
93
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -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,9 +107,9 @@ 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
|
-
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
96
113
|
ConstantString.ResponseCodeFor20X = [
|
|
97
114
|
"200",
|
|
98
115
|
"201",
|
|
@@ -153,7 +170,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
153
170
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
154
171
|
ConstantString.CommandTitleMaxLens = 32;
|
|
155
172
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
156
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
173
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
174
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
157
175
|
|
|
158
176
|
// Copyright (c) Microsoft Corporation.
|
|
159
177
|
class Utils {
|
|
@@ -168,253 +186,33 @@ class Utils {
|
|
|
168
186
|
}
|
|
169
187
|
return false;
|
|
170
188
|
}
|
|
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
189
|
static containMultipleMediaTypes(bodyObject) {
|
|
270
190
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
271
191
|
}
|
|
272
|
-
|
|
273
|
-
|
|
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(authSchemaArray, options) {
|
|
366
|
-
if (authSchemaArray.length === 0) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
370
|
-
// Currently we don't support multiple auth in one operation
|
|
371
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
for (const auths of authSchemaArray) {
|
|
375
|
-
if (auths.length === 1) {
|
|
376
|
-
if (!options.allowOauth2 &&
|
|
377
|
-
options.allowAPIKeyAuth &&
|
|
378
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
else if (!options.allowAPIKeyAuth &&
|
|
382
|
-
options.allowOauth2 &&
|
|
383
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema)) {
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
else if (options.allowAPIKeyAuth &&
|
|
387
|
-
options.allowOauth2 &&
|
|
388
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
389
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema))) {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return false;
|
|
192
|
+
static isBearerTokenAuth(authScheme) {
|
|
193
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
396
194
|
}
|
|
397
|
-
static isAPIKeyAuth(
|
|
398
|
-
return
|
|
195
|
+
static isAPIKeyAuth(authScheme) {
|
|
196
|
+
return authScheme.type === "apiKey";
|
|
399
197
|
}
|
|
400
|
-
static isOAuthWithAuthCodeFlow(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return false;
|
|
198
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
199
|
+
return !!(authScheme.type === "oauth2" &&
|
|
200
|
+
authScheme.flows &&
|
|
201
|
+
authScheme.flows.authorizationCode);
|
|
405
202
|
}
|
|
406
203
|
static getAuthArray(securities, spec) {
|
|
407
204
|
var _a;
|
|
408
205
|
const result = [];
|
|
409
206
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
207
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
208
|
+
if (securitiesArr && securitySchemas) {
|
|
209
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
210
|
+
const security = securitiesArr[i];
|
|
413
211
|
const authArray = [];
|
|
414
212
|
for (const name in security) {
|
|
415
213
|
const auth = securitySchemas[name];
|
|
416
214
|
authArray.push({
|
|
417
|
-
|
|
215
|
+
authScheme: auth,
|
|
418
216
|
name: name,
|
|
419
217
|
});
|
|
420
218
|
}
|
|
@@ -426,17 +224,39 @@ class Utils {
|
|
|
426
224
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
427
225
|
return result;
|
|
428
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
|
+
}
|
|
429
246
|
static updateFirstLetter(str) {
|
|
430
247
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
431
248
|
}
|
|
432
|
-
static getResponseJson(operationObject
|
|
249
|
+
static getResponseJson(operationObject) {
|
|
433
250
|
var _a, _b;
|
|
434
251
|
let json = {};
|
|
252
|
+
let multipleMediaType = false;
|
|
435
253
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
436
254
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
437
255
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
256
|
+
multipleMediaType = false;
|
|
438
257
|
json = responseObject.content["application/json"];
|
|
439
|
-
if (
|
|
258
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
259
|
+
multipleMediaType = true;
|
|
440
260
|
json = {};
|
|
441
261
|
}
|
|
442
262
|
else {
|
|
@@ -444,7 +264,7 @@ class Utils {
|
|
|
444
264
|
}
|
|
445
265
|
}
|
|
446
266
|
}
|
|
447
|
-
return json;
|
|
267
|
+
return { json, multipleMediaType };
|
|
448
268
|
}
|
|
449
269
|
static convertPathToCamelCase(path) {
|
|
450
270
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -464,10 +284,10 @@ class Utils {
|
|
|
464
284
|
return undefined;
|
|
465
285
|
}
|
|
466
286
|
}
|
|
467
|
-
static
|
|
287
|
+
static resolveEnv(str) {
|
|
468
288
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
469
|
-
let matches = placeHolderReg.exec(
|
|
470
|
-
let
|
|
289
|
+
let matches = placeHolderReg.exec(str);
|
|
290
|
+
let newStr = str;
|
|
471
291
|
while (matches != null) {
|
|
472
292
|
const envVar = matches[1];
|
|
473
293
|
const envVal = process.env[envVar];
|
|
@@ -475,17 +295,17 @@ class Utils {
|
|
|
475
295
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
476
296
|
}
|
|
477
297
|
else {
|
|
478
|
-
|
|
298
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
479
299
|
}
|
|
480
|
-
matches = placeHolderReg.exec(
|
|
300
|
+
matches = placeHolderReg.exec(str);
|
|
481
301
|
}
|
|
482
|
-
return
|
|
302
|
+
return newStr;
|
|
483
303
|
}
|
|
484
304
|
static checkServerUrl(servers) {
|
|
485
305
|
const errors = [];
|
|
486
306
|
let serverUrl;
|
|
487
307
|
try {
|
|
488
|
-
serverUrl = Utils.
|
|
308
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
489
309
|
}
|
|
490
310
|
catch (err) {
|
|
491
311
|
errors.push({
|
|
@@ -516,6 +336,7 @@ class Utils {
|
|
|
516
336
|
return errors;
|
|
517
337
|
}
|
|
518
338
|
static validateServer(spec, options) {
|
|
339
|
+
var _a;
|
|
519
340
|
const errors = [];
|
|
520
341
|
let hasTopLevelServers = false;
|
|
521
342
|
let hasPathLevelServers = false;
|
|
@@ -536,7 +357,7 @@ class Utils {
|
|
|
536
357
|
}
|
|
537
358
|
for (const method in methods) {
|
|
538
359
|
const operationObject = methods[method];
|
|
539
|
-
if (
|
|
360
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
540
361
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
541
362
|
hasOperationLevelServers = true;
|
|
542
363
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -579,6 +400,7 @@ class Utils {
|
|
|
579
400
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
580
401
|
}
|
|
581
402
|
if (isRequired && schema.default === undefined) {
|
|
403
|
+
parameter.isRequired = true;
|
|
582
404
|
requiredParams.push(parameter);
|
|
583
405
|
}
|
|
584
406
|
else {
|
|
@@ -642,6 +464,7 @@ class Utils {
|
|
|
642
464
|
}
|
|
643
465
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
644
466
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
467
|
+
parameter.isRequired = true;
|
|
645
468
|
requiredParams.push(parameter);
|
|
646
469
|
}
|
|
647
470
|
else {
|
|
@@ -661,13 +484,7 @@ class Utils {
|
|
|
661
484
|
}
|
|
662
485
|
}
|
|
663
486
|
const operationId = operationItem.operationId;
|
|
664
|
-
const parameters = [];
|
|
665
|
-
if (requiredParams.length !== 0) {
|
|
666
|
-
parameters.push(...requiredParams);
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
parameters.push(optionalParams[0]);
|
|
670
|
-
}
|
|
487
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
671
488
|
const command = {
|
|
672
489
|
context: ["compose"],
|
|
673
490
|
type: "query",
|
|
@@ -676,104 +493,534 @@ class Utils {
|
|
|
676
493
|
parameters: parameters,
|
|
677
494
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
678
495
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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;
|
|
686
512
|
}
|
|
687
|
-
return
|
|
513
|
+
return safeRegistrationIdEnvName;
|
|
688
514
|
}
|
|
689
|
-
static
|
|
690
|
-
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;
|
|
691
534
|
const result = {};
|
|
692
535
|
for (const path in paths) {
|
|
693
536
|
const methods = paths[path];
|
|
694
537
|
for (const method in methods) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
};
|
|
698
546
|
}
|
|
699
547
|
}
|
|
700
548
|
}
|
|
549
|
+
this.apiMap = result;
|
|
701
550
|
return result;
|
|
702
551
|
}
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
// Server validation
|
|
713
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
714
|
-
errors.push(...serverErrors);
|
|
715
|
-
// Remote reference not supported
|
|
716
|
-
const refPaths = parser.$refs.paths();
|
|
717
|
-
// refPaths [0] is the current spec file path
|
|
718
|
-
if (refPaths.length > 1) {
|
|
719
|
-
errors.push({
|
|
720
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
721
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
722
|
-
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,
|
|
723
559
|
});
|
|
724
560
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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({
|
|
729
581
|
type: ErrorType.NoSupportedApi,
|
|
730
582
|
content: ConstantString.NoSupportedApi,
|
|
583
|
+
data,
|
|
731
584
|
});
|
|
732
585
|
}
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
validateSpecOperationId() {
|
|
589
|
+
const result = { errors: [], warnings: [] };
|
|
590
|
+
const apiMap = this.listAPIs();
|
|
733
591
|
// OperationId missing
|
|
734
592
|
const apisMissingOperationId = [];
|
|
735
593
|
for (const key in apiMap) {
|
|
736
|
-
const
|
|
737
|
-
if (!
|
|
594
|
+
const { operation } = apiMap[key];
|
|
595
|
+
if (!operation.operationId) {
|
|
738
596
|
apisMissingOperationId.push(key);
|
|
739
597
|
}
|
|
740
598
|
}
|
|
741
599
|
if (apisMissingOperationId.length > 0) {
|
|
742
|
-
warnings.push({
|
|
600
|
+
result.warnings.push({
|
|
743
601
|
type: WarningType.OperationIdMissing,
|
|
744
602
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
745
603
|
data: apisMissingOperationId,
|
|
746
604
|
});
|
|
747
605
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
}
|
|
751
636
|
}
|
|
752
|
-
|
|
753
|
-
|
|
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);
|
|
754
645
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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: [] };
|
|
660
|
+
}
|
|
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
|
+
}
|
|
680
|
+
}
|
|
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: [],
|
|
759
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;
|
|
760
733
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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;
|
|
767
790
|
}
|
|
768
|
-
|
|
769
|
-
if (
|
|
770
|
-
|
|
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
|
+
}
|
|
771
799
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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}`);
|
|
775
1023
|
}
|
|
776
|
-
return safeRegistrationIdEnvName;
|
|
777
1024
|
}
|
|
778
1025
|
}
|
|
779
1026
|
|
|
@@ -793,9 +1040,14 @@ class SpecParser {
|
|
|
793
1040
|
allowSwagger: false,
|
|
794
1041
|
allowAPIKeyAuth: false,
|
|
795
1042
|
allowMultipleParameters: false,
|
|
1043
|
+
allowBearerTokenAuth: false,
|
|
796
1044
|
allowOauth2: false,
|
|
797
1045
|
allowMethods: ["get", "post"],
|
|
1046
|
+
allowConversationStarters: false,
|
|
1047
|
+
allowResponseSemantics: false,
|
|
1048
|
+
allowConfirmation: false,
|
|
798
1049
|
projectType: ProjectType.SME,
|
|
1050
|
+
isGptPlugin: false,
|
|
799
1051
|
};
|
|
800
1052
|
this.pathOrSpec = pathOrDoc;
|
|
801
1053
|
this.parser = new SwaggerParser();
|
|
@@ -810,11 +1062,7 @@ class SpecParser {
|
|
|
810
1062
|
try {
|
|
811
1063
|
try {
|
|
812
1064
|
await this.loadSpec();
|
|
813
|
-
await this.parser.validate(this.spec
|
|
814
|
-
validate: {
|
|
815
|
-
schema: false,
|
|
816
|
-
},
|
|
817
|
-
});
|
|
1065
|
+
await this.parser.validate(this.spec);
|
|
818
1066
|
}
|
|
819
1067
|
catch (e) {
|
|
820
1068
|
return {
|
|
@@ -823,16 +1071,46 @@ class SpecParser {
|
|
|
823
1071
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
824
1072
|
};
|
|
825
1073
|
}
|
|
1074
|
+
const errors = [];
|
|
1075
|
+
const warnings = [];
|
|
826
1076
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
827
1077
|
return {
|
|
828
1078
|
status: ValidationStatus.Error,
|
|
829
1079
|
warnings: [],
|
|
830
1080
|
errors: [
|
|
831
|
-
{
|
|
1081
|
+
{
|
|
1082
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1083
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1084
|
+
},
|
|
832
1085
|
],
|
|
833
1086
|
};
|
|
834
1087
|
}
|
|
835
|
-
|
|
1088
|
+
// Remote reference not supported
|
|
1089
|
+
const refPaths = this.parser.$refs.paths();
|
|
1090
|
+
// refPaths [0] is the current spec file path
|
|
1091
|
+
if (refPaths.length > 1) {
|
|
1092
|
+
errors.push({
|
|
1093
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1094
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1095
|
+
data: refPaths,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
const validator = this.getValidator(this.spec);
|
|
1099
|
+
const validationResult = validator.validateSpec();
|
|
1100
|
+
warnings.push(...validationResult.warnings);
|
|
1101
|
+
errors.push(...validationResult.errors);
|
|
1102
|
+
let status = ValidationStatus.Valid;
|
|
1103
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1104
|
+
status = ValidationStatus.Warning;
|
|
1105
|
+
}
|
|
1106
|
+
else if (errors.length > 0) {
|
|
1107
|
+
status = ValidationStatus.Error;
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
status: status,
|
|
1111
|
+
warnings: warnings,
|
|
1112
|
+
errors: errors,
|
|
1113
|
+
};
|
|
836
1114
|
}
|
|
837
1115
|
catch (err) {
|
|
838
1116
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -841,17 +1119,20 @@ class SpecParser {
|
|
|
841
1119
|
async listSupportedAPIInfo() {
|
|
842
1120
|
try {
|
|
843
1121
|
await this.loadSpec();
|
|
844
|
-
const apiMap = this.
|
|
1122
|
+
const apiMap = this.getAPIs(this.spec);
|
|
845
1123
|
const apiInfos = [];
|
|
846
1124
|
for (const key in apiMap) {
|
|
847
|
-
const
|
|
1125
|
+
const { operation, isValid } = apiMap[key];
|
|
1126
|
+
if (!isValid) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
848
1129
|
const [method, path] = key.split(" ");
|
|
849
|
-
const operationId =
|
|
1130
|
+
const operationId = operation.operationId;
|
|
850
1131
|
// In Browser environment, this api is by default not support api without operationId
|
|
851
1132
|
if (!operationId) {
|
|
852
1133
|
continue;
|
|
853
1134
|
}
|
|
854
|
-
const
|
|
1135
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
855
1136
|
const apiInfo = {
|
|
856
1137
|
method: method,
|
|
857
1138
|
path: path,
|
|
@@ -860,9 +1141,6 @@ class SpecParser {
|
|
|
860
1141
|
parameters: command.parameters,
|
|
861
1142
|
description: command.description,
|
|
862
1143
|
};
|
|
863
|
-
if (warning) {
|
|
864
|
-
apiInfo.warning = warning;
|
|
865
|
-
}
|
|
866
1144
|
apiInfos.push(apiInfo);
|
|
867
1145
|
}
|
|
868
1146
|
return apiInfos;
|
|
@@ -921,13 +1199,18 @@ class SpecParser {
|
|
|
921
1199
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
922
1200
|
}
|
|
923
1201
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1202
|
+
getAPIs(spec) {
|
|
1203
|
+
const validator = this.getValidator(spec);
|
|
1204
|
+
const apiMap = validator.listAPIs();
|
|
1205
|
+
return apiMap;
|
|
1206
|
+
}
|
|
1207
|
+
getValidator(spec) {
|
|
1208
|
+
if (this.validator) {
|
|
1209
|
+
return this.validator;
|
|
927
1210
|
}
|
|
928
|
-
const
|
|
929
|
-
this.
|
|
930
|
-
return
|
|
1211
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1212
|
+
this.validator = validator;
|
|
1213
|
+
return validator;
|
|
931
1214
|
}
|
|
932
1215
|
}
|
|
933
1216
|
|
|
@@ -935,7 +1218,7 @@ class SpecParser {
|
|
|
935
1218
|
class AdaptiveCardGenerator {
|
|
936
1219
|
static generateAdaptiveCard(operationItem) {
|
|
937
1220
|
try {
|
|
938
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1221
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
939
1222
|
let cardBody = [];
|
|
940
1223
|
let schema = json.schema;
|
|
941
1224
|
let jsonPath = "$";
|