@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.mjs
CHANGED
|
@@ -19,7 +19,8 @@ var ErrorType;
|
|
|
19
19
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
20
20
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
21
21
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
22
|
-
ErrorType["
|
|
22
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
23
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
23
24
|
ErrorType["ListFailed"] = "list-failed";
|
|
24
25
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
25
26
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -27,6 +28,22 @@ var ErrorType;
|
|
|
27
28
|
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
|
|
28
29
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
29
30
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
31
|
+
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
32
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
33
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
34
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
35
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
36
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
37
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
38
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
39
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
40
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
41
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
42
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
43
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
44
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
45
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
46
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
30
47
|
ErrorType["Cancelled"] = "cancelled";
|
|
31
48
|
ErrorType["Unknown"] = "unknown";
|
|
32
49
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -49,7 +66,13 @@ var ValidationStatus;
|
|
|
49
66
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
50
67
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
51
68
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
52
|
-
})(ValidationStatus || (ValidationStatus = {}));
|
|
69
|
+
})(ValidationStatus || (ValidationStatus = {}));
|
|
70
|
+
var ProjectType;
|
|
71
|
+
(function (ProjectType) {
|
|
72
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
73
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
74
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
75
|
+
})(ProjectType || (ProjectType = {}));
|
|
53
76
|
|
|
54
77
|
// Copyright (c) Microsoft Corporation.
|
|
55
78
|
class ConstantString {
|
|
@@ -68,7 +91,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
68
91
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
69
92
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
70
93
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
71
|
-
ConstantString.
|
|
94
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
95
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
96
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
72
97
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
73
98
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
74
99
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -80,6 +105,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
|
80
105
|
ConstantString.TextBlockType = "TextBlock";
|
|
81
106
|
ConstantString.ContainerType = "Container";
|
|
82
107
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
108
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
83
109
|
ConstantString.ResponseCodeFor20X = [
|
|
84
110
|
"200",
|
|
85
111
|
"201",
|
|
@@ -139,7 +165,8 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
139
165
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
140
166
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
141
167
|
ConstantString.CommandTitleMaxLens = 32;
|
|
142
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
168
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
169
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
143
170
|
|
|
144
171
|
// Copyright (c) Microsoft Corporation.
|
|
145
172
|
class SpecParserError extends Error {
|
|
@@ -151,201 +178,30 @@ class SpecParserError extends Error {
|
|
|
151
178
|
|
|
152
179
|
// Copyright (c) Microsoft Corporation.
|
|
153
180
|
class Utils {
|
|
154
|
-
static
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
if (!paramObject) {
|
|
161
|
-
return paramResult;
|
|
162
|
-
}
|
|
163
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
164
|
-
const param = paramObject[i];
|
|
165
|
-
const schema = param.schema;
|
|
166
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
167
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
168
|
-
if (isRequiredWithoutDefault) {
|
|
169
|
-
paramResult.isValid = false;
|
|
170
|
-
}
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (schema.type !== "boolean" &&
|
|
174
|
-
schema.type !== "string" &&
|
|
175
|
-
schema.type !== "number" &&
|
|
176
|
-
schema.type !== "integer") {
|
|
177
|
-
if (isRequiredWithoutDefault) {
|
|
178
|
-
paramResult.isValid = false;
|
|
179
|
-
}
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (param.in === "query" || param.in === "path") {
|
|
183
|
-
if (isRequiredWithoutDefault) {
|
|
184
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return paramResult;
|
|
192
|
-
}
|
|
193
|
-
static checkPostBody(schema, isRequired = false) {
|
|
194
|
-
var _a;
|
|
195
|
-
const paramResult = {
|
|
196
|
-
requiredNum: 0,
|
|
197
|
-
optionalNum: 0,
|
|
198
|
-
isValid: true,
|
|
199
|
-
};
|
|
200
|
-
if (Object.keys(schema).length === 0) {
|
|
201
|
-
return paramResult;
|
|
202
|
-
}
|
|
203
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
204
|
-
if (schema.type === "string" ||
|
|
205
|
-
schema.type === "integer" ||
|
|
206
|
-
schema.type === "boolean" ||
|
|
207
|
-
schema.type === "number") {
|
|
208
|
-
if (isRequiredWithoutDefault) {
|
|
209
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
else if (schema.type === "object") {
|
|
216
|
-
const { properties } = schema;
|
|
217
|
-
for (const property in properties) {
|
|
218
|
-
let isRequired = false;
|
|
219
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
220
|
-
isRequired = true;
|
|
221
|
-
}
|
|
222
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
223
|
-
paramResult.requiredNum += result.requiredNum;
|
|
224
|
-
paramResult.optionalNum += result.optionalNum;
|
|
225
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
if (isRequiredWithoutDefault) {
|
|
230
|
-
paramResult.isValid = false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return paramResult;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Checks if the given API is supported.
|
|
237
|
-
* @param {string} method - The HTTP method of the API.
|
|
238
|
-
* @param {string} path - The path of the API.
|
|
239
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
240
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
241
|
-
* @description The following APIs are supported:
|
|
242
|
-
* 1. only support Get/Post operation without auth property
|
|
243
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
244
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
245
|
-
* 4. request body + required parameters <= 1
|
|
246
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
247
|
-
* 6. only support request body with “application/json” content type
|
|
248
|
-
*/
|
|
249
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
250
|
-
const pathObj = spec.paths[path];
|
|
251
|
-
method = method.toLocaleLowerCase();
|
|
252
|
-
if (pathObj) {
|
|
253
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
254
|
-
pathObj[method]) {
|
|
255
|
-
const securities = pathObj[method].security;
|
|
256
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
257
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
const operationObject = pathObj[method];
|
|
261
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
const paramObject = operationObject.parameters;
|
|
265
|
-
const requestBody = operationObject.requestBody;
|
|
266
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
267
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
268
|
-
if (mediaTypesCount > 1) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
272
|
-
if (Object.keys(responseJson).length === 0) {
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
let requestBodyParamResult = {
|
|
276
|
-
requiredNum: 0,
|
|
277
|
-
optionalNum: 0,
|
|
278
|
-
isValid: true,
|
|
279
|
-
};
|
|
280
|
-
if (requestJsonBody) {
|
|
281
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
282
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
283
|
-
}
|
|
284
|
-
if (!requestBodyParamResult.isValid) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
288
|
-
if (!paramResult.isValid) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
292
|
-
if (allowMultipleParameters &&
|
|
293
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
else if (requestBodyParamResult.requiredNum +
|
|
299
|
-
requestBodyParamResult.optionalNum +
|
|
300
|
-
paramResult.requiredNum +
|
|
301
|
-
paramResult.optionalNum ===
|
|
302
|
-
0) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
181
|
+
static hasNestedObjectInSchema(schema) {
|
|
182
|
+
if (schema.type === "object") {
|
|
183
|
+
for (const property in schema.properties) {
|
|
184
|
+
const nestedSchema = schema.properties[property];
|
|
185
|
+
if (nestedSchema.type === "object") {
|
|
306
186
|
return true;
|
|
307
187
|
}
|
|
308
188
|
}
|
|
309
189
|
}
|
|
310
190
|
return false;
|
|
311
191
|
}
|
|
312
|
-
static
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// Currently we don't support multiple auth in one operation
|
|
318
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
for (const auths of authSchemaArray) {
|
|
322
|
-
if (auths.length === 1) {
|
|
323
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
else if (!allowAPIKeyAuth &&
|
|
327
|
-
allowOauth2 &&
|
|
328
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
else if (allowAPIKeyAuth &&
|
|
332
|
-
allowOauth2 &&
|
|
333
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
334
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return false;
|
|
192
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
193
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
194
|
+
}
|
|
195
|
+
static isBearerTokenAuth(authScheme) {
|
|
196
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
341
197
|
}
|
|
342
|
-
static isAPIKeyAuth(
|
|
343
|
-
return
|
|
198
|
+
static isAPIKeyAuth(authScheme) {
|
|
199
|
+
return authScheme.type === "apiKey";
|
|
344
200
|
}
|
|
345
|
-
static
|
|
346
|
-
return (
|
|
347
|
-
|
|
348
|
-
|
|
201
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
202
|
+
return !!(authScheme.type === "oauth2" &&
|
|
203
|
+
authScheme.flows &&
|
|
204
|
+
authScheme.flows.authorizationCode);
|
|
349
205
|
}
|
|
350
206
|
static getAuthArray(securities, spec) {
|
|
351
207
|
var _a;
|
|
@@ -358,7 +214,7 @@ class Utils {
|
|
|
358
214
|
for (const name in security) {
|
|
359
215
|
const auth = securitySchemas[name];
|
|
360
216
|
authArray.push({
|
|
361
|
-
|
|
217
|
+
authScheme: auth,
|
|
362
218
|
name: name,
|
|
363
219
|
});
|
|
364
220
|
}
|
|
@@ -376,18 +232,22 @@ class Utils {
|
|
|
376
232
|
static getResponseJson(operationObject) {
|
|
377
233
|
var _a, _b;
|
|
378
234
|
let json = {};
|
|
235
|
+
let multipleMediaType = false;
|
|
379
236
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
380
237
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
381
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
382
|
-
if (mediaTypesCount > 1) {
|
|
383
|
-
return {};
|
|
384
|
-
}
|
|
385
238
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
239
|
+
multipleMediaType = false;
|
|
386
240
|
json = responseObject.content["application/json"];
|
|
387
|
-
|
|
241
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
242
|
+
multipleMediaType = true;
|
|
243
|
+
json = {};
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
388
248
|
}
|
|
389
249
|
}
|
|
390
|
-
return json;
|
|
250
|
+
return { json, multipleMediaType };
|
|
391
251
|
}
|
|
392
252
|
static convertPathToCamelCase(path) {
|
|
393
253
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -407,10 +267,10 @@ class Utils {
|
|
|
407
267
|
return undefined;
|
|
408
268
|
}
|
|
409
269
|
}
|
|
410
|
-
static
|
|
270
|
+
static resolveEnv(str) {
|
|
411
271
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
412
|
-
let matches = placeHolderReg.exec(
|
|
413
|
-
let
|
|
272
|
+
let matches = placeHolderReg.exec(str);
|
|
273
|
+
let newStr = str;
|
|
414
274
|
while (matches != null) {
|
|
415
275
|
const envVar = matches[1];
|
|
416
276
|
const envVal = process.env[envVar];
|
|
@@ -418,17 +278,17 @@ class Utils {
|
|
|
418
278
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
419
279
|
}
|
|
420
280
|
else {
|
|
421
|
-
|
|
281
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
422
282
|
}
|
|
423
|
-
matches = placeHolderReg.exec(
|
|
283
|
+
matches = placeHolderReg.exec(str);
|
|
424
284
|
}
|
|
425
|
-
return
|
|
285
|
+
return newStr;
|
|
426
286
|
}
|
|
427
287
|
static checkServerUrl(servers) {
|
|
428
288
|
const errors = [];
|
|
429
289
|
let serverUrl;
|
|
430
290
|
try {
|
|
431
|
-
serverUrl = Utils.
|
|
291
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
432
292
|
}
|
|
433
293
|
catch (err) {
|
|
434
294
|
errors.push({
|
|
@@ -458,7 +318,8 @@ class Utils {
|
|
|
458
318
|
}
|
|
459
319
|
return errors;
|
|
460
320
|
}
|
|
461
|
-
static validateServer(spec,
|
|
321
|
+
static validateServer(spec, options) {
|
|
322
|
+
var _a;
|
|
462
323
|
const errors = [];
|
|
463
324
|
let hasTopLevelServers = false;
|
|
464
325
|
let hasPathLevelServers = false;
|
|
@@ -479,7 +340,7 @@ class Utils {
|
|
|
479
340
|
}
|
|
480
341
|
for (const method in methods) {
|
|
481
342
|
const operationObject = methods[method];
|
|
482
|
-
if (
|
|
343
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
483
344
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
484
345
|
hasOperationLevelServers = true;
|
|
485
346
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -522,6 +383,7 @@ class Utils {
|
|
|
522
383
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
523
384
|
}
|
|
524
385
|
if (isRequired && schema.default === undefined) {
|
|
386
|
+
parameter.isRequired = true;
|
|
525
387
|
requiredParams.push(parameter);
|
|
526
388
|
}
|
|
527
389
|
else {
|
|
@@ -566,7 +428,7 @@ class Utils {
|
|
|
566
428
|
param.value = schema.default;
|
|
567
429
|
}
|
|
568
430
|
}
|
|
569
|
-
static parseApiInfo(operationItem,
|
|
431
|
+
static parseApiInfo(operationItem, options) {
|
|
570
432
|
var _a, _b;
|
|
571
433
|
const requiredParams = [];
|
|
572
434
|
const optionalParams = [];
|
|
@@ -580,11 +442,12 @@ class Utils {
|
|
|
580
442
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
581
443
|
};
|
|
582
444
|
const schema = param.schema;
|
|
583
|
-
if (allowMultipleParameters && schema) {
|
|
445
|
+
if (options.allowMultipleParameters && schema) {
|
|
584
446
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
585
447
|
}
|
|
586
448
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
587
449
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
450
|
+
parameter.isRequired = true;
|
|
588
451
|
requiredParams.push(parameter);
|
|
589
452
|
}
|
|
590
453
|
else {
|
|
@@ -598,19 +461,13 @@ class Utils {
|
|
|
598
461
|
const requestJson = requestBody.content["application/json"];
|
|
599
462
|
if (Object.keys(requestJson).length !== 0) {
|
|
600
463
|
const schema = requestJson.schema;
|
|
601
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
464
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
602
465
|
requiredParams.push(...requiredP);
|
|
603
466
|
optionalParams.push(...optionalP);
|
|
604
467
|
}
|
|
605
468
|
}
|
|
606
469
|
const operationId = operationItem.operationId;
|
|
607
|
-
const parameters = [];
|
|
608
|
-
if (requiredParams.length !== 0) {
|
|
609
|
-
parameters.push(...requiredParams);
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
parameters.push(optionalParams[0]);
|
|
613
|
-
}
|
|
470
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
614
471
|
const command = {
|
|
615
472
|
context: ["compose"],
|
|
616
473
|
type: "query",
|
|
@@ -619,32 +476,9 @@ class Utils {
|
|
|
619
476
|
parameters: parameters,
|
|
620
477
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
621
478
|
};
|
|
622
|
-
|
|
623
|
-
if (requiredParams.length === 0 && optionalParams.length > 1) {
|
|
624
|
-
warning = {
|
|
625
|
-
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
626
|
-
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
|
|
627
|
-
data: operationId,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
return [command, warning];
|
|
479
|
+
return command;
|
|
631
480
|
}
|
|
632
|
-
static
|
|
633
|
-
const paths = spec.paths;
|
|
634
|
-
const result = {};
|
|
635
|
-
for (const path in paths) {
|
|
636
|
-
const methods = paths[path];
|
|
637
|
-
for (const method in methods) {
|
|
638
|
-
// For developer preview, only support GET operation with only 1 parameter without auth
|
|
639
|
-
if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
|
|
640
|
-
const operationObject = methods[method];
|
|
641
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return result;
|
|
646
|
-
}
|
|
647
|
-
static validateSpec(spec, parser, isSwaggerFile, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
481
|
+
static validateSpec(spec, parser, apiMap, isSwaggerFile, options) {
|
|
648
482
|
const errors = [];
|
|
649
483
|
const warnings = [];
|
|
650
484
|
if (isSwaggerFile) {
|
|
@@ -653,8 +487,7 @@ class Utils {
|
|
|
653
487
|
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
654
488
|
});
|
|
655
489
|
}
|
|
656
|
-
|
|
657
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
|
|
490
|
+
const serverErrors = Utils.validateServer(spec, options);
|
|
658
491
|
errors.push(...serverErrors);
|
|
659
492
|
// Remote reference not supported
|
|
660
493
|
const refPaths = parser.$refs.paths();
|
|
@@ -667,8 +500,8 @@ class Utils {
|
|
|
667
500
|
});
|
|
668
501
|
}
|
|
669
502
|
// No supported API
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
503
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
504
|
+
if (validAPIs.length === 0) {
|
|
672
505
|
errors.push({
|
|
673
506
|
type: ErrorType.NoSupportedApi,
|
|
674
507
|
content: ConstantString.NoSupportedApi,
|
|
@@ -677,8 +510,8 @@ class Utils {
|
|
|
677
510
|
// OperationId missing
|
|
678
511
|
const apisMissingOperationId = [];
|
|
679
512
|
for (const key in apiMap) {
|
|
680
|
-
const
|
|
681
|
-
if (!
|
|
513
|
+
const { operation } = apiMap[key];
|
|
514
|
+
if (!operation.operationId) {
|
|
682
515
|
apisMissingOperationId.push(key);
|
|
683
516
|
}
|
|
684
517
|
}
|
|
@@ -719,30 +552,434 @@ class Utils {
|
|
|
719
552
|
}
|
|
720
553
|
return safeRegistrationIdEnvName;
|
|
721
554
|
}
|
|
555
|
+
static getAllAPICount(spec) {
|
|
556
|
+
let count = 0;
|
|
557
|
+
const paths = spec.paths;
|
|
558
|
+
for (const path in paths) {
|
|
559
|
+
const methods = paths[path];
|
|
560
|
+
for (const method in methods) {
|
|
561
|
+
if (ConstantString.AllOperationMethods.includes(method)) {
|
|
562
|
+
count++;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return count;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Copyright (c) Microsoft Corporation.
|
|
571
|
+
class Validator {
|
|
572
|
+
validateMethodAndPath(method, path) {
|
|
573
|
+
const result = { isValid: true, reason: [] };
|
|
574
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
575
|
+
result.isValid = false;
|
|
576
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
const pathObj = this.spec.paths[path];
|
|
580
|
+
if (!pathObj || !pathObj[method]) {
|
|
581
|
+
result.isValid = false;
|
|
582
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
validateResponse(method, path) {
|
|
588
|
+
const result = { isValid: true, reason: [] };
|
|
589
|
+
const operationObject = this.spec.paths[path][method];
|
|
590
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
591
|
+
// only support response body only contains “application/json” content type
|
|
592
|
+
if (multipleMediaType) {
|
|
593
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
594
|
+
}
|
|
595
|
+
else if (Object.keys(json).length === 0) {
|
|
596
|
+
// response body should not be empty
|
|
597
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
598
|
+
}
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
validateServer(method, path) {
|
|
602
|
+
const pathObj = this.spec.paths[path];
|
|
603
|
+
const result = { isValid: true, reason: [] };
|
|
604
|
+
const operationObject = pathObj[method];
|
|
605
|
+
const rootServer = this.spec.servers && this.spec.servers[0];
|
|
606
|
+
const methodServer = this.spec.paths[path].servers && this.spec.paths[path].servers[0];
|
|
607
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
608
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
609
|
+
if (!serverUrl) {
|
|
610
|
+
// should contain server URL
|
|
611
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// server url should be absolute url with https protocol
|
|
615
|
+
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
616
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
validateAuth(method, path) {
|
|
621
|
+
const pathObj = this.spec.paths[path];
|
|
622
|
+
const operationObject = pathObj[method];
|
|
623
|
+
const securities = operationObject.security;
|
|
624
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
625
|
+
if (authSchemeArray.length === 0) {
|
|
626
|
+
return { isValid: true, reason: [] };
|
|
627
|
+
}
|
|
628
|
+
if (this.options.allowAPIKeyAuth ||
|
|
629
|
+
this.options.allowOauth2 ||
|
|
630
|
+
this.options.allowBearerTokenAuth) {
|
|
631
|
+
// Currently we don't support multiple auth in one operation
|
|
632
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
633
|
+
return {
|
|
634
|
+
isValid: false,
|
|
635
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
for (const auths of authSchemeArray) {
|
|
639
|
+
if (auths.length === 1) {
|
|
640
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
641
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
642
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
643
|
+
return { isValid: true, reason: [] };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
649
|
+
}
|
|
650
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
651
|
+
var _a;
|
|
652
|
+
const paramResult = {
|
|
653
|
+
requiredNum: 0,
|
|
654
|
+
optionalNum: 0,
|
|
655
|
+
isValid: true,
|
|
656
|
+
reason: [],
|
|
657
|
+
};
|
|
658
|
+
if (Object.keys(schema).length === 0) {
|
|
659
|
+
return paramResult;
|
|
660
|
+
}
|
|
661
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
662
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
663
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
664
|
+
paramResult.isValid = false;
|
|
665
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
666
|
+
return paramResult;
|
|
667
|
+
}
|
|
668
|
+
if (schema.type === "string" ||
|
|
669
|
+
schema.type === "integer" ||
|
|
670
|
+
schema.type === "boolean" ||
|
|
671
|
+
schema.type === "number") {
|
|
672
|
+
if (isRequiredWithoutDefault) {
|
|
673
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else if (schema.type === "object") {
|
|
680
|
+
const { properties } = schema;
|
|
681
|
+
for (const property in properties) {
|
|
682
|
+
let isRequired = false;
|
|
683
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
684
|
+
isRequired = true;
|
|
685
|
+
}
|
|
686
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
687
|
+
paramResult.requiredNum += result.requiredNum;
|
|
688
|
+
paramResult.optionalNum += result.optionalNum;
|
|
689
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
690
|
+
paramResult.reason.push(...result.reason);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
695
|
+
paramResult.isValid = false;
|
|
696
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return paramResult;
|
|
700
|
+
}
|
|
701
|
+
checkParamSchema(paramObject) {
|
|
702
|
+
const paramResult = {
|
|
703
|
+
requiredNum: 0,
|
|
704
|
+
optionalNum: 0,
|
|
705
|
+
isValid: true,
|
|
706
|
+
reason: [],
|
|
707
|
+
};
|
|
708
|
+
if (!paramObject) {
|
|
709
|
+
return paramResult;
|
|
710
|
+
}
|
|
711
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
712
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
713
|
+
const param = paramObject[i];
|
|
714
|
+
const schema = param.schema;
|
|
715
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
716
|
+
paramResult.isValid = false;
|
|
717
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
721
|
+
if (isCopilot) {
|
|
722
|
+
if (isRequiredWithoutDefault) {
|
|
723
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
727
|
+
}
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
731
|
+
if (isRequiredWithoutDefault) {
|
|
732
|
+
paramResult.isValid = false;
|
|
733
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (schema.type !== "boolean" &&
|
|
738
|
+
schema.type !== "string" &&
|
|
739
|
+
schema.type !== "number" &&
|
|
740
|
+
schema.type !== "integer") {
|
|
741
|
+
if (isRequiredWithoutDefault) {
|
|
742
|
+
paramResult.isValid = false;
|
|
743
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
744
|
+
}
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (param.in === "query" || param.in === "path") {
|
|
748
|
+
if (isRequiredWithoutDefault) {
|
|
749
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return paramResult;
|
|
757
|
+
}
|
|
758
|
+
hasNestedObjectInSchema(schema) {
|
|
759
|
+
if (schema.type === "object") {
|
|
760
|
+
for (const property in schema.properties) {
|
|
761
|
+
const nestedSchema = schema.properties[property];
|
|
762
|
+
if (nestedSchema.type === "object") {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Copyright (c) Microsoft Corporation.
|
|
772
|
+
class CopilotValidator extends Validator {
|
|
773
|
+
constructor(spec, options) {
|
|
774
|
+
super();
|
|
775
|
+
this.projectType = ProjectType.Copilot;
|
|
776
|
+
this.options = options;
|
|
777
|
+
this.spec = spec;
|
|
778
|
+
}
|
|
779
|
+
validateAPI(method, path) {
|
|
780
|
+
const result = { isValid: true, reason: [] };
|
|
781
|
+
method = method.toLocaleLowerCase();
|
|
782
|
+
// validate method and path
|
|
783
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
784
|
+
if (!methodAndPathResult.isValid) {
|
|
785
|
+
return methodAndPathResult;
|
|
786
|
+
}
|
|
787
|
+
const operationObject = this.spec.paths[path][method];
|
|
788
|
+
// validate auth
|
|
789
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
790
|
+
result.reason.push(...authCheckResult.reason);
|
|
791
|
+
// validate operationId
|
|
792
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
793
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
794
|
+
}
|
|
795
|
+
// validate server
|
|
796
|
+
const validateServerResult = this.validateServer(method, path);
|
|
797
|
+
result.reason.push(...validateServerResult.reason);
|
|
798
|
+
// validate response
|
|
799
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
800
|
+
result.reason.push(...validateResponseResult.reason);
|
|
801
|
+
// validate requestBody
|
|
802
|
+
const requestBody = operationObject.requestBody;
|
|
803
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
804
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
805
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
806
|
+
}
|
|
807
|
+
if (requestJsonBody) {
|
|
808
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
809
|
+
if (requestBodySchema.type !== "object") {
|
|
810
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
811
|
+
}
|
|
812
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
813
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
814
|
+
}
|
|
815
|
+
// validate parameters
|
|
816
|
+
const paramObject = operationObject.parameters;
|
|
817
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
818
|
+
result.reason.push(...paramResult.reason);
|
|
819
|
+
if (result.reason.length > 0) {
|
|
820
|
+
result.isValid = false;
|
|
821
|
+
}
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Copyright (c) Microsoft Corporation.
|
|
827
|
+
class SMEValidator extends Validator {
|
|
828
|
+
constructor(spec, options) {
|
|
829
|
+
super();
|
|
830
|
+
this.projectType = ProjectType.SME;
|
|
831
|
+
this.options = options;
|
|
832
|
+
this.spec = spec;
|
|
833
|
+
}
|
|
834
|
+
validateAPI(method, path) {
|
|
835
|
+
const result = { isValid: true, reason: [] };
|
|
836
|
+
method = method.toLocaleLowerCase();
|
|
837
|
+
// validate method and path
|
|
838
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
839
|
+
if (!methodAndPathResult.isValid) {
|
|
840
|
+
return methodAndPathResult;
|
|
841
|
+
}
|
|
842
|
+
const operationObject = this.spec.paths[path][method];
|
|
843
|
+
// validate auth
|
|
844
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
845
|
+
result.reason.push(...authCheckResult.reason);
|
|
846
|
+
// validate operationId
|
|
847
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
848
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
849
|
+
}
|
|
850
|
+
// validate server
|
|
851
|
+
const validateServerResult = this.validateServer(method, path);
|
|
852
|
+
result.reason.push(...validateServerResult.reason);
|
|
853
|
+
// validate response
|
|
854
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
855
|
+
result.reason.push(...validateResponseResult.reason);
|
|
856
|
+
let postBodyResult = {
|
|
857
|
+
requiredNum: 0,
|
|
858
|
+
optionalNum: 0,
|
|
859
|
+
isValid: true,
|
|
860
|
+
reason: [],
|
|
861
|
+
};
|
|
862
|
+
// validate requestBody
|
|
863
|
+
const requestBody = operationObject.requestBody;
|
|
864
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
865
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
866
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
867
|
+
}
|
|
868
|
+
if (requestJsonBody) {
|
|
869
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
870
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
871
|
+
result.reason.push(...postBodyResult.reason);
|
|
872
|
+
}
|
|
873
|
+
// validate parameters
|
|
874
|
+
const paramObject = operationObject.parameters;
|
|
875
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
876
|
+
result.reason.push(...paramResult.reason);
|
|
877
|
+
// validate total parameters count
|
|
878
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
879
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
880
|
+
result.reason.push(...paramCountResult.reason);
|
|
881
|
+
}
|
|
882
|
+
if (result.reason.length > 0) {
|
|
883
|
+
result.isValid = false;
|
|
884
|
+
}
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
888
|
+
const result = { isValid: true, reason: [] };
|
|
889
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
890
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
891
|
+
if (totalRequiredParams > 1) {
|
|
892
|
+
if (!this.options.allowMultipleParameters ||
|
|
893
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
894
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
else if (totalParams === 0) {
|
|
898
|
+
result.reason.push(ErrorType.NoParameter);
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
904
|
+
|
|
905
|
+
// Copyright (c) Microsoft Corporation.
|
|
906
|
+
class TeamsAIValidator extends Validator {
|
|
907
|
+
constructor(spec, options) {
|
|
908
|
+
super();
|
|
909
|
+
this.projectType = ProjectType.TeamsAi;
|
|
910
|
+
this.options = options;
|
|
911
|
+
this.spec = spec;
|
|
912
|
+
}
|
|
913
|
+
validateAPI(method, path) {
|
|
914
|
+
const result = { isValid: true, reason: [] };
|
|
915
|
+
method = method.toLocaleLowerCase();
|
|
916
|
+
// validate method and path
|
|
917
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
918
|
+
if (!methodAndPathResult.isValid) {
|
|
919
|
+
return methodAndPathResult;
|
|
920
|
+
}
|
|
921
|
+
const operationObject = this.spec.paths[path][method];
|
|
922
|
+
// validate operationId
|
|
923
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
924
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
925
|
+
}
|
|
926
|
+
// validate server
|
|
927
|
+
const validateServerResult = this.validateServer(method, path);
|
|
928
|
+
result.reason.push(...validateServerResult.reason);
|
|
929
|
+
if (result.reason.length > 0) {
|
|
930
|
+
result.isValid = false;
|
|
931
|
+
}
|
|
932
|
+
return result;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
class ValidatorFactory {
|
|
937
|
+
static create(spec, options) {
|
|
938
|
+
var _a;
|
|
939
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
940
|
+
switch (type) {
|
|
941
|
+
case ProjectType.SME:
|
|
942
|
+
return new SMEValidator(spec, options);
|
|
943
|
+
case ProjectType.Copilot:
|
|
944
|
+
return new CopilotValidator(spec, options);
|
|
945
|
+
case ProjectType.TeamsAi:
|
|
946
|
+
return new TeamsAIValidator(spec, options);
|
|
947
|
+
default:
|
|
948
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
722
951
|
}
|
|
723
952
|
|
|
724
953
|
// Copyright (c) Microsoft Corporation.
|
|
725
954
|
class SpecFilter {
|
|
726
|
-
static specFilter(filter, unResolveSpec, resolvedSpec,
|
|
955
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
956
|
+
var _a;
|
|
727
957
|
try {
|
|
728
958
|
const newSpec = Object.assign({}, unResolveSpec);
|
|
729
959
|
const newPaths = {};
|
|
730
960
|
for (const filterItem of filter) {
|
|
731
961
|
const [method, path] = filterItem.split(" ");
|
|
732
962
|
const methodName = method.toLowerCase();
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
963
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
964
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
965
|
+
pathObj &&
|
|
966
|
+
pathObj[methodName]) {
|
|
967
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
968
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
969
|
+
if (!validateResult.isValid) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
if (!newPaths[path]) {
|
|
973
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
974
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
975
|
+
delete newPaths[path][m];
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
979
|
+
// Add the operationId if missing
|
|
980
|
+
if (!newPaths[path][methodName].operationId) {
|
|
981
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
740
982
|
}
|
|
741
|
-
}
|
|
742
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
743
|
-
// Add the operationId if missing
|
|
744
|
-
if (!newPaths[path][methodName].operationId) {
|
|
745
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
746
983
|
}
|
|
747
984
|
}
|
|
748
985
|
newSpec.paths = newPaths;
|
|
@@ -756,46 +993,168 @@ class SpecFilter {
|
|
|
756
993
|
|
|
757
994
|
// Copyright (c) Microsoft Corporation.
|
|
758
995
|
class ManifestUpdater {
|
|
759
|
-
static async
|
|
996
|
+
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
|
|
997
|
+
const manifest = await fs.readJSON(manifestPath);
|
|
998
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
999
|
+
manifest.plugins = [
|
|
1000
|
+
{
|
|
1001
|
+
pluginFile: apiPluginRelativePath,
|
|
1002
|
+
},
|
|
1003
|
+
];
|
|
1004
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1005
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1006
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1007
|
+
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
1008
|
+
return [manifest, apiPlugin];
|
|
1009
|
+
}
|
|
1010
|
+
static updateManifestDescription(manifest, spec) {
|
|
760
1011
|
var _a, _b;
|
|
1012
|
+
manifest.description = {
|
|
1013
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1014
|
+
full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : manifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
1018
|
+
let parameter;
|
|
1019
|
+
if (schema.type === "string" ||
|
|
1020
|
+
schema.type === "boolean" ||
|
|
1021
|
+
schema.type === "integer" ||
|
|
1022
|
+
schema.type === "number" ||
|
|
1023
|
+
schema.type === "array") {
|
|
1024
|
+
parameter = schema;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
|
|
1028
|
+
}
|
|
1029
|
+
return parameter;
|
|
1030
|
+
}
|
|
1031
|
+
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
1032
|
+
var _a, _b, _c;
|
|
1033
|
+
const functions = [];
|
|
1034
|
+
const functionNames = [];
|
|
1035
|
+
const paths = spec.paths;
|
|
1036
|
+
for (const pathUrl in paths) {
|
|
1037
|
+
const pathItem = paths[pathUrl];
|
|
1038
|
+
if (pathItem) {
|
|
1039
|
+
const operations = pathItem;
|
|
1040
|
+
for (const method in operations) {
|
|
1041
|
+
if (options.allowMethods.includes(method)) {
|
|
1042
|
+
const operationItem = operations[method];
|
|
1043
|
+
if (operationItem) {
|
|
1044
|
+
const operationId = operationItem.operationId;
|
|
1045
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1046
|
+
const paramObject = operationItem.parameters;
|
|
1047
|
+
const requestBody = operationItem.requestBody;
|
|
1048
|
+
const parameters = {
|
|
1049
|
+
type: "object",
|
|
1050
|
+
properties: {},
|
|
1051
|
+
required: [],
|
|
1052
|
+
};
|
|
1053
|
+
if (paramObject) {
|
|
1054
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1055
|
+
const param = paramObject[i];
|
|
1056
|
+
const schema = param.schema;
|
|
1057
|
+
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1058
|
+
if (param.required) {
|
|
1059
|
+
parameters.required.push(param.name);
|
|
1060
|
+
}
|
|
1061
|
+
if (!parameters.properties[param.name].description) {
|
|
1062
|
+
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (requestBody) {
|
|
1067
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1068
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1069
|
+
if (requestBodySchema.type === "object") {
|
|
1070
|
+
if (requestBodySchema.required) {
|
|
1071
|
+
parameters.required.push(...requestBodySchema.required);
|
|
1072
|
+
}
|
|
1073
|
+
for (const property in requestBodySchema.properties) {
|
|
1074
|
+
const schema = requestBodySchema.properties[property];
|
|
1075
|
+
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const funcObj = {
|
|
1083
|
+
name: operationId,
|
|
1084
|
+
description: description,
|
|
1085
|
+
parameters: parameters,
|
|
1086
|
+
};
|
|
1087
|
+
functions.push(funcObj);
|
|
1088
|
+
functionNames.push(operationId);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const apiPlugin = {
|
|
1095
|
+
schema_version: "v2",
|
|
1096
|
+
name_for_human: appName,
|
|
1097
|
+
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
1098
|
+
functions: functions,
|
|
1099
|
+
runtimes: [
|
|
1100
|
+
{
|
|
1101
|
+
type: "OpenApi",
|
|
1102
|
+
auth: {
|
|
1103
|
+
type: "none", // TODO, support auth in the future
|
|
1104
|
+
},
|
|
1105
|
+
spec: {
|
|
1106
|
+
url: specRelativePath,
|
|
1107
|
+
},
|
|
1108
|
+
run_for_functions: functionNames,
|
|
1109
|
+
},
|
|
1110
|
+
],
|
|
1111
|
+
};
|
|
1112
|
+
return apiPlugin;
|
|
1113
|
+
}
|
|
1114
|
+
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
761
1115
|
try {
|
|
762
1116
|
const originalManifest = await fs.readJSON(manifestPath);
|
|
763
1117
|
const updatedPart = {};
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
commands
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1118
|
+
updatedPart.composeExtensions = [];
|
|
1119
|
+
let warnings = [];
|
|
1120
|
+
if (options.projectType === ProjectType.SME) {
|
|
1121
|
+
const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1122
|
+
const commands = updateResult[0];
|
|
1123
|
+
warnings = updateResult[1];
|
|
1124
|
+
const composeExtension = {
|
|
1125
|
+
composeExtensionType: "apiBased",
|
|
1126
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1127
|
+
commands: commands,
|
|
1128
|
+
};
|
|
1129
|
+
if (authInfo) {
|
|
1130
|
+
const auth = authInfo.authScheme;
|
|
1131
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1132
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1133
|
+
composeExtension.authorization = {
|
|
1134
|
+
authType: "apiSecretServiceAuth",
|
|
1135
|
+
apiSecretServiceAuthConfiguration: {
|
|
1136
|
+
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1137
|
+
},
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1141
|
+
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1142
|
+
composeExtension.authorization = {
|
|
1143
|
+
authType: "oAuth2.0",
|
|
1144
|
+
oAuthConfiguration: {
|
|
1145
|
+
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
updatedPart.webApplicationInfo = {
|
|
1149
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1150
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
792
1153
|
}
|
|
1154
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
793
1155
|
}
|
|
794
|
-
updatedPart.description =
|
|
795
|
-
|
|
796
|
-
full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : originalManifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
|
|
797
|
-
};
|
|
798
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
1156
|
+
updatedPart.description = originalManifest.description;
|
|
1157
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
799
1158
|
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
800
1159
|
return [updatedManifest, warnings];
|
|
801
1160
|
}
|
|
@@ -803,7 +1162,8 @@ class ManifestUpdater {
|
|
|
803
1162
|
throw new SpecParserError(err.toString(), ErrorType.UpdateManifestFailed);
|
|
804
1163
|
}
|
|
805
1164
|
}
|
|
806
|
-
static async generateCommands(spec,
|
|
1165
|
+
static async generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1166
|
+
var _a;
|
|
807
1167
|
const paths = spec.paths;
|
|
808
1168
|
const commands = [];
|
|
809
1169
|
const warnings = [];
|
|
@@ -814,16 +1174,28 @@ class ManifestUpdater {
|
|
|
814
1174
|
const operations = pathItem;
|
|
815
1175
|
// Currently only support GET and POST method
|
|
816
1176
|
for (const method in operations) {
|
|
817
|
-
if (
|
|
1177
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
818
1178
|
const operationItem = operations[method];
|
|
819
1179
|
if (operationItem) {
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1180
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1181
|
+
if (command.parameters &&
|
|
1182
|
+
command.parameters.length >= 1 &&
|
|
1183
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1184
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1185
|
+
}
|
|
1186
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1187
|
+
command.parameters = [command.parameters[0]];
|
|
1188
|
+
warnings.push({
|
|
1189
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1190
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1191
|
+
data: command.id,
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
if (adaptiveCardFolder) {
|
|
1195
|
+
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1196
|
+
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1197
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1198
|
+
: "";
|
|
827
1199
|
}
|
|
828
1200
|
commands.push(command);
|
|
829
1201
|
}
|
|
@@ -838,13 +1210,22 @@ class ManifestUpdater {
|
|
|
838
1210
|
const relativePath = path.relative(path.dirname(from), to);
|
|
839
1211
|
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
840
1212
|
}
|
|
1213
|
+
static removeEnvs(str) {
|
|
1214
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1215
|
+
const matches = placeHolderReg.exec(str);
|
|
1216
|
+
let newStr = str;
|
|
1217
|
+
if (matches != null) {
|
|
1218
|
+
newStr = newStr.replace(matches[0], "");
|
|
1219
|
+
}
|
|
1220
|
+
return newStr;
|
|
1221
|
+
}
|
|
841
1222
|
}
|
|
842
1223
|
|
|
843
1224
|
// Copyright (c) Microsoft Corporation.
|
|
844
1225
|
class AdaptiveCardGenerator {
|
|
845
1226
|
static generateAdaptiveCard(operationItem) {
|
|
846
1227
|
try {
|
|
847
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1228
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
848
1229
|
let cardBody = [];
|
|
849
1230
|
let schema = json.schema;
|
|
850
1231
|
let jsonPath = "$";
|
|
@@ -1104,8 +1485,11 @@ class SpecParser {
|
|
|
1104
1485
|
allowMissingId: true,
|
|
1105
1486
|
allowSwagger: true,
|
|
1106
1487
|
allowAPIKeyAuth: false,
|
|
1488
|
+
allowBearerTokenAuth: false,
|
|
1107
1489
|
allowMultipleParameters: false,
|
|
1108
1490
|
allowOauth2: false,
|
|
1491
|
+
allowMethods: ["get", "post"],
|
|
1492
|
+
projectType: ProjectType.SME,
|
|
1109
1493
|
};
|
|
1110
1494
|
this.pathOrSpec = pathOrDoc;
|
|
1111
1495
|
this.parser = new SwaggerParser();
|
|
@@ -1138,7 +1522,24 @@ class SpecParser {
|
|
|
1138
1522
|
],
|
|
1139
1523
|
};
|
|
1140
1524
|
}
|
|
1141
|
-
|
|
1525
|
+
if (this.options.projectType === ProjectType.SME ||
|
|
1526
|
+
this.options.projectType === ProjectType.Copilot) {
|
|
1527
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
1528
|
+
return {
|
|
1529
|
+
status: ValidationStatus.Error,
|
|
1530
|
+
warnings: [],
|
|
1531
|
+
errors: [
|
|
1532
|
+
{
|
|
1533
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
1534
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
1535
|
+
data: this.spec.openapi,
|
|
1536
|
+
},
|
|
1537
|
+
],
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const apiMap = this.getAPIs(this.spec);
|
|
1542
|
+
return Utils.validateSpec(this.spec, this.parser, apiMap, !!this.isSwaggerFile, this.options);
|
|
1142
1543
|
}
|
|
1143
1544
|
catch (err) {
|
|
1144
1545
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1158,16 +1559,22 @@ class SpecParser {
|
|
|
1158
1559
|
try {
|
|
1159
1560
|
await this.loadSpec();
|
|
1160
1561
|
const spec = this.spec;
|
|
1161
|
-
const apiMap = this.
|
|
1162
|
-
const result =
|
|
1562
|
+
const apiMap = this.getAPIs(spec);
|
|
1563
|
+
const result = {
|
|
1564
|
+
APIs: [],
|
|
1565
|
+
allAPICount: 0,
|
|
1566
|
+
validAPICount: 0,
|
|
1567
|
+
};
|
|
1163
1568
|
for (const apiKey in apiMap) {
|
|
1569
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1570
|
+
const [method, path] = apiKey.split(" ");
|
|
1164
1571
|
const apiResult = {
|
|
1165
1572
|
api: "",
|
|
1166
1573
|
server: "",
|
|
1167
1574
|
operationId: "",
|
|
1575
|
+
isValid: isValid,
|
|
1576
|
+
reason: reason,
|
|
1168
1577
|
};
|
|
1169
|
-
const [method, path] = apiKey.split(" ");
|
|
1170
|
-
const operation = apiMap[apiKey];
|
|
1171
1578
|
const rootServer = spec.servers && spec.servers[0];
|
|
1172
1579
|
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
1173
1580
|
const operationServer = operation.servers && operation.servers[0];
|
|
@@ -1175,7 +1582,7 @@ class SpecParser {
|
|
|
1175
1582
|
if (!serverUrl) {
|
|
1176
1583
|
throw new SpecParserError(ConstantString.NoServerInformation, ErrorType.NoServerInformation);
|
|
1177
1584
|
}
|
|
1178
|
-
apiResult.server = Utils.
|
|
1585
|
+
apiResult.server = Utils.resolveEnv(serverUrl.url);
|
|
1179
1586
|
let operationId = operation.operationId;
|
|
1180
1587
|
if (!operationId) {
|
|
1181
1588
|
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
@@ -1184,13 +1591,15 @@ class SpecParser {
|
|
|
1184
1591
|
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1185
1592
|
for (const auths of authArray) {
|
|
1186
1593
|
if (auths.length === 1) {
|
|
1187
|
-
apiResult.auth = auths[0]
|
|
1594
|
+
apiResult.auth = auths[0];
|
|
1188
1595
|
break;
|
|
1189
1596
|
}
|
|
1190
1597
|
}
|
|
1191
1598
|
apiResult.api = apiKey;
|
|
1192
|
-
result.push(apiResult);
|
|
1599
|
+
result.APIs.push(apiResult);
|
|
1193
1600
|
}
|
|
1601
|
+
result.allAPICount = result.APIs.length;
|
|
1602
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1194
1603
|
return result;
|
|
1195
1604
|
}
|
|
1196
1605
|
catch (err) {
|
|
@@ -1200,48 +1609,108 @@ class SpecParser {
|
|
|
1200
1609
|
throw new SpecParserError(err.toString(), ErrorType.ListFailed);
|
|
1201
1610
|
}
|
|
1202
1611
|
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Generate specs according to the filters.
|
|
1614
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1615
|
+
*/
|
|
1616
|
+
async getFilteredSpecs(filter, signal) {
|
|
1617
|
+
try {
|
|
1618
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1619
|
+
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1620
|
+
}
|
|
1621
|
+
await this.loadSpec();
|
|
1622
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1623
|
+
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1624
|
+
}
|
|
1625
|
+
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
|
|
1626
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1627
|
+
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1628
|
+
}
|
|
1629
|
+
const newSpec = (await this.parser.dereference(newUnResolvedSpec));
|
|
1630
|
+
return [newUnResolvedSpec, newSpec];
|
|
1631
|
+
}
|
|
1632
|
+
catch (err) {
|
|
1633
|
+
if (err instanceof SpecParserError) {
|
|
1634
|
+
throw err;
|
|
1635
|
+
}
|
|
1636
|
+
throw new SpecParserError(err.toString(), ErrorType.GetSpecFailed);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1203
1639
|
/**
|
|
1204
1640
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1205
1641
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1206
1642
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1207
1643
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1208
|
-
* @param
|
|
1644
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1209
1645
|
*/
|
|
1210
|
-
async
|
|
1646
|
+
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1211
1647
|
const result = {
|
|
1212
1648
|
allSuccess: true,
|
|
1213
1649
|
warnings: [],
|
|
1214
1650
|
};
|
|
1215
1651
|
try {
|
|
1216
|
-
|
|
1217
|
-
|
|
1652
|
+
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1653
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1654
|
+
const newSpec = newSpecs[1];
|
|
1655
|
+
let resultStr;
|
|
1656
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1657
|
+
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1218
1658
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1659
|
+
else {
|
|
1660
|
+
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1222
1661
|
}
|
|
1223
|
-
|
|
1662
|
+
await fs.outputFile(outputSpecPath, resultStr);
|
|
1224
1663
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1225
1664
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1226
1665
|
}
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1666
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
|
|
1667
|
+
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1668
|
+
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1669
|
+
}
|
|
1670
|
+
catch (err) {
|
|
1671
|
+
if (err instanceof SpecParserError) {
|
|
1672
|
+
throw err;
|
|
1673
|
+
}
|
|
1674
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateFailed);
|
|
1675
|
+
}
|
|
1676
|
+
return result;
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1680
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1681
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1682
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1683
|
+
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1684
|
+
*/
|
|
1685
|
+
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
1686
|
+
const result = {
|
|
1687
|
+
allSuccess: true,
|
|
1688
|
+
warnings: [],
|
|
1689
|
+
};
|
|
1690
|
+
try {
|
|
1691
|
+
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1692
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1693
|
+
const newSpec = newSpecs[1];
|
|
1694
|
+
let hasMultipleAuth = false;
|
|
1695
|
+
let authInfo = undefined;
|
|
1230
1696
|
for (const url in newSpec.paths) {
|
|
1231
1697
|
for (const method in newSpec.paths[url]) {
|
|
1232
1698
|
const operation = newSpec.paths[url][method];
|
|
1233
1699
|
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1234
1700
|
if (authArray && authArray.length > 0) {
|
|
1235
|
-
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1701
|
+
const currentAuth = authArray[0][0];
|
|
1702
|
+
if (!authInfo) {
|
|
1703
|
+
authInfo = authArray[0][0];
|
|
1704
|
+
}
|
|
1705
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
1706
|
+
hasMultipleAuth = true;
|
|
1238
1707
|
break;
|
|
1239
1708
|
}
|
|
1240
1709
|
}
|
|
1241
1710
|
}
|
|
1242
1711
|
}
|
|
1243
|
-
if (
|
|
1244
|
-
throw new SpecParserError(ConstantString.
|
|
1712
|
+
if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
|
|
1713
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
1245
1714
|
}
|
|
1246
1715
|
let resultStr;
|
|
1247
1716
|
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
@@ -1251,26 +1720,28 @@ class SpecParser {
|
|
|
1251
1720
|
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1252
1721
|
}
|
|
1253
1722
|
await fs.outputFile(outputSpecPath, resultStr);
|
|
1254
|
-
|
|
1255
|
-
for (const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1723
|
+
if (adaptiveCardFolder) {
|
|
1724
|
+
for (const url in newSpec.paths) {
|
|
1725
|
+
for (const method in newSpec.paths[url]) {
|
|
1726
|
+
// paths object may contain description/summary which is not a http method, so we need to check if it is a operation object
|
|
1727
|
+
if (this.options.allowMethods.includes(method)) {
|
|
1728
|
+
const operation = newSpec.paths[url][method];
|
|
1729
|
+
try {
|
|
1730
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
|
|
1731
|
+
const fileName = path.join(adaptiveCardFolder, `${operation.operationId}.json`);
|
|
1732
|
+
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
|
|
1733
|
+
await fs.outputJSON(fileName, wrappedCard, { spaces: 2 });
|
|
1734
|
+
const dataFileName = path.join(adaptiveCardFolder, `${operation.operationId}.data.json`);
|
|
1735
|
+
await fs.outputJSON(dataFileName, {}, { spaces: 2 });
|
|
1736
|
+
}
|
|
1737
|
+
catch (err) {
|
|
1738
|
+
result.allSuccess = false;
|
|
1739
|
+
result.warnings.push({
|
|
1740
|
+
type: WarningType.GenerateCardFailed,
|
|
1741
|
+
content: err.toString(),
|
|
1742
|
+
data: operation.operationId,
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1274
1745
|
}
|
|
1275
1746
|
}
|
|
1276
1747
|
}
|
|
@@ -1278,8 +1749,7 @@ class SpecParser {
|
|
|
1278
1749
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1279
1750
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1280
1751
|
}
|
|
1281
|
-
const
|
|
1282
|
-
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, newSpec, this.options.allowMultipleParameters, auth);
|
|
1752
|
+
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1283
1753
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1284
1754
|
result.warnings.push(...warnings);
|
|
1285
1755
|
}
|
|
@@ -1304,15 +1774,36 @@ class SpecParser {
|
|
|
1304
1774
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1305
1775
|
}
|
|
1306
1776
|
}
|
|
1307
|
-
|
|
1777
|
+
getAPIs(spec) {
|
|
1308
1778
|
if (this.apiMap !== undefined) {
|
|
1309
1779
|
return this.apiMap;
|
|
1310
1780
|
}
|
|
1311
|
-
const result =
|
|
1781
|
+
const result = this.listAPIs(spec, this.options);
|
|
1312
1782
|
this.apiMap = result;
|
|
1313
1783
|
return result;
|
|
1314
1784
|
}
|
|
1785
|
+
listAPIs(spec, options) {
|
|
1786
|
+
var _a;
|
|
1787
|
+
const paths = spec.paths;
|
|
1788
|
+
const result = {};
|
|
1789
|
+
for (const path in paths) {
|
|
1790
|
+
const methods = paths[path];
|
|
1791
|
+
for (const method in methods) {
|
|
1792
|
+
const operationObject = methods[method];
|
|
1793
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1794
|
+
const validator = ValidatorFactory.create(spec, options);
|
|
1795
|
+
const validateResult = validator.validateAPI(method, path);
|
|
1796
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
1797
|
+
operation: operationObject,
|
|
1798
|
+
isValid: validateResult.isValid,
|
|
1799
|
+
reason: validateResult.reason,
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return result;
|
|
1805
|
+
}
|
|
1315
1806
|
}
|
|
1316
1807
|
|
|
1317
|
-
export { ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1808
|
+
export { AdaptiveCardGenerator, ConstantString, ErrorType, ProjectType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1318
1809
|
//# sourceMappingURL=index.esm2017.mjs.map
|