@microsoft/m365-spec-parser 0.1.1-alpha.2a327dc81.0 → 0.1.1-alpha.2c983f96a.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm2017.js +607 -333
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1139 -700
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +607 -333
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1088 -645
- 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 +63 -2
- package/dist/src/manifestUpdater.d.ts +5 -2
- 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 -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,221 +186,9 @@ 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
|
-
* Checks if the given API is supported.
|
|
274
|
-
* @param {string} method - The HTTP method of the API.
|
|
275
|
-
* @param {string} path - The path of the API.
|
|
276
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
277
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
278
|
-
* @description The following APIs are supported:
|
|
279
|
-
* 1. only support Get/Post operation without auth property
|
|
280
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
281
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
282
|
-
* 4. request body + required parameters <= 1
|
|
283
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
284
|
-
* 6. only support request body with “application/json” content type
|
|
285
|
-
*/
|
|
286
|
-
static isSupportedApi(method, path, spec, options) {
|
|
287
|
-
var _a;
|
|
288
|
-
const pathObj = spec.paths[path];
|
|
289
|
-
method = method.toLocaleLowerCase();
|
|
290
|
-
if (pathObj) {
|
|
291
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
292
|
-
const securities = pathObj[method].security;
|
|
293
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
294
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
295
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
296
|
-
if (!isTeamsAi) {
|
|
297
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
298
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const operationObject = pathObj[method];
|
|
303
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
const paramObject = operationObject.parameters;
|
|
307
|
-
const requestBody = operationObject.requestBody;
|
|
308
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
309
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
313
|
-
if (Object.keys(responseJson).length === 0) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
// Teams AI project doesn't care about request parameters/body
|
|
317
|
-
if (isTeamsAi) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
let requestBodyParamResult = {
|
|
321
|
-
requiredNum: 0,
|
|
322
|
-
optionalNum: 0,
|
|
323
|
-
isValid: true,
|
|
324
|
-
};
|
|
325
|
-
if (requestJsonBody) {
|
|
326
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
327
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
331
|
-
}
|
|
332
|
-
if (!requestBodyParamResult.isValid) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
336
|
-
if (!paramResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
// Copilot support arbitrary parameters
|
|
340
|
-
if (isCopilot) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
344
|
-
if (options.allowMultipleParameters &&
|
|
345
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
346
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
else if (requestBodyParamResult.requiredNum +
|
|
352
|
-
requestBodyParamResult.optionalNum +
|
|
353
|
-
paramResult.requiredNum +
|
|
354
|
-
paramResult.optionalNum ===
|
|
355
|
-
0) {
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
366
|
-
if (authSchemeArray.length === 0) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
370
|
-
// Currently we don't support multiple auth in one operation
|
|
371
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
for (const auths of authSchemeArray) {
|
|
375
|
-
if (auths.length === 1) {
|
|
376
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
377
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
378
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
192
|
static isBearerTokenAuth(authScheme) {
|
|
387
193
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
388
194
|
}
|
|
@@ -390,18 +196,18 @@ class Utils {
|
|
|
390
196
|
return authScheme.type === "apiKey";
|
|
391
197
|
}
|
|
392
198
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return false;
|
|
199
|
+
return !!(authScheme.type === "oauth2" &&
|
|
200
|
+
authScheme.flows &&
|
|
201
|
+
authScheme.flows.authorizationCode);
|
|
397
202
|
}
|
|
398
203
|
static getAuthArray(securities, spec) {
|
|
399
204
|
var _a;
|
|
400
205
|
const result = [];
|
|
401
206
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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];
|
|
405
211
|
const authArray = [];
|
|
406
212
|
for (const name in security) {
|
|
407
213
|
const auth = securitySchemas[name];
|
|
@@ -418,17 +224,39 @@ class Utils {
|
|
|
418
224
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
419
225
|
return result;
|
|
420
226
|
}
|
|
227
|
+
static getAuthInfo(spec) {
|
|
228
|
+
let authInfo = undefined;
|
|
229
|
+
for (const url in spec.paths) {
|
|
230
|
+
for (const method in spec.paths[url]) {
|
|
231
|
+
const operation = spec.paths[url][method];
|
|
232
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
233
|
+
if (authArray && authArray.length > 0) {
|
|
234
|
+
const currentAuth = authArray[0][0];
|
|
235
|
+
if (!authInfo) {
|
|
236
|
+
authInfo = authArray[0][0];
|
|
237
|
+
}
|
|
238
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
239
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return authInfo;
|
|
245
|
+
}
|
|
421
246
|
static updateFirstLetter(str) {
|
|
422
247
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
423
248
|
}
|
|
424
|
-
static getResponseJson(operationObject
|
|
249
|
+
static getResponseJson(operationObject) {
|
|
425
250
|
var _a, _b;
|
|
426
251
|
let json = {};
|
|
252
|
+
let multipleMediaType = false;
|
|
427
253
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
428
254
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
429
255
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
256
|
+
multipleMediaType = false;
|
|
430
257
|
json = responseObject.content["application/json"];
|
|
431
|
-
if (
|
|
258
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
259
|
+
multipleMediaType = true;
|
|
432
260
|
json = {};
|
|
433
261
|
}
|
|
434
262
|
else {
|
|
@@ -436,7 +264,7 @@ class Utils {
|
|
|
436
264
|
}
|
|
437
265
|
}
|
|
438
266
|
}
|
|
439
|
-
return json;
|
|
267
|
+
return { json, multipleMediaType };
|
|
440
268
|
}
|
|
441
269
|
static convertPathToCamelCase(path) {
|
|
442
270
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -456,10 +284,10 @@ class Utils {
|
|
|
456
284
|
return undefined;
|
|
457
285
|
}
|
|
458
286
|
}
|
|
459
|
-
static
|
|
287
|
+
static resolveEnv(str) {
|
|
460
288
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
461
|
-
let matches = placeHolderReg.exec(
|
|
462
|
-
let
|
|
289
|
+
let matches = placeHolderReg.exec(str);
|
|
290
|
+
let newStr = str;
|
|
463
291
|
while (matches != null) {
|
|
464
292
|
const envVar = matches[1];
|
|
465
293
|
const envVal = process.env[envVar];
|
|
@@ -467,17 +295,17 @@ class Utils {
|
|
|
467
295
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
468
296
|
}
|
|
469
297
|
else {
|
|
470
|
-
|
|
298
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
471
299
|
}
|
|
472
|
-
matches = placeHolderReg.exec(
|
|
300
|
+
matches = placeHolderReg.exec(str);
|
|
473
301
|
}
|
|
474
|
-
return
|
|
302
|
+
return newStr;
|
|
475
303
|
}
|
|
476
304
|
static checkServerUrl(servers) {
|
|
477
305
|
const errors = [];
|
|
478
306
|
let serverUrl;
|
|
479
307
|
try {
|
|
480
|
-
serverUrl = Utils.
|
|
308
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
481
309
|
}
|
|
482
310
|
catch (err) {
|
|
483
311
|
errors.push({
|
|
@@ -508,6 +336,7 @@ class Utils {
|
|
|
508
336
|
return errors;
|
|
509
337
|
}
|
|
510
338
|
static validateServer(spec, options) {
|
|
339
|
+
var _a;
|
|
511
340
|
const errors = [];
|
|
512
341
|
let hasTopLevelServers = false;
|
|
513
342
|
let hasPathLevelServers = false;
|
|
@@ -528,7 +357,7 @@ class Utils {
|
|
|
528
357
|
}
|
|
529
358
|
for (const method in methods) {
|
|
530
359
|
const operationObject = methods[method];
|
|
531
|
-
if (
|
|
360
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
532
361
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
533
362
|
hasOperationLevelServers = true;
|
|
534
363
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -655,13 +484,7 @@ class Utils {
|
|
|
655
484
|
}
|
|
656
485
|
}
|
|
657
486
|
const operationId = operationItem.operationId;
|
|
658
|
-
const parameters = [];
|
|
659
|
-
if (requiredParams.length !== 0) {
|
|
660
|
-
parameters.push(...requiredParams);
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
parameters.push(optionalParams[0]);
|
|
664
|
-
}
|
|
487
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
665
488
|
const command = {
|
|
666
489
|
context: ["compose"],
|
|
667
490
|
type: "query",
|
|
@@ -670,117 +493,534 @@ class Utils {
|
|
|
670
493
|
parameters: parameters,
|
|
671
494
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
672
495
|
};
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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;
|
|
680
512
|
}
|
|
681
|
-
return
|
|
513
|
+
return safeRegistrationIdEnvName;
|
|
682
514
|
}
|
|
683
|
-
static
|
|
684
|
-
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;
|
|
685
534
|
const result = {};
|
|
686
535
|
for (const path in paths) {
|
|
687
536
|
const methods = paths[path];
|
|
688
537
|
for (const method in methods) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
+
};
|
|
692
546
|
}
|
|
693
547
|
}
|
|
694
548
|
}
|
|
549
|
+
this.apiMap = result;
|
|
695
550
|
return result;
|
|
696
551
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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,
|
|
704
559
|
});
|
|
705
560
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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({
|
|
723
581
|
type: ErrorType.NoSupportedApi,
|
|
724
582
|
content: ConstantString.NoSupportedApi,
|
|
583
|
+
data,
|
|
725
584
|
});
|
|
726
585
|
}
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
validateSpecOperationId() {
|
|
589
|
+
const result = { errors: [], warnings: [] };
|
|
590
|
+
const apiMap = this.listAPIs();
|
|
727
591
|
// OperationId missing
|
|
728
592
|
const apisMissingOperationId = [];
|
|
729
593
|
for (const key in apiMap) {
|
|
730
|
-
const
|
|
731
|
-
if (!
|
|
594
|
+
const { operation } = apiMap[key];
|
|
595
|
+
if (!operation.operationId) {
|
|
732
596
|
apisMissingOperationId.push(key);
|
|
733
597
|
}
|
|
734
598
|
}
|
|
735
599
|
if (apisMissingOperationId.length > 0) {
|
|
736
|
-
warnings.push({
|
|
600
|
+
result.warnings.push({
|
|
737
601
|
type: WarningType.OperationIdMissing,
|
|
738
602
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
739
603
|
data: apisMissingOperationId,
|
|
740
604
|
});
|
|
741
605
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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;
|
|
745
614
|
}
|
|
746
|
-
|
|
747
|
-
|
|
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;
|
|
748
620
|
}
|
|
749
|
-
return
|
|
750
|
-
status,
|
|
751
|
-
warnings,
|
|
752
|
-
errors,
|
|
753
|
-
};
|
|
621
|
+
return result;
|
|
754
622
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
623
|
+
validateResponse(method, path) {
|
|
624
|
+
const result = { isValid: true, reason: [] };
|
|
625
|
+
const operationObject = this.spec.paths[path][method];
|
|
626
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
627
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
628
|
+
// only support response body only contains “application/json” content type
|
|
629
|
+
if (multipleMediaType) {
|
|
630
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
631
|
+
}
|
|
632
|
+
else if (Object.keys(json).length === 0) {
|
|
633
|
+
// response body should not be empty
|
|
634
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
761
638
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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);
|
|
765
645
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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));
|
|
769
650
|
}
|
|
770
|
-
return
|
|
651
|
+
return result;
|
|
771
652
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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: [],
|
|
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;
|
|
780
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);
|
|
781
724
|
}
|
|
782
725
|
}
|
|
783
|
-
|
|
726
|
+
else {
|
|
727
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
728
|
+
paramResult.isValid = false;
|
|
729
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return paramResult;
|
|
733
|
+
}
|
|
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;
|
|
790
|
+
}
|
|
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
|
+
}
|
|
799
|
+
}
|
|
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}`);
|
|
1023
|
+
}
|
|
784
1024
|
}
|
|
785
1025
|
}
|
|
786
1026
|
|
|
@@ -803,6 +1043,9 @@ class SpecParser {
|
|
|
803
1043
|
allowBearerTokenAuth: false,
|
|
804
1044
|
allowOauth2: false,
|
|
805
1045
|
allowMethods: ["get", "post"],
|
|
1046
|
+
allowConversationStarters: false,
|
|
1047
|
+
allowResponseSemantics: false,
|
|
1048
|
+
allowConfirmation: false,
|
|
806
1049
|
projectType: ProjectType.SME,
|
|
807
1050
|
};
|
|
808
1051
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -818,11 +1061,7 @@ class SpecParser {
|
|
|
818
1061
|
try {
|
|
819
1062
|
try {
|
|
820
1063
|
await this.loadSpec();
|
|
821
|
-
await this.parser.validate(this.spec
|
|
822
|
-
validate: {
|
|
823
|
-
schema: false,
|
|
824
|
-
},
|
|
825
|
-
});
|
|
1064
|
+
await this.parser.validate(this.spec);
|
|
826
1065
|
}
|
|
827
1066
|
catch (e) {
|
|
828
1067
|
return {
|
|
@@ -831,16 +1070,46 @@ class SpecParser {
|
|
|
831
1070
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
832
1071
|
};
|
|
833
1072
|
}
|
|
1073
|
+
const errors = [];
|
|
1074
|
+
const warnings = [];
|
|
834
1075
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
835
1076
|
return {
|
|
836
1077
|
status: ValidationStatus.Error,
|
|
837
1078
|
warnings: [],
|
|
838
1079
|
errors: [
|
|
839
|
-
{
|
|
1080
|
+
{
|
|
1081
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1082
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1083
|
+
},
|
|
840
1084
|
],
|
|
841
1085
|
};
|
|
842
1086
|
}
|
|
843
|
-
|
|
1087
|
+
// Remote reference not supported
|
|
1088
|
+
const refPaths = this.parser.$refs.paths();
|
|
1089
|
+
// refPaths [0] is the current spec file path
|
|
1090
|
+
if (refPaths.length > 1) {
|
|
1091
|
+
errors.push({
|
|
1092
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1093
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1094
|
+
data: refPaths,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
const validator = this.getValidator(this.spec);
|
|
1098
|
+
const validationResult = validator.validateSpec();
|
|
1099
|
+
warnings.push(...validationResult.warnings);
|
|
1100
|
+
errors.push(...validationResult.errors);
|
|
1101
|
+
let status = ValidationStatus.Valid;
|
|
1102
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1103
|
+
status = ValidationStatus.Warning;
|
|
1104
|
+
}
|
|
1105
|
+
else if (errors.length > 0) {
|
|
1106
|
+
status = ValidationStatus.Error;
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
status: status,
|
|
1110
|
+
warnings: warnings,
|
|
1111
|
+
errors: errors,
|
|
1112
|
+
};
|
|
844
1113
|
}
|
|
845
1114
|
catch (err) {
|
|
846
1115
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -849,17 +1118,20 @@ class SpecParser {
|
|
|
849
1118
|
async listSupportedAPIInfo() {
|
|
850
1119
|
try {
|
|
851
1120
|
await this.loadSpec();
|
|
852
|
-
const apiMap = this.
|
|
1121
|
+
const apiMap = this.getAPIs(this.spec);
|
|
853
1122
|
const apiInfos = [];
|
|
854
1123
|
for (const key in apiMap) {
|
|
855
|
-
const
|
|
1124
|
+
const { operation, isValid } = apiMap[key];
|
|
1125
|
+
if (!isValid) {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
856
1128
|
const [method, path] = key.split(" ");
|
|
857
|
-
const operationId =
|
|
1129
|
+
const operationId = operation.operationId;
|
|
858
1130
|
// In Browser environment, this api is by default not support api without operationId
|
|
859
1131
|
if (!operationId) {
|
|
860
1132
|
continue;
|
|
861
1133
|
}
|
|
862
|
-
const
|
|
1134
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
863
1135
|
const apiInfo = {
|
|
864
1136
|
method: method,
|
|
865
1137
|
path: path,
|
|
@@ -868,9 +1140,6 @@ class SpecParser {
|
|
|
868
1140
|
parameters: command.parameters,
|
|
869
1141
|
description: command.description,
|
|
870
1142
|
};
|
|
871
|
-
if (warning) {
|
|
872
|
-
apiInfo.warning = warning;
|
|
873
|
-
}
|
|
874
1143
|
apiInfos.push(apiInfo);
|
|
875
1144
|
}
|
|
876
1145
|
return apiInfos;
|
|
@@ -929,13 +1198,18 @@ class SpecParser {
|
|
|
929
1198
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
930
1199
|
}
|
|
931
1200
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1201
|
+
getAPIs(spec) {
|
|
1202
|
+
const validator = this.getValidator(spec);
|
|
1203
|
+
const apiMap = validator.listAPIs();
|
|
1204
|
+
return apiMap;
|
|
1205
|
+
}
|
|
1206
|
+
getValidator(spec) {
|
|
1207
|
+
if (this.validator) {
|
|
1208
|
+
return this.validator;
|
|
935
1209
|
}
|
|
936
|
-
const
|
|
937
|
-
this.
|
|
938
|
-
return
|
|
1210
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1211
|
+
this.validator = validator;
|
|
1212
|
+
return validator;
|
|
939
1213
|
}
|
|
940
1214
|
}
|
|
941
1215
|
|
|
@@ -943,7 +1217,7 @@ class SpecParser {
|
|
|
943
1217
|
class AdaptiveCardGenerator {
|
|
944
1218
|
static generateAdaptiveCard(operationItem) {
|
|
945
1219
|
try {
|
|
946
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1220
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
947
1221
|
let cardBody = [];
|
|
948
1222
|
let schema = json.schema;
|
|
949
1223
|
let jsonPath = "$";
|