@microsoft/m365-spec-parser 0.1.0 → 0.1.1-alpha.0de595af8.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 +691 -259
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +845 -354
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +695 -259
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +859 -361
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +5 -1
- package/dist/src/index.browser.d.ts +2 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/interfaces.d.ts +76 -18
- package/dist/src/manifestUpdater.d.ts +9 -4
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +17 -2
- package/dist/src/specParser.d.ts +17 -3
- package/dist/src/utils.d.ts +19 -33
- package/package.json +62 -18
package/dist/index.esm2017.js
CHANGED
|
@@ -15,7 +15,8 @@ var ErrorType;
|
|
|
15
15
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
16
16
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
17
17
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
18
|
-
ErrorType["
|
|
18
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
19
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
19
20
|
ErrorType["ListFailed"] = "list-failed";
|
|
20
21
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
21
22
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -23,6 +24,22 @@ var ErrorType;
|
|
|
23
24
|
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
|
|
24
25
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
25
26
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
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";
|
|
26
43
|
ErrorType["Cancelled"] = "cancelled";
|
|
27
44
|
ErrorType["Unknown"] = "unknown";
|
|
28
45
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -45,7 +62,13 @@ var ValidationStatus;
|
|
|
45
62
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
46
63
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
47
64
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
48
|
-
})(ValidationStatus || (ValidationStatus = {}));
|
|
65
|
+
})(ValidationStatus || (ValidationStatus = {}));
|
|
66
|
+
var ProjectType;
|
|
67
|
+
(function (ProjectType) {
|
|
68
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
69
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
70
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
71
|
+
})(ProjectType || (ProjectType = {}));
|
|
49
72
|
|
|
50
73
|
// Copyright (c) Microsoft Corporation.
|
|
51
74
|
class SpecParserError extends Error {
|
|
@@ -72,7 +95,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
72
95
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
73
96
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
74
97
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
75
|
-
ConstantString.
|
|
98
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
99
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
100
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
76
101
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
77
102
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
78
103
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -84,6 +109,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
|
84
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
85
110
|
ConstantString.ContainerType = "Container";
|
|
86
111
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
112
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
87
113
|
ConstantString.ResponseCodeFor20X = [
|
|
88
114
|
"200",
|
|
89
115
|
"201",
|
|
@@ -143,205 +169,35 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
143
169
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
144
170
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
145
171
|
ConstantString.CommandTitleMaxLens = 32;
|
|
146
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
172
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
173
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
147
174
|
|
|
148
175
|
// Copyright (c) Microsoft Corporation.
|
|
149
176
|
class Utils {
|
|
150
|
-
static
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
};
|
|
156
|
-
if (!paramObject) {
|
|
157
|
-
return paramResult;
|
|
158
|
-
}
|
|
159
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
160
|
-
const param = paramObject[i];
|
|
161
|
-
const schema = param.schema;
|
|
162
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
163
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
164
|
-
if (isRequiredWithoutDefault) {
|
|
165
|
-
paramResult.isValid = false;
|
|
166
|
-
}
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
if (schema.type !== "boolean" &&
|
|
170
|
-
schema.type !== "string" &&
|
|
171
|
-
schema.type !== "number" &&
|
|
172
|
-
schema.type !== "integer") {
|
|
173
|
-
if (isRequiredWithoutDefault) {
|
|
174
|
-
paramResult.isValid = false;
|
|
175
|
-
}
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (param.in === "query" || param.in === "path") {
|
|
179
|
-
if (isRequiredWithoutDefault) {
|
|
180
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return paramResult;
|
|
188
|
-
}
|
|
189
|
-
static checkPostBody(schema, isRequired = false) {
|
|
190
|
-
var _a;
|
|
191
|
-
const paramResult = {
|
|
192
|
-
requiredNum: 0,
|
|
193
|
-
optionalNum: 0,
|
|
194
|
-
isValid: true,
|
|
195
|
-
};
|
|
196
|
-
if (Object.keys(schema).length === 0) {
|
|
197
|
-
return paramResult;
|
|
198
|
-
}
|
|
199
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
200
|
-
if (schema.type === "string" ||
|
|
201
|
-
schema.type === "integer" ||
|
|
202
|
-
schema.type === "boolean" ||
|
|
203
|
-
schema.type === "number") {
|
|
204
|
-
if (isRequiredWithoutDefault) {
|
|
205
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else if (schema.type === "object") {
|
|
212
|
-
const { properties } = schema;
|
|
213
|
-
for (const property in properties) {
|
|
214
|
-
let isRequired = false;
|
|
215
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
216
|
-
isRequired = true;
|
|
217
|
-
}
|
|
218
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
219
|
-
paramResult.requiredNum += result.requiredNum;
|
|
220
|
-
paramResult.optionalNum += result.optionalNum;
|
|
221
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
if (isRequiredWithoutDefault) {
|
|
226
|
-
paramResult.isValid = false;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return paramResult;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Checks if the given API is supported.
|
|
233
|
-
* @param {string} method - The HTTP method of the API.
|
|
234
|
-
* @param {string} path - The path of the API.
|
|
235
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
236
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
237
|
-
* @description The following APIs are supported:
|
|
238
|
-
* 1. only support Get/Post operation without auth property
|
|
239
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
240
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
241
|
-
* 4. request body + required parameters <= 1
|
|
242
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
243
|
-
* 6. only support request body with “application/json” content type
|
|
244
|
-
*/
|
|
245
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
246
|
-
const pathObj = spec.paths[path];
|
|
247
|
-
method = method.toLocaleLowerCase();
|
|
248
|
-
if (pathObj) {
|
|
249
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
250
|
-
pathObj[method]) {
|
|
251
|
-
const securities = pathObj[method].security;
|
|
252
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
253
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
const operationObject = pathObj[method];
|
|
257
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
const paramObject = operationObject.parameters;
|
|
261
|
-
const requestBody = operationObject.requestBody;
|
|
262
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
263
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
264
|
-
if (mediaTypesCount > 1) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
268
|
-
if (Object.keys(responseJson).length === 0) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
let requestBodyParamResult = {
|
|
272
|
-
requiredNum: 0,
|
|
273
|
-
optionalNum: 0,
|
|
274
|
-
isValid: true,
|
|
275
|
-
};
|
|
276
|
-
if (requestJsonBody) {
|
|
277
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
278
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
279
|
-
}
|
|
280
|
-
if (!requestBodyParamResult.isValid) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
284
|
-
if (!paramResult.isValid) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
288
|
-
if (allowMultipleParameters &&
|
|
289
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
else if (requestBodyParamResult.requiredNum +
|
|
295
|
-
requestBodyParamResult.optionalNum +
|
|
296
|
-
paramResult.requiredNum +
|
|
297
|
-
paramResult.optionalNum ===
|
|
298
|
-
0) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
177
|
+
static hasNestedObjectInSchema(schema) {
|
|
178
|
+
if (schema.type === "object") {
|
|
179
|
+
for (const property in schema.properties) {
|
|
180
|
+
const nestedSchema = schema.properties[property];
|
|
181
|
+
if (nestedSchema.type === "object") {
|
|
302
182
|
return true;
|
|
303
183
|
}
|
|
304
184
|
}
|
|
305
185
|
}
|
|
306
186
|
return false;
|
|
307
187
|
}
|
|
308
|
-
static
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// Currently we don't support multiple auth in one operation
|
|
314
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
for (const auths of authSchemaArray) {
|
|
318
|
-
if (auths.length === 1) {
|
|
319
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
else if (!allowAPIKeyAuth &&
|
|
323
|
-
allowOauth2 &&
|
|
324
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
else if (allowAPIKeyAuth &&
|
|
328
|
-
allowOauth2 &&
|
|
329
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
330
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return false;
|
|
188
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
189
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
190
|
+
}
|
|
191
|
+
static isBearerTokenAuth(authScheme) {
|
|
192
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
337
193
|
}
|
|
338
|
-
static isAPIKeyAuth(
|
|
339
|
-
return
|
|
194
|
+
static isAPIKeyAuth(authScheme) {
|
|
195
|
+
return authScheme.type === "apiKey";
|
|
340
196
|
}
|
|
341
|
-
static
|
|
342
|
-
return (
|
|
343
|
-
|
|
344
|
-
|
|
197
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
198
|
+
return !!(authScheme.type === "oauth2" &&
|
|
199
|
+
authScheme.flows &&
|
|
200
|
+
authScheme.flows.authorizationCode);
|
|
345
201
|
}
|
|
346
202
|
static getAuthArray(securities, spec) {
|
|
347
203
|
var _a;
|
|
@@ -354,7 +210,7 @@ class Utils {
|
|
|
354
210
|
for (const name in security) {
|
|
355
211
|
const auth = securitySchemas[name];
|
|
356
212
|
authArray.push({
|
|
357
|
-
|
|
213
|
+
authScheme: auth,
|
|
358
214
|
name: name,
|
|
359
215
|
});
|
|
360
216
|
}
|
|
@@ -372,18 +228,22 @@ class Utils {
|
|
|
372
228
|
static getResponseJson(operationObject) {
|
|
373
229
|
var _a, _b;
|
|
374
230
|
let json = {};
|
|
231
|
+
let multipleMediaType = false;
|
|
375
232
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
376
233
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
377
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
378
|
-
if (mediaTypesCount > 1) {
|
|
379
|
-
return {};
|
|
380
|
-
}
|
|
381
234
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
235
|
+
multipleMediaType = false;
|
|
382
236
|
json = responseObject.content["application/json"];
|
|
383
|
-
|
|
237
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
238
|
+
multipleMediaType = true;
|
|
239
|
+
json = {};
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
384
244
|
}
|
|
385
245
|
}
|
|
386
|
-
return json;
|
|
246
|
+
return { json, multipleMediaType };
|
|
387
247
|
}
|
|
388
248
|
static convertPathToCamelCase(path) {
|
|
389
249
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -403,10 +263,10 @@ class Utils {
|
|
|
403
263
|
return undefined;
|
|
404
264
|
}
|
|
405
265
|
}
|
|
406
|
-
static
|
|
266
|
+
static resolveEnv(str) {
|
|
407
267
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
408
|
-
let matches = placeHolderReg.exec(
|
|
409
|
-
let
|
|
268
|
+
let matches = placeHolderReg.exec(str);
|
|
269
|
+
let newStr = str;
|
|
410
270
|
while (matches != null) {
|
|
411
271
|
const envVar = matches[1];
|
|
412
272
|
const envVal = process.env[envVar];
|
|
@@ -414,17 +274,17 @@ class Utils {
|
|
|
414
274
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
415
275
|
}
|
|
416
276
|
else {
|
|
417
|
-
|
|
277
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
418
278
|
}
|
|
419
|
-
matches = placeHolderReg.exec(
|
|
279
|
+
matches = placeHolderReg.exec(str);
|
|
420
280
|
}
|
|
421
|
-
return
|
|
281
|
+
return newStr;
|
|
422
282
|
}
|
|
423
283
|
static checkServerUrl(servers) {
|
|
424
284
|
const errors = [];
|
|
425
285
|
let serverUrl;
|
|
426
286
|
try {
|
|
427
|
-
serverUrl = Utils.
|
|
287
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
428
288
|
}
|
|
429
289
|
catch (err) {
|
|
430
290
|
errors.push({
|
|
@@ -454,7 +314,8 @@ class Utils {
|
|
|
454
314
|
}
|
|
455
315
|
return errors;
|
|
456
316
|
}
|
|
457
|
-
static validateServer(spec,
|
|
317
|
+
static validateServer(spec, options) {
|
|
318
|
+
var _a;
|
|
458
319
|
const errors = [];
|
|
459
320
|
let hasTopLevelServers = false;
|
|
460
321
|
let hasPathLevelServers = false;
|
|
@@ -475,7 +336,7 @@ class Utils {
|
|
|
475
336
|
}
|
|
476
337
|
for (const method in methods) {
|
|
477
338
|
const operationObject = methods[method];
|
|
478
|
-
if (
|
|
339
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
479
340
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
480
341
|
hasOperationLevelServers = true;
|
|
481
342
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -518,6 +379,7 @@ class Utils {
|
|
|
518
379
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
519
380
|
}
|
|
520
381
|
if (isRequired && schema.default === undefined) {
|
|
382
|
+
parameter.isRequired = true;
|
|
521
383
|
requiredParams.push(parameter);
|
|
522
384
|
}
|
|
523
385
|
else {
|
|
@@ -562,7 +424,7 @@ class Utils {
|
|
|
562
424
|
param.value = schema.default;
|
|
563
425
|
}
|
|
564
426
|
}
|
|
565
|
-
static parseApiInfo(operationItem,
|
|
427
|
+
static parseApiInfo(operationItem, options) {
|
|
566
428
|
var _a, _b;
|
|
567
429
|
const requiredParams = [];
|
|
568
430
|
const optionalParams = [];
|
|
@@ -576,11 +438,12 @@ class Utils {
|
|
|
576
438
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
577
439
|
};
|
|
578
440
|
const schema = param.schema;
|
|
579
|
-
if (allowMultipleParameters && schema) {
|
|
441
|
+
if (options.allowMultipleParameters && schema) {
|
|
580
442
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
581
443
|
}
|
|
582
444
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
583
445
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
446
|
+
parameter.isRequired = true;
|
|
584
447
|
requiredParams.push(parameter);
|
|
585
448
|
}
|
|
586
449
|
else {
|
|
@@ -594,19 +457,13 @@ class Utils {
|
|
|
594
457
|
const requestJson = requestBody.content["application/json"];
|
|
595
458
|
if (Object.keys(requestJson).length !== 0) {
|
|
596
459
|
const schema = requestJson.schema;
|
|
597
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
460
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
598
461
|
requiredParams.push(...requiredP);
|
|
599
462
|
optionalParams.push(...optionalP);
|
|
600
463
|
}
|
|
601
464
|
}
|
|
602
465
|
const operationId = operationItem.operationId;
|
|
603
|
-
const parameters = [];
|
|
604
|
-
if (requiredParams.length !== 0) {
|
|
605
|
-
parameters.push(...requiredParams);
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
parameters.push(optionalParams[0]);
|
|
609
|
-
}
|
|
466
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
610
467
|
const command = {
|
|
611
468
|
context: ["compose"],
|
|
612
469
|
type: "query",
|
|
@@ -615,32 +472,9 @@ class Utils {
|
|
|
615
472
|
parameters: parameters,
|
|
616
473
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
617
474
|
};
|
|
618
|
-
|
|
619
|
-
if (requiredParams.length === 0 && optionalParams.length > 1) {
|
|
620
|
-
warning = {
|
|
621
|
-
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
622
|
-
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
|
|
623
|
-
data: operationId,
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
return [command, warning];
|
|
627
|
-
}
|
|
628
|
-
static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
629
|
-
const paths = spec.paths;
|
|
630
|
-
const result = {};
|
|
631
|
-
for (const path in paths) {
|
|
632
|
-
const methods = paths[path];
|
|
633
|
-
for (const method in methods) {
|
|
634
|
-
// For developer preview, only support GET operation with only 1 parameter without auth
|
|
635
|
-
if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
|
|
636
|
-
const operationObject = methods[method];
|
|
637
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return result;
|
|
475
|
+
return command;
|
|
642
476
|
}
|
|
643
|
-
static validateSpec(spec, parser,
|
|
477
|
+
static validateSpec(spec, parser, apiMap, isSwaggerFile, options) {
|
|
644
478
|
const errors = [];
|
|
645
479
|
const warnings = [];
|
|
646
480
|
if (isSwaggerFile) {
|
|
@@ -649,8 +483,7 @@ class Utils {
|
|
|
649
483
|
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
650
484
|
});
|
|
651
485
|
}
|
|
652
|
-
|
|
653
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
|
|
486
|
+
const serverErrors = Utils.validateServer(spec, options);
|
|
654
487
|
errors.push(...serverErrors);
|
|
655
488
|
// Remote reference not supported
|
|
656
489
|
const refPaths = parser.$refs.paths();
|
|
@@ -663,8 +496,8 @@ class Utils {
|
|
|
663
496
|
});
|
|
664
497
|
}
|
|
665
498
|
// No supported API
|
|
666
|
-
const
|
|
667
|
-
if (
|
|
499
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
500
|
+
if (validAPIs.length === 0) {
|
|
668
501
|
errors.push({
|
|
669
502
|
type: ErrorType.NoSupportedApi,
|
|
670
503
|
content: ConstantString.NoSupportedApi,
|
|
@@ -673,8 +506,8 @@ class Utils {
|
|
|
673
506
|
// OperationId missing
|
|
674
507
|
const apisMissingOperationId = [];
|
|
675
508
|
for (const key in apiMap) {
|
|
676
|
-
const
|
|
677
|
-
if (!
|
|
509
|
+
const { operation } = apiMap[key];
|
|
510
|
+
if (!operation.operationId) {
|
|
678
511
|
apisMissingOperationId.push(key);
|
|
679
512
|
}
|
|
680
513
|
}
|
|
@@ -715,6 +548,402 @@ class Utils {
|
|
|
715
548
|
}
|
|
716
549
|
return safeRegistrationIdEnvName;
|
|
717
550
|
}
|
|
551
|
+
static getAllAPICount(spec) {
|
|
552
|
+
let count = 0;
|
|
553
|
+
const paths = spec.paths;
|
|
554
|
+
for (const path in paths) {
|
|
555
|
+
const methods = paths[path];
|
|
556
|
+
for (const method in methods) {
|
|
557
|
+
if (ConstantString.AllOperationMethods.includes(method)) {
|
|
558
|
+
count++;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return count;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Copyright (c) Microsoft Corporation.
|
|
567
|
+
class Validator {
|
|
568
|
+
validateMethodAndPath(method, path) {
|
|
569
|
+
const result = { isValid: true, reason: [] };
|
|
570
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
571
|
+
result.isValid = false;
|
|
572
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
const pathObj = this.spec.paths[path];
|
|
576
|
+
if (!pathObj || !pathObj[method]) {
|
|
577
|
+
result.isValid = false;
|
|
578
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
validateResponse(method, path) {
|
|
584
|
+
const result = { isValid: true, reason: [] };
|
|
585
|
+
const operationObject = this.spec.paths[path][method];
|
|
586
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
587
|
+
// only support response body only contains “application/json” content type
|
|
588
|
+
if (multipleMediaType) {
|
|
589
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
590
|
+
}
|
|
591
|
+
else if (Object.keys(json).length === 0) {
|
|
592
|
+
// response body should not be empty
|
|
593
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
597
|
+
validateServer(method, path) {
|
|
598
|
+
const pathObj = this.spec.paths[path];
|
|
599
|
+
const result = { isValid: true, reason: [] };
|
|
600
|
+
const operationObject = pathObj[method];
|
|
601
|
+
const rootServer = this.spec.servers && this.spec.servers[0];
|
|
602
|
+
const methodServer = this.spec.paths[path].servers && this.spec.paths[path].servers[0];
|
|
603
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
604
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
605
|
+
if (!serverUrl) {
|
|
606
|
+
// should contain server URL
|
|
607
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// server url should be absolute url with https protocol
|
|
611
|
+
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
612
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
validateAuth(method, path) {
|
|
617
|
+
const pathObj = this.spec.paths[path];
|
|
618
|
+
const operationObject = pathObj[method];
|
|
619
|
+
const securities = operationObject.security;
|
|
620
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
621
|
+
if (authSchemeArray.length === 0) {
|
|
622
|
+
return { isValid: true, reason: [] };
|
|
623
|
+
}
|
|
624
|
+
if (this.options.allowAPIKeyAuth ||
|
|
625
|
+
this.options.allowOauth2 ||
|
|
626
|
+
this.options.allowBearerTokenAuth) {
|
|
627
|
+
// Currently we don't support multiple auth in one operation
|
|
628
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
629
|
+
return {
|
|
630
|
+
isValid: false,
|
|
631
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
for (const auths of authSchemeArray) {
|
|
635
|
+
if (auths.length === 1) {
|
|
636
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
637
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
638
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
639
|
+
return { isValid: true, reason: [] };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
645
|
+
}
|
|
646
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
647
|
+
var _a;
|
|
648
|
+
const paramResult = {
|
|
649
|
+
requiredNum: 0,
|
|
650
|
+
optionalNum: 0,
|
|
651
|
+
isValid: true,
|
|
652
|
+
reason: [],
|
|
653
|
+
};
|
|
654
|
+
if (Object.keys(schema).length === 0) {
|
|
655
|
+
return paramResult;
|
|
656
|
+
}
|
|
657
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
658
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
659
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
660
|
+
paramResult.isValid = false;
|
|
661
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
662
|
+
return paramResult;
|
|
663
|
+
}
|
|
664
|
+
if (schema.type === "string" ||
|
|
665
|
+
schema.type === "integer" ||
|
|
666
|
+
schema.type === "boolean" ||
|
|
667
|
+
schema.type === "number") {
|
|
668
|
+
if (isRequiredWithoutDefault) {
|
|
669
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else if (schema.type === "object") {
|
|
676
|
+
const { properties } = schema;
|
|
677
|
+
for (const property in properties) {
|
|
678
|
+
let isRequired = false;
|
|
679
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
680
|
+
isRequired = true;
|
|
681
|
+
}
|
|
682
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
683
|
+
paramResult.requiredNum += result.requiredNum;
|
|
684
|
+
paramResult.optionalNum += result.optionalNum;
|
|
685
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
686
|
+
paramResult.reason.push(...result.reason);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
691
|
+
paramResult.isValid = false;
|
|
692
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return paramResult;
|
|
696
|
+
}
|
|
697
|
+
checkParamSchema(paramObject) {
|
|
698
|
+
const paramResult = {
|
|
699
|
+
requiredNum: 0,
|
|
700
|
+
optionalNum: 0,
|
|
701
|
+
isValid: true,
|
|
702
|
+
reason: [],
|
|
703
|
+
};
|
|
704
|
+
if (!paramObject) {
|
|
705
|
+
return paramResult;
|
|
706
|
+
}
|
|
707
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
708
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
709
|
+
const param = paramObject[i];
|
|
710
|
+
const schema = param.schema;
|
|
711
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
712
|
+
paramResult.isValid = false;
|
|
713
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
717
|
+
if (isCopilot) {
|
|
718
|
+
if (isRequiredWithoutDefault) {
|
|
719
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
723
|
+
}
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
727
|
+
if (isRequiredWithoutDefault) {
|
|
728
|
+
paramResult.isValid = false;
|
|
729
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
730
|
+
}
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (schema.type !== "boolean" &&
|
|
734
|
+
schema.type !== "string" &&
|
|
735
|
+
schema.type !== "number" &&
|
|
736
|
+
schema.type !== "integer") {
|
|
737
|
+
if (isRequiredWithoutDefault) {
|
|
738
|
+
paramResult.isValid = false;
|
|
739
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
740
|
+
}
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (param.in === "query" || param.in === "path") {
|
|
744
|
+
if (isRequiredWithoutDefault) {
|
|
745
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return paramResult;
|
|
753
|
+
}
|
|
754
|
+
hasNestedObjectInSchema(schema) {
|
|
755
|
+
if (schema.type === "object") {
|
|
756
|
+
for (const property in schema.properties) {
|
|
757
|
+
const nestedSchema = schema.properties[property];
|
|
758
|
+
if (nestedSchema.type === "object") {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Copyright (c) Microsoft Corporation.
|
|
768
|
+
class CopilotValidator extends Validator {
|
|
769
|
+
constructor(spec, options) {
|
|
770
|
+
super();
|
|
771
|
+
this.projectType = ProjectType.Copilot;
|
|
772
|
+
this.options = options;
|
|
773
|
+
this.spec = spec;
|
|
774
|
+
}
|
|
775
|
+
validateAPI(method, path) {
|
|
776
|
+
const result = { isValid: true, reason: [] };
|
|
777
|
+
method = method.toLocaleLowerCase();
|
|
778
|
+
// validate method and path
|
|
779
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
780
|
+
if (!methodAndPathResult.isValid) {
|
|
781
|
+
return methodAndPathResult;
|
|
782
|
+
}
|
|
783
|
+
const operationObject = this.spec.paths[path][method];
|
|
784
|
+
// validate auth
|
|
785
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
786
|
+
result.reason.push(...authCheckResult.reason);
|
|
787
|
+
// validate operationId
|
|
788
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
789
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
790
|
+
}
|
|
791
|
+
// validate server
|
|
792
|
+
const validateServerResult = this.validateServer(method, path);
|
|
793
|
+
result.reason.push(...validateServerResult.reason);
|
|
794
|
+
// validate response
|
|
795
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
796
|
+
result.reason.push(...validateResponseResult.reason);
|
|
797
|
+
// validate requestBody
|
|
798
|
+
const requestBody = operationObject.requestBody;
|
|
799
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
800
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
801
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
802
|
+
}
|
|
803
|
+
if (requestJsonBody) {
|
|
804
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
805
|
+
if (requestBodySchema.type !== "object") {
|
|
806
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
807
|
+
}
|
|
808
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
809
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
810
|
+
}
|
|
811
|
+
// validate parameters
|
|
812
|
+
const paramObject = operationObject.parameters;
|
|
813
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
814
|
+
result.reason.push(...paramResult.reason);
|
|
815
|
+
if (result.reason.length > 0) {
|
|
816
|
+
result.isValid = false;
|
|
817
|
+
}
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Copyright (c) Microsoft Corporation.
|
|
823
|
+
class SMEValidator extends Validator {
|
|
824
|
+
constructor(spec, options) {
|
|
825
|
+
super();
|
|
826
|
+
this.projectType = ProjectType.SME;
|
|
827
|
+
this.options = options;
|
|
828
|
+
this.spec = spec;
|
|
829
|
+
}
|
|
830
|
+
validateAPI(method, path) {
|
|
831
|
+
const result = { isValid: true, reason: [] };
|
|
832
|
+
method = method.toLocaleLowerCase();
|
|
833
|
+
// validate method and path
|
|
834
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
835
|
+
if (!methodAndPathResult.isValid) {
|
|
836
|
+
return methodAndPathResult;
|
|
837
|
+
}
|
|
838
|
+
const operationObject = this.spec.paths[path][method];
|
|
839
|
+
// validate auth
|
|
840
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
841
|
+
result.reason.push(...authCheckResult.reason);
|
|
842
|
+
// validate operationId
|
|
843
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
844
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
845
|
+
}
|
|
846
|
+
// validate server
|
|
847
|
+
const validateServerResult = this.validateServer(method, path);
|
|
848
|
+
result.reason.push(...validateServerResult.reason);
|
|
849
|
+
// validate response
|
|
850
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
851
|
+
result.reason.push(...validateResponseResult.reason);
|
|
852
|
+
let postBodyResult = {
|
|
853
|
+
requiredNum: 0,
|
|
854
|
+
optionalNum: 0,
|
|
855
|
+
isValid: true,
|
|
856
|
+
reason: [],
|
|
857
|
+
};
|
|
858
|
+
// validate requestBody
|
|
859
|
+
const requestBody = operationObject.requestBody;
|
|
860
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
861
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
862
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
863
|
+
}
|
|
864
|
+
if (requestJsonBody) {
|
|
865
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
866
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
867
|
+
result.reason.push(...postBodyResult.reason);
|
|
868
|
+
}
|
|
869
|
+
// validate parameters
|
|
870
|
+
const paramObject = operationObject.parameters;
|
|
871
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
872
|
+
result.reason.push(...paramResult.reason);
|
|
873
|
+
// validate total parameters count
|
|
874
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
875
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
876
|
+
result.reason.push(...paramCountResult.reason);
|
|
877
|
+
}
|
|
878
|
+
if (result.reason.length > 0) {
|
|
879
|
+
result.isValid = false;
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
884
|
+
const result = { isValid: true, reason: [] };
|
|
885
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
886
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
887
|
+
if (totalRequiredParams > 1) {
|
|
888
|
+
if (!this.options.allowMultipleParameters ||
|
|
889
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
890
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
else if (totalParams === 0) {
|
|
894
|
+
result.reason.push(ErrorType.NoParameter);
|
|
895
|
+
}
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
900
|
+
|
|
901
|
+
// Copyright (c) Microsoft Corporation.
|
|
902
|
+
class TeamsAIValidator extends Validator {
|
|
903
|
+
constructor(spec, options) {
|
|
904
|
+
super();
|
|
905
|
+
this.projectType = ProjectType.TeamsAi;
|
|
906
|
+
this.options = options;
|
|
907
|
+
this.spec = spec;
|
|
908
|
+
}
|
|
909
|
+
validateAPI(method, path) {
|
|
910
|
+
const result = { isValid: true, reason: [] };
|
|
911
|
+
method = method.toLocaleLowerCase();
|
|
912
|
+
// validate method and path
|
|
913
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
914
|
+
if (!methodAndPathResult.isValid) {
|
|
915
|
+
return methodAndPathResult;
|
|
916
|
+
}
|
|
917
|
+
const operationObject = this.spec.paths[path][method];
|
|
918
|
+
// validate operationId
|
|
919
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
920
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
921
|
+
}
|
|
922
|
+
// validate server
|
|
923
|
+
const validateServerResult = this.validateServer(method, path);
|
|
924
|
+
result.reason.push(...validateServerResult.reason);
|
|
925
|
+
if (result.reason.length > 0) {
|
|
926
|
+
result.isValid = false;
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
class ValidatorFactory {
|
|
933
|
+
static create(spec, options) {
|
|
934
|
+
var _a;
|
|
935
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
936
|
+
switch (type) {
|
|
937
|
+
case ProjectType.SME:
|
|
938
|
+
return new SMEValidator(spec, options);
|
|
939
|
+
case ProjectType.Copilot:
|
|
940
|
+
return new CopilotValidator(spec, options);
|
|
941
|
+
case ProjectType.TeamsAi:
|
|
942
|
+
return new TeamsAIValidator(spec, options);
|
|
943
|
+
default:
|
|
944
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
718
947
|
}
|
|
719
948
|
|
|
720
949
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -733,7 +962,10 @@ class SpecParser {
|
|
|
733
962
|
allowSwagger: false,
|
|
734
963
|
allowAPIKeyAuth: false,
|
|
735
964
|
allowMultipleParameters: false,
|
|
965
|
+
allowBearerTokenAuth: false,
|
|
736
966
|
allowOauth2: false,
|
|
967
|
+
allowMethods: ["get", "post"],
|
|
968
|
+
projectType: ProjectType.SME,
|
|
737
969
|
};
|
|
738
970
|
this.pathOrSpec = pathOrDoc;
|
|
739
971
|
this.parser = new SwaggerParser();
|
|
@@ -770,7 +1002,8 @@ class SpecParser {
|
|
|
770
1002
|
],
|
|
771
1003
|
};
|
|
772
1004
|
}
|
|
773
|
-
|
|
1005
|
+
const apiMap = this.getAPIs(this.spec);
|
|
1006
|
+
return Utils.validateSpec(this.spec, this.parser, apiMap, !!this.isSwaggerFile, this.options);
|
|
774
1007
|
}
|
|
775
1008
|
catch (err) {
|
|
776
1009
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -779,17 +1012,20 @@ class SpecParser {
|
|
|
779
1012
|
async listSupportedAPIInfo() {
|
|
780
1013
|
try {
|
|
781
1014
|
await this.loadSpec();
|
|
782
|
-
const apiMap = this.
|
|
1015
|
+
const apiMap = this.getAPIs(this.spec);
|
|
783
1016
|
const apiInfos = [];
|
|
784
1017
|
for (const key in apiMap) {
|
|
785
|
-
const
|
|
1018
|
+
const { operation, isValid } = apiMap[key];
|
|
1019
|
+
if (!isValid) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
786
1022
|
const [method, path] = key.split(" ");
|
|
787
|
-
const operationId =
|
|
1023
|
+
const operationId = operation.operationId;
|
|
788
1024
|
// In Browser environment, this api is by default not support api without operationId
|
|
789
1025
|
if (!operationId) {
|
|
790
1026
|
continue;
|
|
791
1027
|
}
|
|
792
|
-
const
|
|
1028
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
793
1029
|
const apiInfo = {
|
|
794
1030
|
method: method,
|
|
795
1031
|
path: path,
|
|
@@ -798,9 +1034,6 @@ class SpecParser {
|
|
|
798
1034
|
parameters: command.parameters,
|
|
799
1035
|
description: command.description,
|
|
800
1036
|
};
|
|
801
|
-
if (warning) {
|
|
802
|
-
apiInfo.warning = warning;
|
|
803
|
-
}
|
|
804
1037
|
apiInfos.push(apiInfo);
|
|
805
1038
|
}
|
|
806
1039
|
return apiInfos;
|
|
@@ -818,12 +1051,32 @@ class SpecParser {
|
|
|
818
1051
|
async list() {
|
|
819
1052
|
throw new Error("Method not implemented.");
|
|
820
1053
|
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Generate specs according to the filters.
|
|
1056
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1057
|
+
*/
|
|
1058
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1059
|
+
async getFilteredSpecs(filter, signal) {
|
|
1060
|
+
throw new Error("Method not implemented.");
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1064
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1065
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1066
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1067
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1068
|
+
*/
|
|
1069
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1070
|
+
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1071
|
+
throw new Error("Method not implemented.");
|
|
1072
|
+
}
|
|
821
1073
|
/**
|
|
822
1074
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
823
1075
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
824
1076
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
825
1077
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
826
1078
|
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1079
|
+
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
827
1080
|
*/
|
|
828
1081
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
829
1082
|
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
@@ -839,15 +1092,194 @@ class SpecParser {
|
|
|
839
1092
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
840
1093
|
}
|
|
841
1094
|
}
|
|
842
|
-
|
|
1095
|
+
getAPIs(spec) {
|
|
843
1096
|
if (this.apiMap !== undefined) {
|
|
844
1097
|
return this.apiMap;
|
|
845
1098
|
}
|
|
846
|
-
const result =
|
|
1099
|
+
const result = this.listAPIs(spec);
|
|
847
1100
|
this.apiMap = result;
|
|
848
1101
|
return result;
|
|
849
1102
|
}
|
|
1103
|
+
listAPIs(spec) {
|
|
1104
|
+
var _a;
|
|
1105
|
+
const paths = spec.paths;
|
|
1106
|
+
const result = {};
|
|
1107
|
+
for (const path in paths) {
|
|
1108
|
+
const methods = paths[path];
|
|
1109
|
+
for (const method in methods) {
|
|
1110
|
+
const operationObject = methods[method];
|
|
1111
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1112
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1113
|
+
const validateResult = validator.validateAPI(method, path);
|
|
1114
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
1115
|
+
operation: operationObject,
|
|
1116
|
+
isValid: validateResult.isValid,
|
|
1117
|
+
reason: validateResult.reason,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return result;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// Copyright (c) Microsoft Corporation.
|
|
1127
|
+
class AdaptiveCardGenerator {
|
|
1128
|
+
static generateAdaptiveCard(operationItem) {
|
|
1129
|
+
try {
|
|
1130
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1131
|
+
let cardBody = [];
|
|
1132
|
+
let schema = json.schema;
|
|
1133
|
+
let jsonPath = "$";
|
|
1134
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
1135
|
+
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1136
|
+
if (jsonPath !== "$") {
|
|
1137
|
+
schema = schema.properties[jsonPath];
|
|
1138
|
+
}
|
|
1139
|
+
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1140
|
+
}
|
|
1141
|
+
// if no schema, try to use example value
|
|
1142
|
+
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1143
|
+
cardBody = [
|
|
1144
|
+
{
|
|
1145
|
+
type: ConstantString.TextBlockType,
|
|
1146
|
+
text: "${jsonStringify($root)}",
|
|
1147
|
+
wrap: true,
|
|
1148
|
+
},
|
|
1149
|
+
];
|
|
1150
|
+
}
|
|
1151
|
+
// if no example value, use default success response
|
|
1152
|
+
if (cardBody.length === 0) {
|
|
1153
|
+
cardBody = [
|
|
1154
|
+
{
|
|
1155
|
+
type: ConstantString.TextBlockType,
|
|
1156
|
+
text: "success",
|
|
1157
|
+
wrap: true,
|
|
1158
|
+
},
|
|
1159
|
+
];
|
|
1160
|
+
}
|
|
1161
|
+
const fullCard = {
|
|
1162
|
+
type: ConstantString.AdaptiveCardType,
|
|
1163
|
+
$schema: ConstantString.AdaptiveCardSchema,
|
|
1164
|
+
version: ConstantString.AdaptiveCardVersion,
|
|
1165
|
+
body: cardBody,
|
|
1166
|
+
};
|
|
1167
|
+
return [fullCard, jsonPath];
|
|
1168
|
+
}
|
|
1169
|
+
catch (err) {
|
|
1170
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1174
|
+
if (schema.type === "array") {
|
|
1175
|
+
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1176
|
+
if (Object.keys(schema.items).length === 0) {
|
|
1177
|
+
return [
|
|
1178
|
+
{
|
|
1179
|
+
type: ConstantString.TextBlockType,
|
|
1180
|
+
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1181
|
+
wrap: true,
|
|
1182
|
+
},
|
|
1183
|
+
];
|
|
1184
|
+
}
|
|
1185
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1186
|
+
const template = {
|
|
1187
|
+
type: ConstantString.ContainerType,
|
|
1188
|
+
$data: name ? `\${${name}}` : "${$root}",
|
|
1189
|
+
items: Array(),
|
|
1190
|
+
};
|
|
1191
|
+
template.items.push(...obj);
|
|
1192
|
+
return [template];
|
|
1193
|
+
}
|
|
1194
|
+
// some schema may not contain type but contain properties
|
|
1195
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1196
|
+
const { properties } = schema;
|
|
1197
|
+
const result = [];
|
|
1198
|
+
for (const property in properties) {
|
|
1199
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1200
|
+
result.push(...obj);
|
|
1201
|
+
}
|
|
1202
|
+
if (schema.additionalProperties) {
|
|
1203
|
+
// TODO: better ways to handler warnings.
|
|
1204
|
+
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1205
|
+
}
|
|
1206
|
+
return result;
|
|
1207
|
+
}
|
|
1208
|
+
if (schema.type === "string" ||
|
|
1209
|
+
schema.type === "integer" ||
|
|
1210
|
+
schema.type === "boolean" ||
|
|
1211
|
+
schema.type === "number") {
|
|
1212
|
+
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1213
|
+
// string in root: "ddd"
|
|
1214
|
+
let text = "result: ${$root}";
|
|
1215
|
+
if (name) {
|
|
1216
|
+
// object { id: "1" }
|
|
1217
|
+
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1218
|
+
if (parentArrayName) {
|
|
1219
|
+
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1220
|
+
text = `${parentArrayName}.${text}`;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
else if (parentArrayName) {
|
|
1224
|
+
// string array: photoUrls: ["1", "2"]
|
|
1225
|
+
text = `${parentArrayName}: ` + "${$data}";
|
|
1226
|
+
}
|
|
1227
|
+
return [
|
|
1228
|
+
{
|
|
1229
|
+
type: ConstantString.TextBlockType,
|
|
1230
|
+
text,
|
|
1231
|
+
wrap: true,
|
|
1232
|
+
},
|
|
1233
|
+
];
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
if (name) {
|
|
1237
|
+
return [
|
|
1238
|
+
{
|
|
1239
|
+
type: "Image",
|
|
1240
|
+
url: `\${${name}}`,
|
|
1241
|
+
$when: `\${${name} != null}`,
|
|
1242
|
+
},
|
|
1243
|
+
];
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
return [
|
|
1247
|
+
{
|
|
1248
|
+
type: "Image",
|
|
1249
|
+
url: "${$data}",
|
|
1250
|
+
$when: "${$data != null}",
|
|
1251
|
+
},
|
|
1252
|
+
];
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1257
|
+
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1258
|
+
}
|
|
1259
|
+
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1260
|
+
}
|
|
1261
|
+
// Find the first array property in the response schema object with the well-known name
|
|
1262
|
+
static getResponseJsonPathFromSchema(schema) {
|
|
1263
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1264
|
+
const { properties } = schema;
|
|
1265
|
+
for (const property in properties) {
|
|
1266
|
+
const schema = properties[property];
|
|
1267
|
+
if (schema.type === "array" &&
|
|
1268
|
+
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1269
|
+
return property;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return "$";
|
|
1274
|
+
}
|
|
1275
|
+
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1276
|
+
const propertyName = name ? name : parentArrayName;
|
|
1277
|
+
return (!!propertyName &&
|
|
1278
|
+
schema.type === "string" &&
|
|
1279
|
+
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1280
|
+
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1281
|
+
}
|
|
850
1282
|
}
|
|
851
1283
|
|
|
852
|
-
export { ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1284
|
+
export { AdaptiveCardGenerator, ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
853
1285
|
//# sourceMappingURL=index.esm2017.js.map
|