@microsoft/m365-spec-parser 0.1.1-alpha.cf377d39f.0 → 0.1.1-alpha.ded43fb2d.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 +656 -302
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1173 -496
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +658 -302
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1187 -502
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +7 -1
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +106 -18
- package/dist/src/manifestUpdater.d.ts +11 -4
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +12 -3
- package/dist/src/specParser.d.ts +14 -5
- package/dist/src/utils.d.ts +19 -34
- package/package.json +4 -4
package/dist/index.node.cjs.js
CHANGED
|
@@ -61,7 +61,8 @@ exports.ErrorType = void 0;
|
|
|
61
61
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
62
62
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
63
63
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
64
|
-
ErrorType["
|
|
64
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
65
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
65
66
|
ErrorType["ListFailed"] = "list-failed";
|
|
66
67
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
67
68
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -70,6 +71,21 @@ exports.ErrorType = void 0;
|
|
|
70
71
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
71
72
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
72
73
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
74
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
75
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
76
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
77
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
78
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
79
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
80
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
81
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
82
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
83
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
84
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
85
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
86
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
87
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
88
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
73
89
|
ErrorType["Cancelled"] = "cancelled";
|
|
74
90
|
ErrorType["Unknown"] = "unknown";
|
|
75
91
|
})(exports.ErrorType || (exports.ErrorType = {}));
|
|
@@ -92,7 +108,13 @@ exports.ValidationStatus = void 0;
|
|
|
92
108
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
93
109
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
94
110
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
95
|
-
})(exports.ValidationStatus || (exports.ValidationStatus = {}));
|
|
111
|
+
})(exports.ValidationStatus || (exports.ValidationStatus = {}));
|
|
112
|
+
exports.ProjectType = void 0;
|
|
113
|
+
(function (ProjectType) {
|
|
114
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
115
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
116
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
117
|
+
})(exports.ProjectType || (exports.ProjectType = {}));
|
|
96
118
|
|
|
97
119
|
// Copyright (c) Microsoft Corporation.
|
|
98
120
|
class ConstantString {
|
|
@@ -111,7 +133,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
111
133
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
112
134
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
113
135
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
114
|
-
ConstantString.
|
|
136
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
137
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
138
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
115
139
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
116
140
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
117
141
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -121,8 +145,10 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
121
145
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
122
146
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
123
147
|
ConstantString.TextBlockType = "TextBlock";
|
|
148
|
+
ConstantString.ImageType = "Image";
|
|
124
149
|
ConstantString.ContainerType = "Container";
|
|
125
150
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
151
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
126
152
|
ConstantString.ResponseCodeFor20X = [
|
|
127
153
|
"200",
|
|
128
154
|
"201",
|
|
@@ -182,7 +208,9 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
182
208
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
183
209
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
184
210
|
ConstantString.CommandTitleMaxLens = 32;
|
|
185
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
211
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
212
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
213
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
186
214
|
|
|
187
215
|
// Copyright (c) Microsoft Corporation.
|
|
188
216
|
class SpecParserError extends Error {
|
|
@@ -194,201 +222,30 @@ class SpecParserError extends Error {
|
|
|
194
222
|
|
|
195
223
|
// Copyright (c) Microsoft Corporation.
|
|
196
224
|
class Utils {
|
|
197
|
-
static
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
203
|
-
if (!paramObject) {
|
|
204
|
-
return paramResult;
|
|
205
|
-
}
|
|
206
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
207
|
-
const param = paramObject[i];
|
|
208
|
-
const schema = param.schema;
|
|
209
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
210
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
211
|
-
if (isRequiredWithoutDefault) {
|
|
212
|
-
paramResult.isValid = false;
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (schema.type !== "boolean" &&
|
|
217
|
-
schema.type !== "string" &&
|
|
218
|
-
schema.type !== "number" &&
|
|
219
|
-
schema.type !== "integer") {
|
|
220
|
-
if (isRequiredWithoutDefault) {
|
|
221
|
-
paramResult.isValid = false;
|
|
222
|
-
}
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (param.in === "query" || param.in === "path") {
|
|
226
|
-
if (isRequiredWithoutDefault) {
|
|
227
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return paramResult;
|
|
235
|
-
}
|
|
236
|
-
static checkPostBody(schema, isRequired = false) {
|
|
237
|
-
var _a;
|
|
238
|
-
const paramResult = {
|
|
239
|
-
requiredNum: 0,
|
|
240
|
-
optionalNum: 0,
|
|
241
|
-
isValid: true,
|
|
242
|
-
};
|
|
243
|
-
if (Object.keys(schema).length === 0) {
|
|
244
|
-
return paramResult;
|
|
245
|
-
}
|
|
246
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
247
|
-
if (schema.type === "string" ||
|
|
248
|
-
schema.type === "integer" ||
|
|
249
|
-
schema.type === "boolean" ||
|
|
250
|
-
schema.type === "number") {
|
|
251
|
-
if (isRequiredWithoutDefault) {
|
|
252
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else if (schema.type === "object") {
|
|
259
|
-
const { properties } = schema;
|
|
260
|
-
for (const property in properties) {
|
|
261
|
-
let isRequired = false;
|
|
262
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
263
|
-
isRequired = true;
|
|
264
|
-
}
|
|
265
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
266
|
-
paramResult.requiredNum += result.requiredNum;
|
|
267
|
-
paramResult.optionalNum += result.optionalNum;
|
|
268
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
if (isRequiredWithoutDefault) {
|
|
273
|
-
paramResult.isValid = false;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return paramResult;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Checks if the given API is supported.
|
|
280
|
-
* @param {string} method - The HTTP method of the API.
|
|
281
|
-
* @param {string} path - The path of the API.
|
|
282
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
283
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
284
|
-
* @description The following APIs are supported:
|
|
285
|
-
* 1. only support Get/Post operation without auth property
|
|
286
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
287
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
288
|
-
* 4. request body + required parameters <= 1
|
|
289
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
290
|
-
* 6. only support request body with “application/json” content type
|
|
291
|
-
*/
|
|
292
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
293
|
-
const pathObj = spec.paths[path];
|
|
294
|
-
method = method.toLocaleLowerCase();
|
|
295
|
-
if (pathObj) {
|
|
296
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
297
|
-
pathObj[method]) {
|
|
298
|
-
const securities = pathObj[method].security;
|
|
299
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
300
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
const operationObject = pathObj[method];
|
|
304
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
const paramObject = operationObject.parameters;
|
|
308
|
-
const requestBody = operationObject.requestBody;
|
|
309
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
310
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
311
|
-
if (mediaTypesCount > 1) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
315
|
-
if (Object.keys(responseJson).length === 0) {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
let requestBodyParamResult = {
|
|
319
|
-
requiredNum: 0,
|
|
320
|
-
optionalNum: 0,
|
|
321
|
-
isValid: true,
|
|
322
|
-
};
|
|
323
|
-
if (requestJsonBody) {
|
|
324
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
325
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
326
|
-
}
|
|
327
|
-
if (!requestBodyParamResult.isValid) {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
331
|
-
if (!paramResult.isValid) {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
335
|
-
if (allowMultipleParameters &&
|
|
336
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
else if (requestBodyParamResult.requiredNum +
|
|
342
|
-
requestBodyParamResult.optionalNum +
|
|
343
|
-
paramResult.requiredNum +
|
|
344
|
-
paramResult.optionalNum ===
|
|
345
|
-
0) {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
225
|
+
static hasNestedObjectInSchema(schema) {
|
|
226
|
+
if (schema.type === "object") {
|
|
227
|
+
for (const property in schema.properties) {
|
|
228
|
+
const nestedSchema = schema.properties[property];
|
|
229
|
+
if (nestedSchema.type === "object") {
|
|
349
230
|
return true;
|
|
350
231
|
}
|
|
351
232
|
}
|
|
352
233
|
}
|
|
353
234
|
return false;
|
|
354
235
|
}
|
|
355
|
-
static
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
// Currently we don't support multiple auth in one operation
|
|
361
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
for (const auths of authSchemaArray) {
|
|
365
|
-
if (auths.length === 1) {
|
|
366
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
else if (!allowAPIKeyAuth &&
|
|
370
|
-
allowOauth2 &&
|
|
371
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
else if (allowAPIKeyAuth &&
|
|
375
|
-
allowOauth2 &&
|
|
376
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
377
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return false;
|
|
236
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
237
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
238
|
+
}
|
|
239
|
+
static isBearerTokenAuth(authScheme) {
|
|
240
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
384
241
|
}
|
|
385
|
-
static isAPIKeyAuth(
|
|
386
|
-
return
|
|
242
|
+
static isAPIKeyAuth(authScheme) {
|
|
243
|
+
return authScheme.type === "apiKey";
|
|
387
244
|
}
|
|
388
|
-
static
|
|
389
|
-
return (
|
|
390
|
-
|
|
391
|
-
|
|
245
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
246
|
+
return !!(authScheme.type === "oauth2" &&
|
|
247
|
+
authScheme.flows &&
|
|
248
|
+
authScheme.flows.authorizationCode);
|
|
392
249
|
}
|
|
393
250
|
static getAuthArray(securities, spec) {
|
|
394
251
|
var _a;
|
|
@@ -401,7 +258,7 @@ class Utils {
|
|
|
401
258
|
for (const name in security) {
|
|
402
259
|
const auth = securitySchemas[name];
|
|
403
260
|
authArray.push({
|
|
404
|
-
|
|
261
|
+
authScheme: auth,
|
|
405
262
|
name: name,
|
|
406
263
|
});
|
|
407
264
|
}
|
|
@@ -413,24 +270,47 @@ class Utils {
|
|
|
413
270
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
414
271
|
return result;
|
|
415
272
|
}
|
|
273
|
+
static getAuthInfo(spec) {
|
|
274
|
+
let authInfo = undefined;
|
|
275
|
+
for (const url in spec.paths) {
|
|
276
|
+
for (const method in spec.paths[url]) {
|
|
277
|
+
const operation = spec.paths[url][method];
|
|
278
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
279
|
+
if (authArray && authArray.length > 0) {
|
|
280
|
+
const currentAuth = authArray[0][0];
|
|
281
|
+
if (!authInfo) {
|
|
282
|
+
authInfo = authArray[0][0];
|
|
283
|
+
}
|
|
284
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
285
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, exports.ErrorType.MultipleAuthNotSupported);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return authInfo;
|
|
291
|
+
}
|
|
416
292
|
static updateFirstLetter(str) {
|
|
417
293
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
418
294
|
}
|
|
419
295
|
static getResponseJson(operationObject) {
|
|
420
296
|
var _a, _b;
|
|
421
297
|
let json = {};
|
|
298
|
+
let multipleMediaType = false;
|
|
422
299
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
423
300
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
424
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
425
|
-
if (mediaTypesCount > 1) {
|
|
426
|
-
return {};
|
|
427
|
-
}
|
|
428
301
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
302
|
+
multipleMediaType = false;
|
|
429
303
|
json = responseObject.content["application/json"];
|
|
430
|
-
|
|
304
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
305
|
+
multipleMediaType = true;
|
|
306
|
+
json = {};
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
431
311
|
}
|
|
432
312
|
}
|
|
433
|
-
return json;
|
|
313
|
+
return { json, multipleMediaType };
|
|
434
314
|
}
|
|
435
315
|
static convertPathToCamelCase(path) {
|
|
436
316
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -450,10 +330,10 @@ class Utils {
|
|
|
450
330
|
return undefined;
|
|
451
331
|
}
|
|
452
332
|
}
|
|
453
|
-
static
|
|
333
|
+
static resolveEnv(str) {
|
|
454
334
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
455
|
-
let matches = placeHolderReg.exec(
|
|
456
|
-
let
|
|
335
|
+
let matches = placeHolderReg.exec(str);
|
|
336
|
+
let newStr = str;
|
|
457
337
|
while (matches != null) {
|
|
458
338
|
const envVar = matches[1];
|
|
459
339
|
const envVal = process.env[envVar];
|
|
@@ -461,17 +341,17 @@ class Utils {
|
|
|
461
341
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
462
342
|
}
|
|
463
343
|
else {
|
|
464
|
-
|
|
344
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
465
345
|
}
|
|
466
|
-
matches = placeHolderReg.exec(
|
|
346
|
+
matches = placeHolderReg.exec(str);
|
|
467
347
|
}
|
|
468
|
-
return
|
|
348
|
+
return newStr;
|
|
469
349
|
}
|
|
470
350
|
static checkServerUrl(servers) {
|
|
471
351
|
const errors = [];
|
|
472
352
|
let serverUrl;
|
|
473
353
|
try {
|
|
474
|
-
serverUrl = Utils.
|
|
354
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
475
355
|
}
|
|
476
356
|
catch (err) {
|
|
477
357
|
errors.push({
|
|
@@ -501,7 +381,8 @@ class Utils {
|
|
|
501
381
|
}
|
|
502
382
|
return errors;
|
|
503
383
|
}
|
|
504
|
-
static validateServer(spec,
|
|
384
|
+
static validateServer(spec, options) {
|
|
385
|
+
var _a;
|
|
505
386
|
const errors = [];
|
|
506
387
|
let hasTopLevelServers = false;
|
|
507
388
|
let hasPathLevelServers = false;
|
|
@@ -522,7 +403,7 @@ class Utils {
|
|
|
522
403
|
}
|
|
523
404
|
for (const method in methods) {
|
|
524
405
|
const operationObject = methods[method];
|
|
525
|
-
if (
|
|
406
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
526
407
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
527
408
|
hasOperationLevelServers = true;
|
|
528
409
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -565,6 +446,7 @@ class Utils {
|
|
|
565
446
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
566
447
|
}
|
|
567
448
|
if (isRequired && schema.default === undefined) {
|
|
449
|
+
parameter.isRequired = true;
|
|
568
450
|
requiredParams.push(parameter);
|
|
569
451
|
}
|
|
570
452
|
else {
|
|
@@ -609,7 +491,7 @@ class Utils {
|
|
|
609
491
|
param.value = schema.default;
|
|
610
492
|
}
|
|
611
493
|
}
|
|
612
|
-
static parseApiInfo(operationItem,
|
|
494
|
+
static parseApiInfo(operationItem, options) {
|
|
613
495
|
var _a, _b;
|
|
614
496
|
const requiredParams = [];
|
|
615
497
|
const optionalParams = [];
|
|
@@ -623,11 +505,12 @@ class Utils {
|
|
|
623
505
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
624
506
|
};
|
|
625
507
|
const schema = param.schema;
|
|
626
|
-
if (allowMultipleParameters && schema) {
|
|
508
|
+
if (options.allowMultipleParameters && schema) {
|
|
627
509
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
628
510
|
}
|
|
629
511
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
630
512
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
513
|
+
parameter.isRequired = true;
|
|
631
514
|
requiredParams.push(parameter);
|
|
632
515
|
}
|
|
633
516
|
else {
|
|
@@ -641,19 +524,13 @@ class Utils {
|
|
|
641
524
|
const requestJson = requestBody.content["application/json"];
|
|
642
525
|
if (Object.keys(requestJson).length !== 0) {
|
|
643
526
|
const schema = requestJson.schema;
|
|
644
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
527
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
645
528
|
requiredParams.push(...requiredP);
|
|
646
529
|
optionalParams.push(...optionalP);
|
|
647
530
|
}
|
|
648
531
|
}
|
|
649
532
|
const operationId = operationItem.operationId;
|
|
650
|
-
const parameters = [];
|
|
651
|
-
if (requiredParams.length !== 0) {
|
|
652
|
-
parameters.push(...requiredParams);
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
parameters.push(optionalParams[0]);
|
|
656
|
-
}
|
|
533
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
657
534
|
const command = {
|
|
658
535
|
context: ["compose"],
|
|
659
536
|
type: "query",
|
|
@@ -662,228 +539,575 @@ class Utils {
|
|
|
662
539
|
parameters: parameters,
|
|
663
540
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
664
541
|
};
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
542
|
+
return command;
|
|
543
|
+
}
|
|
544
|
+
static format(str, ...args) {
|
|
545
|
+
let index = 0;
|
|
546
|
+
return str.replace(/%s/g, () => {
|
|
547
|
+
const arg = args[index++];
|
|
548
|
+
return arg !== undefined ? arg : "";
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
552
|
+
if (!authName) {
|
|
553
|
+
return "";
|
|
672
554
|
}
|
|
673
|
-
|
|
555
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
556
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
557
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
558
|
+
}
|
|
559
|
+
return safeRegistrationIdEnvName;
|
|
674
560
|
}
|
|
675
|
-
static
|
|
676
|
-
const
|
|
561
|
+
static getServerObject(spec, method, path) {
|
|
562
|
+
const pathObj = spec.paths[path];
|
|
563
|
+
const operationObject = pathObj[method];
|
|
564
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
565
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
566
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
567
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
568
|
+
return serverUrl;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Copyright (c) Microsoft Corporation.
|
|
573
|
+
class Validator {
|
|
574
|
+
listAPIs() {
|
|
575
|
+
var _a;
|
|
576
|
+
if (this.apiMap) {
|
|
577
|
+
return this.apiMap;
|
|
578
|
+
}
|
|
579
|
+
const paths = this.spec.paths;
|
|
677
580
|
const result = {};
|
|
678
581
|
for (const path in paths) {
|
|
679
582
|
const methods = paths[path];
|
|
680
583
|
for (const method in methods) {
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
const
|
|
684
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
584
|
+
const operationObject = methods[method];
|
|
585
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
586
|
+
const validateResult = this.validateAPI(method, path);
|
|
587
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
588
|
+
operation: operationObject,
|
|
589
|
+
isValid: validateResult.isValid,
|
|
590
|
+
reason: validateResult.reason,
|
|
591
|
+
};
|
|
685
592
|
}
|
|
686
593
|
}
|
|
687
594
|
}
|
|
595
|
+
this.apiMap = result;
|
|
688
596
|
return result;
|
|
689
597
|
}
|
|
690
|
-
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
598
|
+
validateSpecVersion() {
|
|
599
|
+
const result = { errors: [], warnings: [] };
|
|
600
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
601
|
+
result.errors.push({
|
|
602
|
+
type: exports.ErrorType.SpecVersionNotSupported,
|
|
603
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
604
|
+
data: this.spec.openapi,
|
|
697
605
|
});
|
|
698
606
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
validateSpecServer() {
|
|
610
|
+
const result = { errors: [], warnings: [] };
|
|
611
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
612
|
+
result.errors.push(...serverErrors);
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
validateSpecNoSupportAPI() {
|
|
616
|
+
const result = { errors: [], warnings: [] };
|
|
617
|
+
const apiMap = this.listAPIs();
|
|
618
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
619
|
+
if (validAPIs.length === 0) {
|
|
620
|
+
const data = [];
|
|
621
|
+
for (const key in apiMap) {
|
|
622
|
+
const { reason } = apiMap[key];
|
|
623
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
624
|
+
data.push(apiInvalidReason);
|
|
625
|
+
}
|
|
626
|
+
result.errors.push({
|
|
716
627
|
type: exports.ErrorType.NoSupportedApi,
|
|
717
628
|
content: ConstantString.NoSupportedApi,
|
|
629
|
+
data,
|
|
718
630
|
});
|
|
719
631
|
}
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
validateSpecOperationId() {
|
|
635
|
+
const result = { errors: [], warnings: [] };
|
|
636
|
+
const apiMap = this.listAPIs();
|
|
720
637
|
// OperationId missing
|
|
721
638
|
const apisMissingOperationId = [];
|
|
722
639
|
for (const key in apiMap) {
|
|
723
|
-
const
|
|
724
|
-
if (!
|
|
640
|
+
const { operation } = apiMap[key];
|
|
641
|
+
if (!operation.operationId) {
|
|
725
642
|
apisMissingOperationId.push(key);
|
|
726
643
|
}
|
|
727
644
|
}
|
|
728
645
|
if (apisMissingOperationId.length > 0) {
|
|
729
|
-
warnings.push({
|
|
646
|
+
result.warnings.push({
|
|
730
647
|
type: exports.WarningType.OperationIdMissing,
|
|
731
648
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
732
649
|
data: apisMissingOperationId,
|
|
733
650
|
});
|
|
734
651
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
652
|
+
return result;
|
|
653
|
+
}
|
|
654
|
+
validateMethodAndPath(method, path) {
|
|
655
|
+
const result = { isValid: true, reason: [] };
|
|
656
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
657
|
+
result.isValid = false;
|
|
658
|
+
result.reason.push(exports.ErrorType.MethodNotAllowed);
|
|
659
|
+
return result;
|
|
738
660
|
}
|
|
739
|
-
|
|
740
|
-
|
|
661
|
+
const pathObj = this.spec.paths[path];
|
|
662
|
+
if (!pathObj || !pathObj[method]) {
|
|
663
|
+
result.isValid = false;
|
|
664
|
+
result.reason.push(exports.ErrorType.UrlPathNotExist);
|
|
665
|
+
return result;
|
|
741
666
|
}
|
|
742
|
-
return
|
|
743
|
-
status,
|
|
744
|
-
warnings,
|
|
745
|
-
errors,
|
|
746
|
-
};
|
|
667
|
+
return result;
|
|
747
668
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
669
|
+
validateResponse(method, path) {
|
|
670
|
+
const result = { isValid: true, reason: [] };
|
|
671
|
+
const operationObject = this.spec.paths[path][method];
|
|
672
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
673
|
+
if (this.options.projectType === exports.ProjectType.SME) {
|
|
674
|
+
// only support response body only contains “application/json” content type
|
|
675
|
+
if (multipleMediaType) {
|
|
676
|
+
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
|
|
677
|
+
}
|
|
678
|
+
else if (Object.keys(json).length === 0) {
|
|
679
|
+
// response body should not be empty
|
|
680
|
+
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return result;
|
|
754
684
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
685
|
+
validateServer(method, path) {
|
|
686
|
+
const result = { isValid: true, reason: [] };
|
|
687
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
688
|
+
if (!serverObj) {
|
|
689
|
+
// should contain server URL
|
|
690
|
+
result.reason.push(exports.ErrorType.NoServerInformation);
|
|
758
691
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
692
|
+
else {
|
|
693
|
+
// server url should be absolute url with https protocol
|
|
694
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
695
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
762
696
|
}
|
|
763
|
-
return
|
|
697
|
+
return result;
|
|
764
698
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
699
|
+
validateAuth(method, path) {
|
|
700
|
+
const pathObj = this.spec.paths[path];
|
|
701
|
+
const operationObject = pathObj[method];
|
|
702
|
+
const securities = operationObject.security;
|
|
703
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
704
|
+
if (authSchemeArray.length === 0) {
|
|
705
|
+
return { isValid: true, reason: [] };
|
|
706
|
+
}
|
|
707
|
+
if (this.options.allowAPIKeyAuth ||
|
|
708
|
+
this.options.allowOauth2 ||
|
|
709
|
+
this.options.allowBearerTokenAuth) {
|
|
710
|
+
// Currently we don't support multiple auth in one operation
|
|
711
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
712
|
+
return {
|
|
713
|
+
isValid: false,
|
|
714
|
+
reason: [exports.ErrorType.MultipleAuthNotSupported],
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
for (const auths of authSchemeArray) {
|
|
718
|
+
if (auths.length === 1) {
|
|
719
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
720
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
721
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
722
|
+
return { isValid: true, reason: [] };
|
|
783
723
|
}
|
|
784
724
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
|
|
728
|
+
}
|
|
729
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
730
|
+
var _a;
|
|
731
|
+
const paramResult = {
|
|
732
|
+
requiredNum: 0,
|
|
733
|
+
optionalNum: 0,
|
|
734
|
+
isValid: true,
|
|
735
|
+
reason: [],
|
|
736
|
+
};
|
|
737
|
+
if (Object.keys(schema).length === 0) {
|
|
738
|
+
return paramResult;
|
|
739
|
+
}
|
|
740
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
741
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
742
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
743
|
+
paramResult.isValid = false;
|
|
744
|
+
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
|
|
745
|
+
return paramResult;
|
|
746
|
+
}
|
|
747
|
+
if (schema.type === "string" ||
|
|
748
|
+
schema.type === "integer" ||
|
|
749
|
+
schema.type === "boolean" ||
|
|
750
|
+
schema.type === "number") {
|
|
751
|
+
if (isRequiredWithoutDefault) {
|
|
752
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
else if (schema.type === "object") {
|
|
759
|
+
const { properties } = schema;
|
|
760
|
+
for (const property in properties) {
|
|
761
|
+
let isRequired = false;
|
|
762
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
763
|
+
isRequired = true;
|
|
789
764
|
}
|
|
765
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
766
|
+
paramResult.requiredNum += result.requiredNum;
|
|
767
|
+
paramResult.optionalNum += result.optionalNum;
|
|
768
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
769
|
+
paramResult.reason.push(...result.reason);
|
|
790
770
|
}
|
|
791
|
-
newSpec.paths = newPaths;
|
|
792
|
-
return newSpec;
|
|
793
771
|
}
|
|
794
|
-
|
|
795
|
-
|
|
772
|
+
else {
|
|
773
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
774
|
+
paramResult.isValid = false;
|
|
775
|
+
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
776
|
+
}
|
|
796
777
|
}
|
|
778
|
+
return paramResult;
|
|
779
|
+
}
|
|
780
|
+
checkParamSchema(paramObject) {
|
|
781
|
+
const paramResult = {
|
|
782
|
+
requiredNum: 0,
|
|
783
|
+
optionalNum: 0,
|
|
784
|
+
isValid: true,
|
|
785
|
+
reason: [],
|
|
786
|
+
};
|
|
787
|
+
if (!paramObject) {
|
|
788
|
+
return paramResult;
|
|
789
|
+
}
|
|
790
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
791
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
792
|
+
const param = paramObject[i];
|
|
793
|
+
const schema = param.schema;
|
|
794
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
795
|
+
paramResult.isValid = false;
|
|
796
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
800
|
+
if (isCopilot) {
|
|
801
|
+
if (isRequiredWithoutDefault) {
|
|
802
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
806
|
+
}
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
810
|
+
if (isRequiredWithoutDefault) {
|
|
811
|
+
paramResult.isValid = false;
|
|
812
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
813
|
+
}
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (schema.type !== "boolean" &&
|
|
817
|
+
schema.type !== "string" &&
|
|
818
|
+
schema.type !== "number" &&
|
|
819
|
+
schema.type !== "integer") {
|
|
820
|
+
if (isRequiredWithoutDefault) {
|
|
821
|
+
paramResult.isValid = false;
|
|
822
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
823
|
+
}
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (param.in === "query" || param.in === "path") {
|
|
827
|
+
if (isRequiredWithoutDefault) {
|
|
828
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return paramResult;
|
|
836
|
+
}
|
|
837
|
+
hasNestedObjectInSchema(schema) {
|
|
838
|
+
if (schema.type === "object") {
|
|
839
|
+
for (const property in schema.properties) {
|
|
840
|
+
const nestedSchema = schema.properties[property];
|
|
841
|
+
if (nestedSchema.type === "object") {
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return false;
|
|
797
847
|
}
|
|
798
848
|
}
|
|
799
849
|
|
|
800
850
|
// Copyright (c) Microsoft Corporation.
|
|
801
|
-
class
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
851
|
+
class CopilotValidator extends Validator {
|
|
852
|
+
constructor(spec, options) {
|
|
853
|
+
super();
|
|
854
|
+
this.projectType = exports.ProjectType.Copilot;
|
|
855
|
+
this.options = options;
|
|
856
|
+
this.spec = spec;
|
|
857
|
+
}
|
|
858
|
+
validateSpec() {
|
|
859
|
+
const result = { errors: [], warnings: [] };
|
|
860
|
+
// validate spec version
|
|
861
|
+
let validationResult = this.validateSpecVersion();
|
|
862
|
+
result.errors.push(...validationResult.errors);
|
|
863
|
+
// validate spec server
|
|
864
|
+
validationResult = this.validateSpecServer();
|
|
865
|
+
result.errors.push(...validationResult.errors);
|
|
866
|
+
// validate no supported API
|
|
867
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
868
|
+
result.errors.push(...validationResult.errors);
|
|
869
|
+
// validate operationId missing
|
|
870
|
+
validationResult = this.validateSpecOperationId();
|
|
871
|
+
result.warnings.push(...validationResult.warnings);
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
validateAPI(method, path) {
|
|
875
|
+
const result = { isValid: true, reason: [] };
|
|
876
|
+
method = method.toLocaleLowerCase();
|
|
877
|
+
// validate method and path
|
|
878
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
879
|
+
if (!methodAndPathResult.isValid) {
|
|
880
|
+
return methodAndPathResult;
|
|
881
|
+
}
|
|
882
|
+
const operationObject = this.spec.paths[path][method];
|
|
883
|
+
// validate auth
|
|
884
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
885
|
+
result.reason.push(...authCheckResult.reason);
|
|
886
|
+
// validate operationId
|
|
887
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
888
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
889
|
+
}
|
|
890
|
+
// validate server
|
|
891
|
+
const validateServerResult = this.validateServer(method, path);
|
|
892
|
+
result.reason.push(...validateServerResult.reason);
|
|
893
|
+
// validate response
|
|
894
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
895
|
+
result.reason.push(...validateResponseResult.reason);
|
|
896
|
+
// validate requestBody
|
|
897
|
+
const requestBody = operationObject.requestBody;
|
|
898
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
899
|
+
if (requestJsonBody) {
|
|
900
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
901
|
+
if (requestBodySchema.type !== "object") {
|
|
902
|
+
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
|
|
845
903
|
}
|
|
846
|
-
|
|
847
|
-
|
|
904
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
905
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
906
|
+
}
|
|
907
|
+
// validate parameters
|
|
908
|
+
const paramObject = operationObject.parameters;
|
|
909
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
910
|
+
result.reason.push(...paramResult.reason);
|
|
911
|
+
if (result.reason.length > 0) {
|
|
912
|
+
result.isValid = false;
|
|
913
|
+
}
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Copyright (c) Microsoft Corporation.
|
|
919
|
+
class SMEValidator extends Validator {
|
|
920
|
+
constructor(spec, options) {
|
|
921
|
+
super();
|
|
922
|
+
this.projectType = exports.ProjectType.SME;
|
|
923
|
+
this.options = options;
|
|
924
|
+
this.spec = spec;
|
|
925
|
+
}
|
|
926
|
+
validateSpec() {
|
|
927
|
+
const result = { errors: [], warnings: [] };
|
|
928
|
+
// validate spec version
|
|
929
|
+
let validationResult = this.validateSpecVersion();
|
|
930
|
+
result.errors.push(...validationResult.errors);
|
|
931
|
+
// validate spec server
|
|
932
|
+
validationResult = this.validateSpecServer();
|
|
933
|
+
result.errors.push(...validationResult.errors);
|
|
934
|
+
// validate no supported API
|
|
935
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
936
|
+
result.errors.push(...validationResult.errors);
|
|
937
|
+
// validate operationId missing
|
|
938
|
+
if (this.options.allowMissingId) {
|
|
939
|
+
validationResult = this.validateSpecOperationId();
|
|
940
|
+
result.warnings.push(...validationResult.warnings);
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
}
|
|
944
|
+
validateAPI(method, path) {
|
|
945
|
+
const result = { isValid: true, reason: [] };
|
|
946
|
+
method = method.toLocaleLowerCase();
|
|
947
|
+
// validate method and path
|
|
948
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
949
|
+
if (!methodAndPathResult.isValid) {
|
|
950
|
+
return methodAndPathResult;
|
|
951
|
+
}
|
|
952
|
+
const operationObject = this.spec.paths[path][method];
|
|
953
|
+
// validate auth
|
|
954
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
955
|
+
result.reason.push(...authCheckResult.reason);
|
|
956
|
+
// validate operationId
|
|
957
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
958
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
959
|
+
}
|
|
960
|
+
// validate server
|
|
961
|
+
const validateServerResult = this.validateServer(method, path);
|
|
962
|
+
result.reason.push(...validateServerResult.reason);
|
|
963
|
+
// validate response
|
|
964
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
965
|
+
result.reason.push(...validateResponseResult.reason);
|
|
966
|
+
let postBodyResult = {
|
|
967
|
+
requiredNum: 0,
|
|
968
|
+
optionalNum: 0,
|
|
969
|
+
isValid: true,
|
|
970
|
+
reason: [],
|
|
971
|
+
};
|
|
972
|
+
// validate requestBody
|
|
973
|
+
const requestBody = operationObject.requestBody;
|
|
974
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
975
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
976
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
977
|
+
}
|
|
978
|
+
if (requestJsonBody) {
|
|
979
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
980
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
981
|
+
result.reason.push(...postBodyResult.reason);
|
|
982
|
+
}
|
|
983
|
+
// validate parameters
|
|
984
|
+
const paramObject = operationObject.parameters;
|
|
985
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
986
|
+
result.reason.push(...paramResult.reason);
|
|
987
|
+
// validate total parameters count
|
|
988
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
989
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
990
|
+
result.reason.push(...paramCountResult.reason);
|
|
991
|
+
}
|
|
992
|
+
if (result.reason.length > 0) {
|
|
993
|
+
result.isValid = false;
|
|
994
|
+
}
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
998
|
+
const result = { isValid: true, reason: [] };
|
|
999
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
1000
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
1001
|
+
if (totalRequiredParams > 1) {
|
|
1002
|
+
if (!this.options.allowMultipleParameters ||
|
|
1003
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
1004
|
+
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
|
|
848
1005
|
}
|
|
849
|
-
}
|
|
1006
|
+
}
|
|
1007
|
+
else if (totalParams === 0) {
|
|
1008
|
+
result.reason.push(exports.ErrorType.NoParameter);
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
1014
|
+
|
|
1015
|
+
// Copyright (c) Microsoft Corporation.
|
|
1016
|
+
class TeamsAIValidator extends Validator {
|
|
1017
|
+
constructor(spec, options) {
|
|
1018
|
+
super();
|
|
1019
|
+
this.projectType = exports.ProjectType.TeamsAi;
|
|
1020
|
+
this.options = options;
|
|
1021
|
+
this.spec = spec;
|
|
1022
|
+
}
|
|
1023
|
+
validateSpec() {
|
|
1024
|
+
const result = { errors: [], warnings: [] };
|
|
1025
|
+
// validate spec server
|
|
1026
|
+
let validationResult = this.validateSpecServer();
|
|
1027
|
+
result.errors.push(...validationResult.errors);
|
|
1028
|
+
// validate no supported API
|
|
1029
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1030
|
+
result.errors.push(...validationResult.errors);
|
|
1031
|
+
return result;
|
|
1032
|
+
}
|
|
1033
|
+
validateAPI(method, path) {
|
|
1034
|
+
const result = { isValid: true, reason: [] };
|
|
1035
|
+
method = method.toLocaleLowerCase();
|
|
1036
|
+
// validate method and path
|
|
1037
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1038
|
+
if (!methodAndPathResult.isValid) {
|
|
1039
|
+
return methodAndPathResult;
|
|
1040
|
+
}
|
|
1041
|
+
const operationObject = this.spec.paths[path][method];
|
|
1042
|
+
// validate operationId
|
|
1043
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1044
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
1045
|
+
}
|
|
1046
|
+
// validate server
|
|
1047
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1048
|
+
result.reason.push(...validateServerResult.reason);
|
|
1049
|
+
if (result.reason.length > 0) {
|
|
1050
|
+
result.isValid = false;
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
class ValidatorFactory {
|
|
1057
|
+
static create(spec, options) {
|
|
1058
|
+
var _a;
|
|
1059
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : exports.ProjectType.SME;
|
|
1060
|
+
switch (type) {
|
|
1061
|
+
case exports.ProjectType.SME:
|
|
1062
|
+
return new SMEValidator(spec, options);
|
|
1063
|
+
case exports.ProjectType.Copilot:
|
|
1064
|
+
return new CopilotValidator(spec, options);
|
|
1065
|
+
case exports.ProjectType.TeamsAi:
|
|
1066
|
+
return new TeamsAIValidator(spec, options);
|
|
1067
|
+
default:
|
|
1068
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1069
|
+
}
|
|
850
1070
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Copyright (c) Microsoft Corporation.
|
|
1074
|
+
class SpecFilter {
|
|
1075
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1076
|
+
var _a;
|
|
1077
|
+
try {
|
|
1078
|
+
const newSpec = Object.assign({}, unResolveSpec);
|
|
1079
|
+
const newPaths = {};
|
|
1080
|
+
for (const filterItem of filter) {
|
|
1081
|
+
const [method, path] = filterItem.split(" ");
|
|
1082
|
+
const methodName = method.toLowerCase();
|
|
1083
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1084
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1085
|
+
pathObj &&
|
|
1086
|
+
pathObj[methodName]) {
|
|
1087
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1088
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1089
|
+
if (!validateResult.isValid) {
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (!newPaths[path]) {
|
|
1093
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1094
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1095
|
+
delete newPaths[path][m];
|
|
877
1096
|
}
|
|
878
1097
|
}
|
|
1098
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1099
|
+
// Add the operationId if missing
|
|
1100
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1101
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
1102
|
+
}
|
|
879
1103
|
}
|
|
880
1104
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1105
|
+
newSpec.paths = newPaths;
|
|
1106
|
+
return newSpec;
|
|
1107
|
+
}
|
|
1108
|
+
catch (err) {
|
|
1109
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.FilterSpecFailed);
|
|
1110
|
+
}
|
|
887
1111
|
}
|
|
888
1112
|
}
|
|
889
1113
|
|
|
@@ -891,7 +1115,7 @@ class ManifestUpdater {
|
|
|
891
1115
|
class AdaptiveCardGenerator {
|
|
892
1116
|
static generateAdaptiveCard(operationItem) {
|
|
893
1117
|
try {
|
|
894
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1118
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
895
1119
|
let cardBody = [];
|
|
896
1120
|
let schema = json.schema;
|
|
897
1121
|
let jsonPath = "$";
|
|
@@ -1057,6 +1281,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1057
1281
|
};
|
|
1058
1282
|
return result;
|
|
1059
1283
|
}
|
|
1284
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1285
|
+
const props = inferProperties(card);
|
|
1286
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1287
|
+
const result = {
|
|
1288
|
+
data_path: dataPath,
|
|
1289
|
+
};
|
|
1290
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1291
|
+
result.properties = {};
|
|
1292
|
+
if (props.title) {
|
|
1293
|
+
result.properties.title = "$." + props.title;
|
|
1294
|
+
}
|
|
1295
|
+
if (props.subtitle) {
|
|
1296
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1297
|
+
}
|
|
1298
|
+
if (props.imageUrl) {
|
|
1299
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
result.static_template = card;
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
1060
1305
|
/**
|
|
1061
1306
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1062
1307
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1069,11 +1314,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1069
1314
|
* @returns The inferred preview card template.
|
|
1070
1315
|
*/
|
|
1071
1316
|
function inferPreviewCardTemplate(card) {
|
|
1072
|
-
var _a;
|
|
1073
1317
|
const result = {
|
|
1074
|
-
title: "",
|
|
1318
|
+
title: "result",
|
|
1075
1319
|
};
|
|
1076
|
-
const
|
|
1320
|
+
const inferredProperties = inferProperties(card);
|
|
1321
|
+
if (inferredProperties.title) {
|
|
1322
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1323
|
+
}
|
|
1324
|
+
if (inferredProperties.subtitle) {
|
|
1325
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1326
|
+
}
|
|
1327
|
+
if (inferredProperties.imageUrl) {
|
|
1328
|
+
result.image = {
|
|
1329
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1330
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1331
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return result;
|
|
1335
|
+
}
|
|
1336
|
+
function inferProperties(card) {
|
|
1337
|
+
var _a;
|
|
1338
|
+
const result = {};
|
|
1339
|
+
const nameSet = new Set();
|
|
1077
1340
|
let rootObject;
|
|
1078
1341
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1079
1342
|
rootObject = card.body[0].items;
|
|
@@ -1086,56 +1349,407 @@ function inferPreviewCardTemplate(card) {
|
|
|
1086
1349
|
const textElement = element;
|
|
1087
1350
|
const index = textElement.text.indexOf("${if(");
|
|
1088
1351
|
if (index > 0) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1352
|
+
const text = textElement.text.substring(index);
|
|
1353
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1354
|
+
const property = match ? match[1] : "";
|
|
1355
|
+
if (property) {
|
|
1356
|
+
nameSet.add(property);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1361
|
+
const imageElement = element;
|
|
1362
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1363
|
+
const property = match ? match[1] : "";
|
|
1364
|
+
if (property) {
|
|
1365
|
+
nameSet.add(property);
|
|
1091
1366
|
}
|
|
1092
1367
|
}
|
|
1093
1368
|
}
|
|
1094
|
-
for (const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
textBlockElements.delete(element);
|
|
1369
|
+
for (const name of nameSet) {
|
|
1370
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1371
|
+
result.title = name;
|
|
1372
|
+
nameSet.delete(name);
|
|
1099
1373
|
}
|
|
1100
1374
|
else if (!result.subtitle &&
|
|
1101
|
-
Utils.isWellKnownName(
|
|
1102
|
-
result.subtitle =
|
|
1103
|
-
|
|
1375
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1376
|
+
result.subtitle = name;
|
|
1377
|
+
nameSet.delete(name);
|
|
1104
1378
|
}
|
|
1105
|
-
else if (!result.
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (property) {
|
|
1109
|
-
result.image = {
|
|
1110
|
-
url: `\${${property}}`,
|
|
1111
|
-
alt: text,
|
|
1112
|
-
$when: `\${${property} != null}`,
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
textBlockElements.delete(element);
|
|
1379
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1380
|
+
result.imageUrl = name;
|
|
1381
|
+
nameSet.delete(name);
|
|
1116
1382
|
}
|
|
1117
1383
|
}
|
|
1118
|
-
for (const
|
|
1119
|
-
const text = element.text;
|
|
1384
|
+
for (const name of nameSet) {
|
|
1120
1385
|
if (!result.title) {
|
|
1121
|
-
result.title =
|
|
1122
|
-
|
|
1386
|
+
result.title = name;
|
|
1387
|
+
nameSet.delete(name);
|
|
1123
1388
|
}
|
|
1124
1389
|
else if (!result.subtitle) {
|
|
1125
|
-
result.subtitle =
|
|
1126
|
-
|
|
1390
|
+
result.subtitle = name;
|
|
1391
|
+
nameSet.delete(name);
|
|
1127
1392
|
}
|
|
1128
1393
|
}
|
|
1129
1394
|
if (!result.title && result.subtitle) {
|
|
1130
1395
|
result.title = result.subtitle;
|
|
1131
1396
|
delete result.subtitle;
|
|
1132
1397
|
}
|
|
1133
|
-
if (!result.title) {
|
|
1134
|
-
result.title = "result";
|
|
1135
|
-
}
|
|
1136
1398
|
return result;
|
|
1137
1399
|
}
|
|
1138
1400
|
|
|
1401
|
+
// Copyright (c) Microsoft Corporation.
|
|
1402
|
+
class ManifestUpdater {
|
|
1403
|
+
static updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
|
|
1404
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1405
|
+
const manifest = yield fs__default['default'].readJSON(manifestPath);
|
|
1406
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1407
|
+
manifest.plugins = [
|
|
1408
|
+
{
|
|
1409
|
+
file: apiPluginRelativePath,
|
|
1410
|
+
id: ConstantString.DefaultPluginId,
|
|
1411
|
+
},
|
|
1412
|
+
];
|
|
1413
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1414
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1415
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1416
|
+
const apiPlugin = yield ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
|
|
1417
|
+
return [manifest, apiPlugin];
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
static updateManifestDescription(manifest, spec) {
|
|
1421
|
+
var _a, _b;
|
|
1422
|
+
manifest.description = {
|
|
1423
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1424
|
+
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),
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
1428
|
+
let parameter;
|
|
1429
|
+
if (schema.type === "array") {
|
|
1430
|
+
const items = schema.items;
|
|
1431
|
+
parameter = {
|
|
1432
|
+
type: "array",
|
|
1433
|
+
items: ManifestUpdater.mapOpenAPISchemaToFuncParam(items, method, pathUrl),
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
else if (schema.type === "string" ||
|
|
1437
|
+
schema.type === "boolean" ||
|
|
1438
|
+
schema.type === "integer" ||
|
|
1439
|
+
schema.type === "number") {
|
|
1440
|
+
parameter = {
|
|
1441
|
+
type: schema.type,
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), exports.ErrorType.UpdateManifestFailed);
|
|
1446
|
+
}
|
|
1447
|
+
if (schema.enum) {
|
|
1448
|
+
parameter.enum = schema.enum;
|
|
1449
|
+
}
|
|
1450
|
+
if (schema.description) {
|
|
1451
|
+
parameter.description = schema.description;
|
|
1452
|
+
}
|
|
1453
|
+
if (schema.default) {
|
|
1454
|
+
parameter.default = schema.default;
|
|
1455
|
+
}
|
|
1456
|
+
return parameter;
|
|
1457
|
+
}
|
|
1458
|
+
static generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
|
|
1459
|
+
var _a, _b, _c, _d, _e;
|
|
1460
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1461
|
+
const functions = [];
|
|
1462
|
+
const functionNames = [];
|
|
1463
|
+
const conversationStarters = [];
|
|
1464
|
+
const paths = spec.paths;
|
|
1465
|
+
const pluginAuthObj = {
|
|
1466
|
+
type: "None",
|
|
1467
|
+
};
|
|
1468
|
+
if (authInfo) {
|
|
1469
|
+
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
|
|
1470
|
+
pluginAuthObj.type = "OAuthPluginVault";
|
|
1471
|
+
}
|
|
1472
|
+
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
|
|
1473
|
+
pluginAuthObj.type = "ApiKeyPluginVault";
|
|
1474
|
+
}
|
|
1475
|
+
if (pluginAuthObj.type !== "None") {
|
|
1476
|
+
pluginAuthObj.reference_id = `${Utils.getSafeRegistrationIdEnvName(authInfo.name)}_REGISTRATION_ID`;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
for (const pathUrl in paths) {
|
|
1480
|
+
const pathItem = paths[pathUrl];
|
|
1481
|
+
if (pathItem) {
|
|
1482
|
+
const operations = pathItem;
|
|
1483
|
+
for (const method in operations) {
|
|
1484
|
+
if (options.allowMethods.includes(method)) {
|
|
1485
|
+
const operationItem = operations[method];
|
|
1486
|
+
const confirmationBodies = [];
|
|
1487
|
+
if (operationItem) {
|
|
1488
|
+
const operationId = operationItem.operationId;
|
|
1489
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1490
|
+
const paramObject = operationItem.parameters;
|
|
1491
|
+
const requestBody = operationItem.requestBody;
|
|
1492
|
+
const parameters = {
|
|
1493
|
+
type: "object",
|
|
1494
|
+
properties: {},
|
|
1495
|
+
required: [],
|
|
1496
|
+
};
|
|
1497
|
+
if (paramObject) {
|
|
1498
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1499
|
+
const param = paramObject[i];
|
|
1500
|
+
const schema = param.schema;
|
|
1501
|
+
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1502
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
|
|
1503
|
+
if (param.required) {
|
|
1504
|
+
parameters.required.push(param.name);
|
|
1505
|
+
}
|
|
1506
|
+
if (!parameters.properties[param.name].description) {
|
|
1507
|
+
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (requestBody) {
|
|
1512
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1513
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1514
|
+
if (requestBodySchema.type === "object") {
|
|
1515
|
+
if (requestBodySchema.required) {
|
|
1516
|
+
parameters.required.push(...requestBodySchema.required);
|
|
1517
|
+
}
|
|
1518
|
+
for (const property in requestBodySchema.properties) {
|
|
1519
|
+
const schema = requestBodySchema.properties[property];
|
|
1520
|
+
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1521
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const funcObj = {
|
|
1529
|
+
name: operationId,
|
|
1530
|
+
description: description,
|
|
1531
|
+
};
|
|
1532
|
+
if (paramObject || requestBody) {
|
|
1533
|
+
funcObj.parameters = parameters;
|
|
1534
|
+
}
|
|
1535
|
+
if (options.allowResponseSemantics) {
|
|
1536
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1537
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1538
|
+
funcObj.capabilities = {
|
|
1539
|
+
response_semantics: responseSemantic,
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
|
|
1543
|
+
if (!funcObj.capabilities) {
|
|
1544
|
+
funcObj.capabilities = {};
|
|
1545
|
+
}
|
|
1546
|
+
funcObj.capabilities.confirmation = {
|
|
1547
|
+
type: "AdaptiveCard",
|
|
1548
|
+
title: (_c = operationItem.summary) !== null && _c !== void 0 ? _c : description,
|
|
1549
|
+
};
|
|
1550
|
+
if (confirmationBodies.length > 0) {
|
|
1551
|
+
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
functions.push(funcObj);
|
|
1555
|
+
functionNames.push(operationId);
|
|
1556
|
+
if (description) {
|
|
1557
|
+
conversationStarters.push(description);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
let apiPlugin;
|
|
1565
|
+
if (yield fs__default['default'].pathExists(apiPluginFilePath)) {
|
|
1566
|
+
apiPlugin = yield fs__default['default'].readJSON(apiPluginFilePath);
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
apiPlugin = {
|
|
1570
|
+
schema_version: "v2.1",
|
|
1571
|
+
name_for_human: "",
|
|
1572
|
+
description_for_human: "",
|
|
1573
|
+
namespace: "",
|
|
1574
|
+
functions: [],
|
|
1575
|
+
runtimes: [],
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1579
|
+
for (const func of functions) {
|
|
1580
|
+
const index = (_d = apiPlugin.functions) === null || _d === void 0 ? void 0 : _d.findIndex((f) => f.name === func.name);
|
|
1581
|
+
if (index === -1) {
|
|
1582
|
+
apiPlugin.functions.push(func);
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
apiPlugin.functions[index] = func;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1589
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => {
|
|
1590
|
+
var _a, _b;
|
|
1591
|
+
return runtime.spec.url === specRelativePath &&
|
|
1592
|
+
runtime.type === "OpenApi" &&
|
|
1593
|
+
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
|
|
1594
|
+
});
|
|
1595
|
+
if (index === -1) {
|
|
1596
|
+
apiPlugin.runtimes.push({
|
|
1597
|
+
type: "OpenApi",
|
|
1598
|
+
auth: pluginAuthObj,
|
|
1599
|
+
spec: {
|
|
1600
|
+
url: specRelativePath,
|
|
1601
|
+
},
|
|
1602
|
+
run_for_functions: functionNames,
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1607
|
+
}
|
|
1608
|
+
if (!apiPlugin.name_for_human) {
|
|
1609
|
+
apiPlugin.name_for_human = appName;
|
|
1610
|
+
}
|
|
1611
|
+
if (!apiPlugin.namespace) {
|
|
1612
|
+
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
|
|
1613
|
+
}
|
|
1614
|
+
if (!apiPlugin.description_for_human) {
|
|
1615
|
+
apiPlugin.description_for_human =
|
|
1616
|
+
(_e = spec.info.description) !== null && _e !== void 0 ? _e : "<Please add description of the plugin>";
|
|
1617
|
+
}
|
|
1618
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1619
|
+
if (!apiPlugin.capabilities) {
|
|
1620
|
+
apiPlugin.capabilities = {
|
|
1621
|
+
localization: {},
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1625
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1626
|
+
.slice(0, 5)
|
|
1627
|
+
.map((text) => ({ text }));
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return apiPlugin;
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
static updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
1634
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1635
|
+
try {
|
|
1636
|
+
const originalManifest = yield fs__default['default'].readJSON(manifestPath);
|
|
1637
|
+
const updatedPart = {};
|
|
1638
|
+
updatedPart.composeExtensions = [];
|
|
1639
|
+
let warnings = [];
|
|
1640
|
+
if (options.projectType === exports.ProjectType.SME) {
|
|
1641
|
+
const updateResult = yield ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1642
|
+
const commands = updateResult[0];
|
|
1643
|
+
warnings = updateResult[1];
|
|
1644
|
+
const composeExtension = {
|
|
1645
|
+
composeExtensionType: "apiBased",
|
|
1646
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1647
|
+
commands: commands,
|
|
1648
|
+
};
|
|
1649
|
+
if (authInfo) {
|
|
1650
|
+
const auth = authInfo.authScheme;
|
|
1651
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1652
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1653
|
+
composeExtension.authorization = {
|
|
1654
|
+
authType: "apiSecretServiceAuth",
|
|
1655
|
+
apiSecretServiceAuthConfiguration: {
|
|
1656
|
+
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1657
|
+
},
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1661
|
+
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1662
|
+
composeExtension.authorization = {
|
|
1663
|
+
authType: "oAuth2.0",
|
|
1664
|
+
oAuthConfiguration: {
|
|
1665
|
+
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1666
|
+
},
|
|
1667
|
+
};
|
|
1668
|
+
updatedPart.webApplicationInfo = {
|
|
1669
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1670
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
1675
|
+
}
|
|
1676
|
+
updatedPart.description = originalManifest.description;
|
|
1677
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
1678
|
+
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
1679
|
+
return [updatedManifest, warnings];
|
|
1680
|
+
}
|
|
1681
|
+
catch (err) {
|
|
1682
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.UpdateManifestFailed);
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
static generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1687
|
+
var _a;
|
|
1688
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1689
|
+
const paths = spec.paths;
|
|
1690
|
+
const commands = [];
|
|
1691
|
+
const warnings = [];
|
|
1692
|
+
if (paths) {
|
|
1693
|
+
for (const pathUrl in paths) {
|
|
1694
|
+
const pathItem = paths[pathUrl];
|
|
1695
|
+
if (pathItem) {
|
|
1696
|
+
const operations = pathItem;
|
|
1697
|
+
// Currently only support GET and POST method
|
|
1698
|
+
for (const method in operations) {
|
|
1699
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1700
|
+
const operationItem = operations[method];
|
|
1701
|
+
if (operationItem) {
|
|
1702
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1703
|
+
if (command.parameters &&
|
|
1704
|
+
command.parameters.length >= 1 &&
|
|
1705
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1706
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1707
|
+
}
|
|
1708
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1709
|
+
command.parameters = [command.parameters[0]];
|
|
1710
|
+
warnings.push({
|
|
1711
|
+
type: exports.WarningType.OperationOnlyContainsOptionalParam,
|
|
1712
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1713
|
+
data: command.id,
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
if (adaptiveCardFolder) {
|
|
1717
|
+
const adaptiveCardPath = path__default['default'].join(adaptiveCardFolder, command.id + ".json");
|
|
1718
|
+
command.apiResponseRenderingTemplateFile = (yield fs__default['default'].pathExists(adaptiveCardPath))
|
|
1719
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1720
|
+
: "";
|
|
1721
|
+
}
|
|
1722
|
+
commands.push(command);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return [commands, warnings];
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
static getRelativePath(from, to) {
|
|
1733
|
+
const relativePath = path__default['default'].relative(path__default['default'].dirname(from), to);
|
|
1734
|
+
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
|
|
1735
|
+
}
|
|
1736
|
+
static removeEnvs(str) {
|
|
1737
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1738
|
+
const matches = placeHolderReg.exec(str);
|
|
1739
|
+
let newStr = str;
|
|
1740
|
+
if (matches != null) {
|
|
1741
|
+
newStr = newStr.replace(matches[0], "");
|
|
1742
|
+
}
|
|
1743
|
+
return newStr;
|
|
1744
|
+
}
|
|
1745
|
+
static removeAllSpecialCharacters(str) {
|
|
1746
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1747
|
+
}
|
|
1748
|
+
static getConfirmationBodyItem(paramName) {
|
|
1749
|
+
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1139
1753
|
// Copyright (c) Microsoft Corporation.
|
|
1140
1754
|
/**
|
|
1141
1755
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1151,8 +1765,14 @@ class SpecParser {
|
|
|
1151
1765
|
allowMissingId: true,
|
|
1152
1766
|
allowSwagger: true,
|
|
1153
1767
|
allowAPIKeyAuth: false,
|
|
1768
|
+
allowBearerTokenAuth: false,
|
|
1154
1769
|
allowMultipleParameters: false,
|
|
1155
1770
|
allowOauth2: false,
|
|
1771
|
+
allowMethods: ["get", "post"],
|
|
1772
|
+
allowConversationStarters: false,
|
|
1773
|
+
allowResponseSemantics: false,
|
|
1774
|
+
allowConfirmation: false,
|
|
1775
|
+
projectType: exports.ProjectType.SME,
|
|
1156
1776
|
};
|
|
1157
1777
|
this.pathOrSpec = pathOrDoc;
|
|
1158
1778
|
this.parser = new SwaggerParser__default['default']();
|
|
@@ -1177,6 +1797,8 @@ class SpecParser {
|
|
|
1177
1797
|
errors: [{ type: exports.ErrorType.SpecNotValid, content: e.toString() }],
|
|
1178
1798
|
};
|
|
1179
1799
|
}
|
|
1800
|
+
const errors = [];
|
|
1801
|
+
const warnings = [];
|
|
1180
1802
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1181
1803
|
return {
|
|
1182
1804
|
status: exports.ValidationStatus.Error,
|
|
@@ -1186,7 +1808,38 @@ class SpecParser {
|
|
|
1186
1808
|
],
|
|
1187
1809
|
};
|
|
1188
1810
|
}
|
|
1189
|
-
|
|
1811
|
+
// Remote reference not supported
|
|
1812
|
+
const refPaths = this.parser.$refs.paths();
|
|
1813
|
+
// refPaths [0] is the current spec file path
|
|
1814
|
+
if (refPaths.length > 1) {
|
|
1815
|
+
errors.push({
|
|
1816
|
+
type: exports.ErrorType.RemoteRefNotSupported,
|
|
1817
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1818
|
+
data: refPaths,
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1822
|
+
warnings.push({
|
|
1823
|
+
type: exports.WarningType.ConvertSwaggerToOpenAPI,
|
|
1824
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
const validator = this.getValidator(this.spec);
|
|
1828
|
+
const validationResult = validator.validateSpec();
|
|
1829
|
+
warnings.push(...validationResult.warnings);
|
|
1830
|
+
errors.push(...validationResult.errors);
|
|
1831
|
+
let status = exports.ValidationStatus.Valid;
|
|
1832
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1833
|
+
status = exports.ValidationStatus.Warning;
|
|
1834
|
+
}
|
|
1835
|
+
else if (errors.length > 0) {
|
|
1836
|
+
status = exports.ValidationStatus.Error;
|
|
1837
|
+
}
|
|
1838
|
+
return {
|
|
1839
|
+
status: status,
|
|
1840
|
+
warnings: warnings,
|
|
1841
|
+
errors: errors,
|
|
1842
|
+
};
|
|
1190
1843
|
}
|
|
1191
1844
|
catch (err) {
|
|
1192
1845
|
throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
|
|
@@ -1210,39 +1863,40 @@ class SpecParser {
|
|
|
1210
1863
|
try {
|
|
1211
1864
|
yield this.loadSpec();
|
|
1212
1865
|
const spec = this.spec;
|
|
1213
|
-
const apiMap = this.
|
|
1214
|
-
const result =
|
|
1866
|
+
const apiMap = this.getAPIs(spec);
|
|
1867
|
+
const result = {
|
|
1868
|
+
APIs: [],
|
|
1869
|
+
allAPICount: 0,
|
|
1870
|
+
validAPICount: 0,
|
|
1871
|
+
};
|
|
1215
1872
|
for (const apiKey in apiMap) {
|
|
1873
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1874
|
+
const [method, path] = apiKey.split(" ");
|
|
1875
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1216
1876
|
const apiResult = {
|
|
1217
|
-
api:
|
|
1877
|
+
api: apiKey,
|
|
1218
1878
|
server: "",
|
|
1219
|
-
operationId:
|
|
1879
|
+
operationId: operationId,
|
|
1880
|
+
isValid: isValid,
|
|
1881
|
+
reason: reason,
|
|
1220
1882
|
};
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
if (!operationId) {
|
|
1233
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1234
|
-
}
|
|
1235
|
-
apiResult.operationId = operationId;
|
|
1236
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1237
|
-
for (const auths of authArray) {
|
|
1238
|
-
if (auths.length === 1) {
|
|
1239
|
-
apiResult.auth = auths[0].authSchema;
|
|
1240
|
-
break;
|
|
1883
|
+
if (isValid) {
|
|
1884
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1885
|
+
if (serverObj) {
|
|
1886
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1887
|
+
}
|
|
1888
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1889
|
+
for (const auths of authArray) {
|
|
1890
|
+
if (auths.length === 1) {
|
|
1891
|
+
apiResult.auth = auths[0];
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1241
1894
|
}
|
|
1242
1895
|
}
|
|
1243
|
-
apiResult
|
|
1244
|
-
result.push(apiResult);
|
|
1896
|
+
result.APIs.push(apiResult);
|
|
1245
1897
|
}
|
|
1898
|
+
result.allAPICount = result.APIs.length;
|
|
1899
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1246
1900
|
return result;
|
|
1247
1901
|
}
|
|
1248
1902
|
catch (err) {
|
|
@@ -1267,7 +1921,7 @@ class SpecParser {
|
|
|
1267
1921
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1268
1922
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1269
1923
|
}
|
|
1270
|
-
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options
|
|
1924
|
+
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
|
|
1271
1925
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1272
1926
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1273
1927
|
}
|
|
@@ -1287,10 +1941,9 @@ class SpecParser {
|
|
|
1287
1941
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1288
1942
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1289
1943
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1290
|
-
* @param
|
|
1291
|
-
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
1944
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1292
1945
|
*/
|
|
1293
|
-
|
|
1946
|
+
generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1294
1947
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1295
1948
|
const result = {
|
|
1296
1949
|
allSuccess: true,
|
|
@@ -1300,52 +1953,68 @@ class SpecParser {
|
|
|
1300
1953
|
const newSpecs = yield this.getFilteredSpecs(filter, signal);
|
|
1301
1954
|
const newUnResolvedSpec = newSpecs[0];
|
|
1302
1955
|
const newSpec = newSpecs[1];
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
const operation = newSpec.paths[url][method];
|
|
1308
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1309
|
-
if (authArray && authArray.length > 0) {
|
|
1310
|
-
AuthSet.add(authArray[0][0].authSchema);
|
|
1311
|
-
if (AuthSet.size > 1) {
|
|
1312
|
-
hasMultipleAPIKeyAuth = true;
|
|
1313
|
-
break;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
if (hasMultipleAPIKeyAuth) {
|
|
1319
|
-
throw new SpecParserError(ConstantString.MultipleAPIKeyNotSupported, exports.ErrorType.MultipleAPIKeyNotSupported);
|
|
1956
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1957
|
+
yield this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1958
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1959
|
+
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1320
1960
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1961
|
+
const [updatedManifest, apiPlugin] = yield ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1962
|
+
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1963
|
+
yield fs__default['default'].outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1964
|
+
}
|
|
1965
|
+
catch (err) {
|
|
1966
|
+
if (err instanceof SpecParserError) {
|
|
1967
|
+
throw err;
|
|
1324
1968
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1969
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.GenerateFailed);
|
|
1970
|
+
}
|
|
1971
|
+
return result;
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1976
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1977
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1978
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1979
|
+
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1980
|
+
*/
|
|
1981
|
+
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
1982
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1983
|
+
const result = {
|
|
1984
|
+
allSuccess: true,
|
|
1985
|
+
warnings: [],
|
|
1986
|
+
};
|
|
1987
|
+
try {
|
|
1988
|
+
const newSpecs = yield this.getFilteredSpecs(filter, signal);
|
|
1989
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1990
|
+
const newSpec = newSpecs[1];
|
|
1991
|
+
let authInfo = undefined;
|
|
1992
|
+
if (this.options.projectType === exports.ProjectType.SME) {
|
|
1993
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1327
1994
|
}
|
|
1328
|
-
yield
|
|
1329
|
-
|
|
1330
|
-
for (const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1995
|
+
yield this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1996
|
+
if (adaptiveCardFolder) {
|
|
1997
|
+
for (const url in newSpec.paths) {
|
|
1998
|
+
for (const method in newSpec.paths[url]) {
|
|
1999
|
+
// paths object may contain description/summary which is not a http method, so we need to check if it is a operation object
|
|
2000
|
+
if (this.options.allowMethods.includes(method)) {
|
|
2001
|
+
const operation = newSpec.paths[url][method];
|
|
2002
|
+
try {
|
|
2003
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
|
|
2004
|
+
const fileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.json`);
|
|
2005
|
+
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
|
|
2006
|
+
yield fs__default['default'].outputJSON(fileName, wrappedCard, { spaces: 2 });
|
|
2007
|
+
const dataFileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.data.json`);
|
|
2008
|
+
yield fs__default['default'].outputJSON(dataFileName, {}, { spaces: 2 });
|
|
2009
|
+
}
|
|
2010
|
+
catch (err) {
|
|
2011
|
+
result.allSuccess = false;
|
|
2012
|
+
result.warnings.push({
|
|
2013
|
+
type: exports.WarningType.GenerateCardFailed,
|
|
2014
|
+
content: err.toString(),
|
|
2015
|
+
data: operation.operationId,
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
1349
2018
|
}
|
|
1350
2019
|
}
|
|
1351
2020
|
}
|
|
@@ -1353,8 +2022,7 @@ class SpecParser {
|
|
|
1353
2022
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1354
2023
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1355
2024
|
}
|
|
1356
|
-
const
|
|
1357
|
-
const [updatedManifest, warnings] = yield ManifestUpdater.updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, newSpec, this.options.allowMultipleParameters, auth, isMe);
|
|
2025
|
+
const [updatedManifest, warnings] = yield ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1358
2026
|
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1359
2027
|
result.warnings.push(...warnings);
|
|
1360
2028
|
}
|
|
@@ -1382,13 +2050,30 @@ class SpecParser {
|
|
|
1382
2050
|
}
|
|
1383
2051
|
});
|
|
1384
2052
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
2053
|
+
getAPIs(spec) {
|
|
2054
|
+
const validator = this.getValidator(spec);
|
|
2055
|
+
const apiMap = validator.listAPIs();
|
|
2056
|
+
return apiMap;
|
|
2057
|
+
}
|
|
2058
|
+
getValidator(spec) {
|
|
2059
|
+
if (this.validator) {
|
|
2060
|
+
return this.validator;
|
|
1388
2061
|
}
|
|
1389
|
-
const
|
|
1390
|
-
this.
|
|
1391
|
-
return
|
|
2062
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
2063
|
+
this.validator = validator;
|
|
2064
|
+
return validator;
|
|
2065
|
+
}
|
|
2066
|
+
saveFilterSpec(outputSpecPath, unResolvedSpec) {
|
|
2067
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2068
|
+
let resultStr;
|
|
2069
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
2070
|
+
resultStr = jsyaml__default['default'].dump(unResolvedSpec);
|
|
2071
|
+
}
|
|
2072
|
+
else {
|
|
2073
|
+
resultStr = JSON.stringify(unResolvedSpec, null, 2);
|
|
2074
|
+
}
|
|
2075
|
+
yield fs__default['default'].outputFile(outputSpecPath, resultStr);
|
|
2076
|
+
});
|
|
1392
2077
|
}
|
|
1393
2078
|
}
|
|
1394
2079
|
|