@microsoft/m365-spec-parser 0.0.2-alpha.2 → 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.esm5.js
CHANGED
|
@@ -45,7 +45,8 @@ var ErrorType;
|
|
|
45
45
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
46
46
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
47
47
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
48
|
-
ErrorType["
|
|
48
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
49
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
49
50
|
ErrorType["ListFailed"] = "list-failed";
|
|
50
51
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
51
52
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -53,6 +54,22 @@ var ErrorType;
|
|
|
53
54
|
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
|
|
54
55
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
55
56
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
57
|
+
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
58
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
59
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
60
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
61
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
62
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
63
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
64
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
65
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
66
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
67
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
68
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
69
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
70
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
71
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
72
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
56
73
|
ErrorType["Cancelled"] = "cancelled";
|
|
57
74
|
ErrorType["Unknown"] = "unknown";
|
|
58
75
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -75,7 +92,13 @@ var ValidationStatus;
|
|
|
75
92
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
76
93
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
77
94
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
78
|
-
})(ValidationStatus || (ValidationStatus = {}));
|
|
95
|
+
})(ValidationStatus || (ValidationStatus = {}));
|
|
96
|
+
var ProjectType;
|
|
97
|
+
(function (ProjectType) {
|
|
98
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
99
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
100
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
101
|
+
})(ProjectType || (ProjectType = {}));
|
|
79
102
|
|
|
80
103
|
// Copyright (c) Microsoft Corporation.
|
|
81
104
|
class SpecParserError extends Error {
|
|
@@ -102,7 +125,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
102
125
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
103
126
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
104
127
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
105
|
-
ConstantString.
|
|
128
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
129
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
130
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
106
131
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
107
132
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
108
133
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -114,6 +139,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
|
114
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
115
140
|
ConstantString.ContainerType = "Container";
|
|
116
141
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
142
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
117
143
|
ConstantString.ResponseCodeFor20X = [
|
|
118
144
|
"200",
|
|
119
145
|
"201",
|
|
@@ -173,205 +199,35 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
173
199
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
174
200
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
175
201
|
ConstantString.CommandTitleMaxLens = 32;
|
|
176
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
202
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
203
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
177
204
|
|
|
178
205
|
// Copyright (c) Microsoft Corporation.
|
|
179
206
|
class Utils {
|
|
180
|
-
static
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
};
|
|
186
|
-
if (!paramObject) {
|
|
187
|
-
return paramResult;
|
|
188
|
-
}
|
|
189
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
190
|
-
const param = paramObject[i];
|
|
191
|
-
const schema = param.schema;
|
|
192
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
193
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
194
|
-
if (isRequiredWithoutDefault) {
|
|
195
|
-
paramResult.isValid = false;
|
|
196
|
-
}
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (schema.type !== "boolean" &&
|
|
200
|
-
schema.type !== "string" &&
|
|
201
|
-
schema.type !== "number" &&
|
|
202
|
-
schema.type !== "integer") {
|
|
203
|
-
if (isRequiredWithoutDefault) {
|
|
204
|
-
paramResult.isValid = false;
|
|
205
|
-
}
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if (param.in === "query" || param.in === "path") {
|
|
209
|
-
if (isRequiredWithoutDefault) {
|
|
210
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return paramResult;
|
|
218
|
-
}
|
|
219
|
-
static checkPostBody(schema, isRequired = false) {
|
|
220
|
-
var _a;
|
|
221
|
-
const paramResult = {
|
|
222
|
-
requiredNum: 0,
|
|
223
|
-
optionalNum: 0,
|
|
224
|
-
isValid: true,
|
|
225
|
-
};
|
|
226
|
-
if (Object.keys(schema).length === 0) {
|
|
227
|
-
return paramResult;
|
|
228
|
-
}
|
|
229
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
230
|
-
if (schema.type === "string" ||
|
|
231
|
-
schema.type === "integer" ||
|
|
232
|
-
schema.type === "boolean" ||
|
|
233
|
-
schema.type === "number") {
|
|
234
|
-
if (isRequiredWithoutDefault) {
|
|
235
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else if (schema.type === "object") {
|
|
242
|
-
const { properties } = schema;
|
|
243
|
-
for (const property in properties) {
|
|
244
|
-
let isRequired = false;
|
|
245
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
246
|
-
isRequired = true;
|
|
247
|
-
}
|
|
248
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
249
|
-
paramResult.requiredNum += result.requiredNum;
|
|
250
|
-
paramResult.optionalNum += result.optionalNum;
|
|
251
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
if (isRequiredWithoutDefault) {
|
|
256
|
-
paramResult.isValid = false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return paramResult;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Checks if the given API is supported.
|
|
263
|
-
* @param {string} method - The HTTP method of the API.
|
|
264
|
-
* @param {string} path - The path of the API.
|
|
265
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
266
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
267
|
-
* @description The following APIs are supported:
|
|
268
|
-
* 1. only support Get/Post operation without auth property
|
|
269
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
270
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
271
|
-
* 4. request body + required parameters <= 1
|
|
272
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
273
|
-
* 6. only support request body with “application/json” content type
|
|
274
|
-
*/
|
|
275
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
276
|
-
const pathObj = spec.paths[path];
|
|
277
|
-
method = method.toLocaleLowerCase();
|
|
278
|
-
if (pathObj) {
|
|
279
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
280
|
-
pathObj[method]) {
|
|
281
|
-
const securities = pathObj[method].security;
|
|
282
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
283
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
const operationObject = pathObj[method];
|
|
287
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
const paramObject = operationObject.parameters;
|
|
291
|
-
const requestBody = operationObject.requestBody;
|
|
292
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
293
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
294
|
-
if (mediaTypesCount > 1) {
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
298
|
-
if (Object.keys(responseJson).length === 0) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
let requestBodyParamResult = {
|
|
302
|
-
requiredNum: 0,
|
|
303
|
-
optionalNum: 0,
|
|
304
|
-
isValid: true,
|
|
305
|
-
};
|
|
306
|
-
if (requestJsonBody) {
|
|
307
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
308
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
309
|
-
}
|
|
310
|
-
if (!requestBodyParamResult.isValid) {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
314
|
-
if (!paramResult.isValid) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
318
|
-
if (allowMultipleParameters &&
|
|
319
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
320
|
-
return true;
|
|
321
|
-
}
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
else if (requestBodyParamResult.requiredNum +
|
|
325
|
-
requestBodyParamResult.optionalNum +
|
|
326
|
-
paramResult.requiredNum +
|
|
327
|
-
paramResult.optionalNum ===
|
|
328
|
-
0) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
207
|
+
static hasNestedObjectInSchema(schema) {
|
|
208
|
+
if (schema.type === "object") {
|
|
209
|
+
for (const property in schema.properties) {
|
|
210
|
+
const nestedSchema = schema.properties[property];
|
|
211
|
+
if (nestedSchema.type === "object") {
|
|
332
212
|
return true;
|
|
333
213
|
}
|
|
334
214
|
}
|
|
335
215
|
}
|
|
336
216
|
return false;
|
|
337
217
|
}
|
|
338
|
-
static
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Currently we don't support multiple auth in one operation
|
|
344
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
for (const auths of authSchemaArray) {
|
|
348
|
-
if (auths.length === 1) {
|
|
349
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
else if (!allowAPIKeyAuth &&
|
|
353
|
-
allowOauth2 &&
|
|
354
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
else if (allowAPIKeyAuth &&
|
|
358
|
-
allowOauth2 &&
|
|
359
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
360
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
361
|
-
return true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return false;
|
|
218
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
219
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
220
|
+
}
|
|
221
|
+
static isBearerTokenAuth(authScheme) {
|
|
222
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
367
223
|
}
|
|
368
|
-
static isAPIKeyAuth(
|
|
369
|
-
return
|
|
224
|
+
static isAPIKeyAuth(authScheme) {
|
|
225
|
+
return authScheme.type === "apiKey";
|
|
370
226
|
}
|
|
371
|
-
static
|
|
372
|
-
return (
|
|
373
|
-
|
|
374
|
-
|
|
227
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
228
|
+
return !!(authScheme.type === "oauth2" &&
|
|
229
|
+
authScheme.flows &&
|
|
230
|
+
authScheme.flows.authorizationCode);
|
|
375
231
|
}
|
|
376
232
|
static getAuthArray(securities, spec) {
|
|
377
233
|
var _a;
|
|
@@ -384,7 +240,7 @@ class Utils {
|
|
|
384
240
|
for (const name in security) {
|
|
385
241
|
const auth = securitySchemas[name];
|
|
386
242
|
authArray.push({
|
|
387
|
-
|
|
243
|
+
authScheme: auth,
|
|
388
244
|
name: name,
|
|
389
245
|
});
|
|
390
246
|
}
|
|
@@ -402,18 +258,22 @@ class Utils {
|
|
|
402
258
|
static getResponseJson(operationObject) {
|
|
403
259
|
var _a, _b;
|
|
404
260
|
let json = {};
|
|
261
|
+
let multipleMediaType = false;
|
|
405
262
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
406
263
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
407
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
408
|
-
if (mediaTypesCount > 1) {
|
|
409
|
-
return {};
|
|
410
|
-
}
|
|
411
264
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
265
|
+
multipleMediaType = false;
|
|
412
266
|
json = responseObject.content["application/json"];
|
|
413
|
-
|
|
267
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
268
|
+
multipleMediaType = true;
|
|
269
|
+
json = {};
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
414
274
|
}
|
|
415
275
|
}
|
|
416
|
-
return json;
|
|
276
|
+
return { json, multipleMediaType };
|
|
417
277
|
}
|
|
418
278
|
static convertPathToCamelCase(path) {
|
|
419
279
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -433,10 +293,10 @@ class Utils {
|
|
|
433
293
|
return undefined;
|
|
434
294
|
}
|
|
435
295
|
}
|
|
436
|
-
static
|
|
296
|
+
static resolveEnv(str) {
|
|
437
297
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
438
|
-
let matches = placeHolderReg.exec(
|
|
439
|
-
let
|
|
298
|
+
let matches = placeHolderReg.exec(str);
|
|
299
|
+
let newStr = str;
|
|
440
300
|
while (matches != null) {
|
|
441
301
|
const envVar = matches[1];
|
|
442
302
|
const envVal = process.env[envVar];
|
|
@@ -444,17 +304,17 @@ class Utils {
|
|
|
444
304
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
445
305
|
}
|
|
446
306
|
else {
|
|
447
|
-
|
|
307
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
448
308
|
}
|
|
449
|
-
matches = placeHolderReg.exec(
|
|
309
|
+
matches = placeHolderReg.exec(str);
|
|
450
310
|
}
|
|
451
|
-
return
|
|
311
|
+
return newStr;
|
|
452
312
|
}
|
|
453
313
|
static checkServerUrl(servers) {
|
|
454
314
|
const errors = [];
|
|
455
315
|
let serverUrl;
|
|
456
316
|
try {
|
|
457
|
-
serverUrl = Utils.
|
|
317
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
458
318
|
}
|
|
459
319
|
catch (err) {
|
|
460
320
|
errors.push({
|
|
@@ -484,7 +344,8 @@ class Utils {
|
|
|
484
344
|
}
|
|
485
345
|
return errors;
|
|
486
346
|
}
|
|
487
|
-
static validateServer(spec,
|
|
347
|
+
static validateServer(spec, options) {
|
|
348
|
+
var _a;
|
|
488
349
|
const errors = [];
|
|
489
350
|
let hasTopLevelServers = false;
|
|
490
351
|
let hasPathLevelServers = false;
|
|
@@ -505,7 +366,7 @@ class Utils {
|
|
|
505
366
|
}
|
|
506
367
|
for (const method in methods) {
|
|
507
368
|
const operationObject = methods[method];
|
|
508
|
-
if (
|
|
369
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
509
370
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
510
371
|
hasOperationLevelServers = true;
|
|
511
372
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -548,6 +409,7 @@ class Utils {
|
|
|
548
409
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
549
410
|
}
|
|
550
411
|
if (isRequired && schema.default === undefined) {
|
|
412
|
+
parameter.isRequired = true;
|
|
551
413
|
requiredParams.push(parameter);
|
|
552
414
|
}
|
|
553
415
|
else {
|
|
@@ -592,7 +454,7 @@ class Utils {
|
|
|
592
454
|
param.value = schema.default;
|
|
593
455
|
}
|
|
594
456
|
}
|
|
595
|
-
static parseApiInfo(operationItem,
|
|
457
|
+
static parseApiInfo(operationItem, options) {
|
|
596
458
|
var _a, _b;
|
|
597
459
|
const requiredParams = [];
|
|
598
460
|
const optionalParams = [];
|
|
@@ -606,11 +468,12 @@ class Utils {
|
|
|
606
468
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
607
469
|
};
|
|
608
470
|
const schema = param.schema;
|
|
609
|
-
if (allowMultipleParameters && schema) {
|
|
471
|
+
if (options.allowMultipleParameters && schema) {
|
|
610
472
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
611
473
|
}
|
|
612
474
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
613
475
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
476
|
+
parameter.isRequired = true;
|
|
614
477
|
requiredParams.push(parameter);
|
|
615
478
|
}
|
|
616
479
|
else {
|
|
@@ -624,19 +487,13 @@ class Utils {
|
|
|
624
487
|
const requestJson = requestBody.content["application/json"];
|
|
625
488
|
if (Object.keys(requestJson).length !== 0) {
|
|
626
489
|
const schema = requestJson.schema;
|
|
627
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
490
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
628
491
|
requiredParams.push(...requiredP);
|
|
629
492
|
optionalParams.push(...optionalP);
|
|
630
493
|
}
|
|
631
494
|
}
|
|
632
495
|
const operationId = operationItem.operationId;
|
|
633
|
-
const parameters = [];
|
|
634
|
-
if (requiredParams.length !== 0) {
|
|
635
|
-
parameters.push(...requiredParams);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
parameters.push(optionalParams[0]);
|
|
639
|
-
}
|
|
496
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
640
497
|
const command = {
|
|
641
498
|
context: ["compose"],
|
|
642
499
|
type: "query",
|
|
@@ -645,32 +502,9 @@ class Utils {
|
|
|
645
502
|
parameters: parameters,
|
|
646
503
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
647
504
|
};
|
|
648
|
-
|
|
649
|
-
if (requiredParams.length === 0 && optionalParams.length > 1) {
|
|
650
|
-
warning = {
|
|
651
|
-
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
652
|
-
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
|
|
653
|
-
data: operationId,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
return [command, warning];
|
|
657
|
-
}
|
|
658
|
-
static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
659
|
-
const paths = spec.paths;
|
|
660
|
-
const result = {};
|
|
661
|
-
for (const path in paths) {
|
|
662
|
-
const methods = paths[path];
|
|
663
|
-
for (const method in methods) {
|
|
664
|
-
// For developer preview, only support GET operation with only 1 parameter without auth
|
|
665
|
-
if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
|
|
666
|
-
const operationObject = methods[method];
|
|
667
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
return result;
|
|
505
|
+
return command;
|
|
672
506
|
}
|
|
673
|
-
static validateSpec(spec, parser,
|
|
507
|
+
static validateSpec(spec, parser, apiMap, isSwaggerFile, options) {
|
|
674
508
|
const errors = [];
|
|
675
509
|
const warnings = [];
|
|
676
510
|
if (isSwaggerFile) {
|
|
@@ -679,8 +513,7 @@ class Utils {
|
|
|
679
513
|
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
680
514
|
});
|
|
681
515
|
}
|
|
682
|
-
|
|
683
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
|
|
516
|
+
const serverErrors = Utils.validateServer(spec, options);
|
|
684
517
|
errors.push(...serverErrors);
|
|
685
518
|
// Remote reference not supported
|
|
686
519
|
const refPaths = parser.$refs.paths();
|
|
@@ -693,8 +526,8 @@ class Utils {
|
|
|
693
526
|
});
|
|
694
527
|
}
|
|
695
528
|
// No supported API
|
|
696
|
-
const
|
|
697
|
-
if (
|
|
529
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
530
|
+
if (validAPIs.length === 0) {
|
|
698
531
|
errors.push({
|
|
699
532
|
type: ErrorType.NoSupportedApi,
|
|
700
533
|
content: ConstantString.NoSupportedApi,
|
|
@@ -703,8 +536,8 @@ class Utils {
|
|
|
703
536
|
// OperationId missing
|
|
704
537
|
const apisMissingOperationId = [];
|
|
705
538
|
for (const key in apiMap) {
|
|
706
|
-
const
|
|
707
|
-
if (!
|
|
539
|
+
const { operation } = apiMap[key];
|
|
540
|
+
if (!operation.operationId) {
|
|
708
541
|
apisMissingOperationId.push(key);
|
|
709
542
|
}
|
|
710
543
|
}
|
|
@@ -745,6 +578,402 @@ class Utils {
|
|
|
745
578
|
}
|
|
746
579
|
return safeRegistrationIdEnvName;
|
|
747
580
|
}
|
|
581
|
+
static getAllAPICount(spec) {
|
|
582
|
+
let count = 0;
|
|
583
|
+
const paths = spec.paths;
|
|
584
|
+
for (const path in paths) {
|
|
585
|
+
const methods = paths[path];
|
|
586
|
+
for (const method in methods) {
|
|
587
|
+
if (ConstantString.AllOperationMethods.includes(method)) {
|
|
588
|
+
count++;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return count;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Copyright (c) Microsoft Corporation.
|
|
597
|
+
class Validator {
|
|
598
|
+
validateMethodAndPath(method, path) {
|
|
599
|
+
const result = { isValid: true, reason: [] };
|
|
600
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
601
|
+
result.isValid = false;
|
|
602
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
const pathObj = this.spec.paths[path];
|
|
606
|
+
if (!pathObj || !pathObj[method]) {
|
|
607
|
+
result.isValid = false;
|
|
608
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
validateResponse(method, path) {
|
|
614
|
+
const result = { isValid: true, reason: [] };
|
|
615
|
+
const operationObject = this.spec.paths[path][method];
|
|
616
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
617
|
+
// only support response body only contains “application/json” content type
|
|
618
|
+
if (multipleMediaType) {
|
|
619
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
620
|
+
}
|
|
621
|
+
else if (Object.keys(json).length === 0) {
|
|
622
|
+
// response body should not be empty
|
|
623
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
validateServer(method, path) {
|
|
628
|
+
const pathObj = this.spec.paths[path];
|
|
629
|
+
const result = { isValid: true, reason: [] };
|
|
630
|
+
const operationObject = pathObj[method];
|
|
631
|
+
const rootServer = this.spec.servers && this.spec.servers[0];
|
|
632
|
+
const methodServer = this.spec.paths[path].servers && this.spec.paths[path].servers[0];
|
|
633
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
634
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
635
|
+
if (!serverUrl) {
|
|
636
|
+
// should contain server URL
|
|
637
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
// server url should be absolute url with https protocol
|
|
641
|
+
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
642
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
643
|
+
}
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
validateAuth(method, path) {
|
|
647
|
+
const pathObj = this.spec.paths[path];
|
|
648
|
+
const operationObject = pathObj[method];
|
|
649
|
+
const securities = operationObject.security;
|
|
650
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
651
|
+
if (authSchemeArray.length === 0) {
|
|
652
|
+
return { isValid: true, reason: [] };
|
|
653
|
+
}
|
|
654
|
+
if (this.options.allowAPIKeyAuth ||
|
|
655
|
+
this.options.allowOauth2 ||
|
|
656
|
+
this.options.allowBearerTokenAuth) {
|
|
657
|
+
// Currently we don't support multiple auth in one operation
|
|
658
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
659
|
+
return {
|
|
660
|
+
isValid: false,
|
|
661
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
for (const auths of authSchemeArray) {
|
|
665
|
+
if (auths.length === 1) {
|
|
666
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
667
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
668
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
669
|
+
return { isValid: true, reason: [] };
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
675
|
+
}
|
|
676
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
677
|
+
var _a;
|
|
678
|
+
const paramResult = {
|
|
679
|
+
requiredNum: 0,
|
|
680
|
+
optionalNum: 0,
|
|
681
|
+
isValid: true,
|
|
682
|
+
reason: [],
|
|
683
|
+
};
|
|
684
|
+
if (Object.keys(schema).length === 0) {
|
|
685
|
+
return paramResult;
|
|
686
|
+
}
|
|
687
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
688
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
689
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
690
|
+
paramResult.isValid = false;
|
|
691
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
692
|
+
return paramResult;
|
|
693
|
+
}
|
|
694
|
+
if (schema.type === "string" ||
|
|
695
|
+
schema.type === "integer" ||
|
|
696
|
+
schema.type === "boolean" ||
|
|
697
|
+
schema.type === "number") {
|
|
698
|
+
if (isRequiredWithoutDefault) {
|
|
699
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else if (schema.type === "object") {
|
|
706
|
+
const { properties } = schema;
|
|
707
|
+
for (const property in properties) {
|
|
708
|
+
let isRequired = false;
|
|
709
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
710
|
+
isRequired = true;
|
|
711
|
+
}
|
|
712
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
713
|
+
paramResult.requiredNum += result.requiredNum;
|
|
714
|
+
paramResult.optionalNum += result.optionalNum;
|
|
715
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
716
|
+
paramResult.reason.push(...result.reason);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
721
|
+
paramResult.isValid = false;
|
|
722
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return paramResult;
|
|
726
|
+
}
|
|
727
|
+
checkParamSchema(paramObject) {
|
|
728
|
+
const paramResult = {
|
|
729
|
+
requiredNum: 0,
|
|
730
|
+
optionalNum: 0,
|
|
731
|
+
isValid: true,
|
|
732
|
+
reason: [],
|
|
733
|
+
};
|
|
734
|
+
if (!paramObject) {
|
|
735
|
+
return paramResult;
|
|
736
|
+
}
|
|
737
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
738
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
739
|
+
const param = paramObject[i];
|
|
740
|
+
const schema = param.schema;
|
|
741
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
742
|
+
paramResult.isValid = false;
|
|
743
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
747
|
+
if (isCopilot) {
|
|
748
|
+
if (isRequiredWithoutDefault) {
|
|
749
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
753
|
+
}
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
757
|
+
if (isRequiredWithoutDefault) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (schema.type !== "boolean" &&
|
|
764
|
+
schema.type !== "string" &&
|
|
765
|
+
schema.type !== "number" &&
|
|
766
|
+
schema.type !== "integer") {
|
|
767
|
+
if (isRequiredWithoutDefault) {
|
|
768
|
+
paramResult.isValid = false;
|
|
769
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
770
|
+
}
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (param.in === "query" || param.in === "path") {
|
|
774
|
+
if (isRequiredWithoutDefault) {
|
|
775
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return paramResult;
|
|
783
|
+
}
|
|
784
|
+
hasNestedObjectInSchema(schema) {
|
|
785
|
+
if (schema.type === "object") {
|
|
786
|
+
for (const property in schema.properties) {
|
|
787
|
+
const nestedSchema = schema.properties[property];
|
|
788
|
+
if (nestedSchema.type === "object") {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Copyright (c) Microsoft Corporation.
|
|
798
|
+
class CopilotValidator extends Validator {
|
|
799
|
+
constructor(spec, options) {
|
|
800
|
+
super();
|
|
801
|
+
this.projectType = ProjectType.Copilot;
|
|
802
|
+
this.options = options;
|
|
803
|
+
this.spec = spec;
|
|
804
|
+
}
|
|
805
|
+
validateAPI(method, path) {
|
|
806
|
+
const result = { isValid: true, reason: [] };
|
|
807
|
+
method = method.toLocaleLowerCase();
|
|
808
|
+
// validate method and path
|
|
809
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
810
|
+
if (!methodAndPathResult.isValid) {
|
|
811
|
+
return methodAndPathResult;
|
|
812
|
+
}
|
|
813
|
+
const operationObject = this.spec.paths[path][method];
|
|
814
|
+
// validate auth
|
|
815
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
816
|
+
result.reason.push(...authCheckResult.reason);
|
|
817
|
+
// validate operationId
|
|
818
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
819
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
820
|
+
}
|
|
821
|
+
// validate server
|
|
822
|
+
const validateServerResult = this.validateServer(method, path);
|
|
823
|
+
result.reason.push(...validateServerResult.reason);
|
|
824
|
+
// validate response
|
|
825
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
826
|
+
result.reason.push(...validateResponseResult.reason);
|
|
827
|
+
// validate requestBody
|
|
828
|
+
const requestBody = operationObject.requestBody;
|
|
829
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
830
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
831
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
832
|
+
}
|
|
833
|
+
if (requestJsonBody) {
|
|
834
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
835
|
+
if (requestBodySchema.type !== "object") {
|
|
836
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
837
|
+
}
|
|
838
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
839
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
840
|
+
}
|
|
841
|
+
// validate parameters
|
|
842
|
+
const paramObject = operationObject.parameters;
|
|
843
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
844
|
+
result.reason.push(...paramResult.reason);
|
|
845
|
+
if (result.reason.length > 0) {
|
|
846
|
+
result.isValid = false;
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Copyright (c) Microsoft Corporation.
|
|
853
|
+
class SMEValidator extends Validator {
|
|
854
|
+
constructor(spec, options) {
|
|
855
|
+
super();
|
|
856
|
+
this.projectType = ProjectType.SME;
|
|
857
|
+
this.options = options;
|
|
858
|
+
this.spec = spec;
|
|
859
|
+
}
|
|
860
|
+
validateAPI(method, path) {
|
|
861
|
+
const result = { isValid: true, reason: [] };
|
|
862
|
+
method = method.toLocaleLowerCase();
|
|
863
|
+
// validate method and path
|
|
864
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
865
|
+
if (!methodAndPathResult.isValid) {
|
|
866
|
+
return methodAndPathResult;
|
|
867
|
+
}
|
|
868
|
+
const operationObject = this.spec.paths[path][method];
|
|
869
|
+
// validate auth
|
|
870
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
871
|
+
result.reason.push(...authCheckResult.reason);
|
|
872
|
+
// validate operationId
|
|
873
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
874
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
875
|
+
}
|
|
876
|
+
// validate server
|
|
877
|
+
const validateServerResult = this.validateServer(method, path);
|
|
878
|
+
result.reason.push(...validateServerResult.reason);
|
|
879
|
+
// validate response
|
|
880
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
881
|
+
result.reason.push(...validateResponseResult.reason);
|
|
882
|
+
let postBodyResult = {
|
|
883
|
+
requiredNum: 0,
|
|
884
|
+
optionalNum: 0,
|
|
885
|
+
isValid: true,
|
|
886
|
+
reason: [],
|
|
887
|
+
};
|
|
888
|
+
// validate requestBody
|
|
889
|
+
const requestBody = operationObject.requestBody;
|
|
890
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
891
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
892
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
893
|
+
}
|
|
894
|
+
if (requestJsonBody) {
|
|
895
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
896
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
897
|
+
result.reason.push(...postBodyResult.reason);
|
|
898
|
+
}
|
|
899
|
+
// validate parameters
|
|
900
|
+
const paramObject = operationObject.parameters;
|
|
901
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
902
|
+
result.reason.push(...paramResult.reason);
|
|
903
|
+
// validate total parameters count
|
|
904
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
905
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
906
|
+
result.reason.push(...paramCountResult.reason);
|
|
907
|
+
}
|
|
908
|
+
if (result.reason.length > 0) {
|
|
909
|
+
result.isValid = false;
|
|
910
|
+
}
|
|
911
|
+
return result;
|
|
912
|
+
}
|
|
913
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
914
|
+
const result = { isValid: true, reason: [] };
|
|
915
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
916
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
917
|
+
if (totalRequiredParams > 1) {
|
|
918
|
+
if (!this.options.allowMultipleParameters ||
|
|
919
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
920
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else if (totalParams === 0) {
|
|
924
|
+
result.reason.push(ErrorType.NoParameter);
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
930
|
+
|
|
931
|
+
// Copyright (c) Microsoft Corporation.
|
|
932
|
+
class TeamsAIValidator extends Validator {
|
|
933
|
+
constructor(spec, options) {
|
|
934
|
+
super();
|
|
935
|
+
this.projectType = ProjectType.TeamsAi;
|
|
936
|
+
this.options = options;
|
|
937
|
+
this.spec = spec;
|
|
938
|
+
}
|
|
939
|
+
validateAPI(method, path) {
|
|
940
|
+
const result = { isValid: true, reason: [] };
|
|
941
|
+
method = method.toLocaleLowerCase();
|
|
942
|
+
// validate method and path
|
|
943
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
944
|
+
if (!methodAndPathResult.isValid) {
|
|
945
|
+
return methodAndPathResult;
|
|
946
|
+
}
|
|
947
|
+
const operationObject = this.spec.paths[path][method];
|
|
948
|
+
// validate operationId
|
|
949
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
950
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
951
|
+
}
|
|
952
|
+
// validate server
|
|
953
|
+
const validateServerResult = this.validateServer(method, path);
|
|
954
|
+
result.reason.push(...validateServerResult.reason);
|
|
955
|
+
if (result.reason.length > 0) {
|
|
956
|
+
result.isValid = false;
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
class ValidatorFactory {
|
|
963
|
+
static create(spec, options) {
|
|
964
|
+
var _a;
|
|
965
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
966
|
+
switch (type) {
|
|
967
|
+
case ProjectType.SME:
|
|
968
|
+
return new SMEValidator(spec, options);
|
|
969
|
+
case ProjectType.Copilot:
|
|
970
|
+
return new CopilotValidator(spec, options);
|
|
971
|
+
case ProjectType.TeamsAi:
|
|
972
|
+
return new TeamsAIValidator(spec, options);
|
|
973
|
+
default:
|
|
974
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
748
977
|
}
|
|
749
978
|
|
|
750
979
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -763,7 +992,10 @@ class SpecParser {
|
|
|
763
992
|
allowSwagger: false,
|
|
764
993
|
allowAPIKeyAuth: false,
|
|
765
994
|
allowMultipleParameters: false,
|
|
995
|
+
allowBearerTokenAuth: false,
|
|
766
996
|
allowOauth2: false,
|
|
997
|
+
allowMethods: ["get", "post"],
|
|
998
|
+
projectType: ProjectType.SME,
|
|
767
999
|
};
|
|
768
1000
|
this.pathOrSpec = pathOrDoc;
|
|
769
1001
|
this.parser = new SwaggerParser();
|
|
@@ -801,7 +1033,8 @@ class SpecParser {
|
|
|
801
1033
|
],
|
|
802
1034
|
};
|
|
803
1035
|
}
|
|
804
|
-
|
|
1036
|
+
const apiMap = this.getAPIs(this.spec);
|
|
1037
|
+
return Utils.validateSpec(this.spec, this.parser, apiMap, !!this.isSwaggerFile, this.options);
|
|
805
1038
|
}
|
|
806
1039
|
catch (err) {
|
|
807
1040
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -812,17 +1045,20 @@ class SpecParser {
|
|
|
812
1045
|
return __awaiter(this, void 0, void 0, function* () {
|
|
813
1046
|
try {
|
|
814
1047
|
yield this.loadSpec();
|
|
815
|
-
const apiMap = this.
|
|
1048
|
+
const apiMap = this.getAPIs(this.spec);
|
|
816
1049
|
const apiInfos = [];
|
|
817
1050
|
for (const key in apiMap) {
|
|
818
|
-
const
|
|
1051
|
+
const { operation, isValid } = apiMap[key];
|
|
1052
|
+
if (!isValid) {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
819
1055
|
const [method, path] = key.split(" ");
|
|
820
|
-
const operationId =
|
|
1056
|
+
const operationId = operation.operationId;
|
|
821
1057
|
// In Browser environment, this api is by default not support api without operationId
|
|
822
1058
|
if (!operationId) {
|
|
823
1059
|
continue;
|
|
824
1060
|
}
|
|
825
|
-
const
|
|
1061
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
826
1062
|
const apiInfo = {
|
|
827
1063
|
method: method,
|
|
828
1064
|
path: path,
|
|
@@ -831,9 +1067,6 @@ class SpecParser {
|
|
|
831
1067
|
parameters: command.parameters,
|
|
832
1068
|
description: command.description,
|
|
833
1069
|
};
|
|
834
|
-
if (warning) {
|
|
835
|
-
apiInfo.warning = warning;
|
|
836
|
-
}
|
|
837
1070
|
apiInfos.push(apiInfo);
|
|
838
1071
|
}
|
|
839
1072
|
return apiInfos;
|
|
@@ -854,12 +1087,36 @@ class SpecParser {
|
|
|
854
1087
|
throw new Error("Method not implemented.");
|
|
855
1088
|
});
|
|
856
1089
|
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Generate specs according to the filters.
|
|
1092
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1093
|
+
*/
|
|
1094
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1095
|
+
getFilteredSpecs(filter, signal) {
|
|
1096
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1097
|
+
throw new Error("Method not implemented.");
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1102
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1103
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1104
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1105
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1106
|
+
*/
|
|
1107
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1108
|
+
generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1110
|
+
throw new Error("Method not implemented.");
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
857
1113
|
/**
|
|
858
1114
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
859
1115
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
860
1116
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
861
1117
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
862
1118
|
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1119
|
+
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
863
1120
|
*/
|
|
864
1121
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
865
1122
|
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
@@ -879,15 +1136,194 @@ class SpecParser {
|
|
|
879
1136
|
}
|
|
880
1137
|
});
|
|
881
1138
|
}
|
|
882
|
-
|
|
1139
|
+
getAPIs(spec) {
|
|
883
1140
|
if (this.apiMap !== undefined) {
|
|
884
1141
|
return this.apiMap;
|
|
885
1142
|
}
|
|
886
|
-
const result =
|
|
1143
|
+
const result = this.listAPIs(spec);
|
|
887
1144
|
this.apiMap = result;
|
|
888
1145
|
return result;
|
|
889
1146
|
}
|
|
1147
|
+
listAPIs(spec) {
|
|
1148
|
+
var _a;
|
|
1149
|
+
const paths = spec.paths;
|
|
1150
|
+
const result = {};
|
|
1151
|
+
for (const path in paths) {
|
|
1152
|
+
const methods = paths[path];
|
|
1153
|
+
for (const method in methods) {
|
|
1154
|
+
const operationObject = methods[method];
|
|
1155
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1156
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1157
|
+
const validateResult = validator.validateAPI(method, path);
|
|
1158
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
1159
|
+
operation: operationObject,
|
|
1160
|
+
isValid: validateResult.isValid,
|
|
1161
|
+
reason: validateResult.reason,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return result;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Copyright (c) Microsoft Corporation.
|
|
1171
|
+
class AdaptiveCardGenerator {
|
|
1172
|
+
static generateAdaptiveCard(operationItem) {
|
|
1173
|
+
try {
|
|
1174
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1175
|
+
let cardBody = [];
|
|
1176
|
+
let schema = json.schema;
|
|
1177
|
+
let jsonPath = "$";
|
|
1178
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
1179
|
+
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1180
|
+
if (jsonPath !== "$") {
|
|
1181
|
+
schema = schema.properties[jsonPath];
|
|
1182
|
+
}
|
|
1183
|
+
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1184
|
+
}
|
|
1185
|
+
// if no schema, try to use example value
|
|
1186
|
+
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1187
|
+
cardBody = [
|
|
1188
|
+
{
|
|
1189
|
+
type: ConstantString.TextBlockType,
|
|
1190
|
+
text: "${jsonStringify($root)}",
|
|
1191
|
+
wrap: true,
|
|
1192
|
+
},
|
|
1193
|
+
];
|
|
1194
|
+
}
|
|
1195
|
+
// if no example value, use default success response
|
|
1196
|
+
if (cardBody.length === 0) {
|
|
1197
|
+
cardBody = [
|
|
1198
|
+
{
|
|
1199
|
+
type: ConstantString.TextBlockType,
|
|
1200
|
+
text: "success",
|
|
1201
|
+
wrap: true,
|
|
1202
|
+
},
|
|
1203
|
+
];
|
|
1204
|
+
}
|
|
1205
|
+
const fullCard = {
|
|
1206
|
+
type: ConstantString.AdaptiveCardType,
|
|
1207
|
+
$schema: ConstantString.AdaptiveCardSchema,
|
|
1208
|
+
version: ConstantString.AdaptiveCardVersion,
|
|
1209
|
+
body: cardBody,
|
|
1210
|
+
};
|
|
1211
|
+
return [fullCard, jsonPath];
|
|
1212
|
+
}
|
|
1213
|
+
catch (err) {
|
|
1214
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1218
|
+
if (schema.type === "array") {
|
|
1219
|
+
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1220
|
+
if (Object.keys(schema.items).length === 0) {
|
|
1221
|
+
return [
|
|
1222
|
+
{
|
|
1223
|
+
type: ConstantString.TextBlockType,
|
|
1224
|
+
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1225
|
+
wrap: true,
|
|
1226
|
+
},
|
|
1227
|
+
];
|
|
1228
|
+
}
|
|
1229
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1230
|
+
const template = {
|
|
1231
|
+
type: ConstantString.ContainerType,
|
|
1232
|
+
$data: name ? `\${${name}}` : "${$root}",
|
|
1233
|
+
items: Array(),
|
|
1234
|
+
};
|
|
1235
|
+
template.items.push(...obj);
|
|
1236
|
+
return [template];
|
|
1237
|
+
}
|
|
1238
|
+
// some schema may not contain type but contain properties
|
|
1239
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1240
|
+
const { properties } = schema;
|
|
1241
|
+
const result = [];
|
|
1242
|
+
for (const property in properties) {
|
|
1243
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1244
|
+
result.push(...obj);
|
|
1245
|
+
}
|
|
1246
|
+
if (schema.additionalProperties) {
|
|
1247
|
+
// TODO: better ways to handler warnings.
|
|
1248
|
+
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1249
|
+
}
|
|
1250
|
+
return result;
|
|
1251
|
+
}
|
|
1252
|
+
if (schema.type === "string" ||
|
|
1253
|
+
schema.type === "integer" ||
|
|
1254
|
+
schema.type === "boolean" ||
|
|
1255
|
+
schema.type === "number") {
|
|
1256
|
+
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1257
|
+
// string in root: "ddd"
|
|
1258
|
+
let text = "result: ${$root}";
|
|
1259
|
+
if (name) {
|
|
1260
|
+
// object { id: "1" }
|
|
1261
|
+
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1262
|
+
if (parentArrayName) {
|
|
1263
|
+
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1264
|
+
text = `${parentArrayName}.${text}`;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
else if (parentArrayName) {
|
|
1268
|
+
// string array: photoUrls: ["1", "2"]
|
|
1269
|
+
text = `${parentArrayName}: ` + "${$data}";
|
|
1270
|
+
}
|
|
1271
|
+
return [
|
|
1272
|
+
{
|
|
1273
|
+
type: ConstantString.TextBlockType,
|
|
1274
|
+
text,
|
|
1275
|
+
wrap: true,
|
|
1276
|
+
},
|
|
1277
|
+
];
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
if (name) {
|
|
1281
|
+
return [
|
|
1282
|
+
{
|
|
1283
|
+
type: "Image",
|
|
1284
|
+
url: `\${${name}}`,
|
|
1285
|
+
$when: `\${${name} != null}`,
|
|
1286
|
+
},
|
|
1287
|
+
];
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
return [
|
|
1291
|
+
{
|
|
1292
|
+
type: "Image",
|
|
1293
|
+
url: "${$data}",
|
|
1294
|
+
$when: "${$data != null}",
|
|
1295
|
+
},
|
|
1296
|
+
];
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1301
|
+
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1302
|
+
}
|
|
1303
|
+
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1304
|
+
}
|
|
1305
|
+
// Find the first array property in the response schema object with the well-known name
|
|
1306
|
+
static getResponseJsonPathFromSchema(schema) {
|
|
1307
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1308
|
+
const { properties } = schema;
|
|
1309
|
+
for (const property in properties) {
|
|
1310
|
+
const schema = properties[property];
|
|
1311
|
+
if (schema.type === "array" &&
|
|
1312
|
+
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1313
|
+
return property;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return "$";
|
|
1318
|
+
}
|
|
1319
|
+
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1320
|
+
const propertyName = name ? name : parentArrayName;
|
|
1321
|
+
return (!!propertyName &&
|
|
1322
|
+
schema.type === "string" &&
|
|
1323
|
+
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1324
|
+
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1325
|
+
}
|
|
890
1326
|
}
|
|
891
1327
|
|
|
892
|
-
export { ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1328
|
+
export { AdaptiveCardGenerator, ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
893
1329
|
//# sourceMappingURL=index.esm5.js.map
|