@microsoft/m365-spec-parser 0.1.1-alpha.d1a09e202.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 +607 -318
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1143 -684
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +607 -318
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1086 -623
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +70 -15
- package/dist/src/manifestUpdater.d.ts +5 -2
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +12 -30
- package/package.json +3 -3
package/dist/index.esm2017.mjs
CHANGED
|
@@ -20,6 +20,7 @@ var ErrorType;
|
|
|
20
20
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
21
21
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
22
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";
|
|
@@ -28,6 +29,21 @@ var ErrorType;
|
|
|
28
29
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
29
30
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
30
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";
|
|
31
47
|
ErrorType["Cancelled"] = "cancelled";
|
|
32
48
|
ErrorType["Unknown"] = "unknown";
|
|
33
49
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -75,6 +91,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
75
91
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
76
92
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
77
93
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
94
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
78
95
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
79
96
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
80
97
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -86,6 +103,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
86
103
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
87
104
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
88
105
|
ConstantString.TextBlockType = "TextBlock";
|
|
106
|
+
ConstantString.ImageType = "Image";
|
|
89
107
|
ConstantString.ContainerType = "Container";
|
|
90
108
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
91
109
|
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
@@ -149,7 +167,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
149
167
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
150
168
|
ConstantString.CommandTitleMaxLens = 32;
|
|
151
169
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
152
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
170
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
171
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
153
172
|
|
|
154
173
|
// Copyright (c) Microsoft Corporation.
|
|
155
174
|
class SpecParserError extends Error {
|
|
@@ -172,221 +191,9 @@ class Utils {
|
|
|
172
191
|
}
|
|
173
192
|
return false;
|
|
174
193
|
}
|
|
175
|
-
static checkParameters(paramObject, isCopilot) {
|
|
176
|
-
const paramResult = {
|
|
177
|
-
requiredNum: 0,
|
|
178
|
-
optionalNum: 0,
|
|
179
|
-
isValid: true,
|
|
180
|
-
};
|
|
181
|
-
if (!paramObject) {
|
|
182
|
-
return paramResult;
|
|
183
|
-
}
|
|
184
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
185
|
-
const param = paramObject[i];
|
|
186
|
-
const schema = param.schema;
|
|
187
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
188
|
-
paramResult.isValid = false;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
192
|
-
if (isCopilot) {
|
|
193
|
-
if (isRequiredWithoutDefault) {
|
|
194
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
198
|
-
}
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
202
|
-
if (isRequiredWithoutDefault) {
|
|
203
|
-
paramResult.isValid = false;
|
|
204
|
-
}
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (schema.type !== "boolean" &&
|
|
208
|
-
schema.type !== "string" &&
|
|
209
|
-
schema.type !== "number" &&
|
|
210
|
-
schema.type !== "integer") {
|
|
211
|
-
if (isRequiredWithoutDefault) {
|
|
212
|
-
paramResult.isValid = false;
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (param.in === "query" || param.in === "path") {
|
|
217
|
-
if (isRequiredWithoutDefault) {
|
|
218
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return paramResult;
|
|
226
|
-
}
|
|
227
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
228
|
-
var _a;
|
|
229
|
-
const paramResult = {
|
|
230
|
-
requiredNum: 0,
|
|
231
|
-
optionalNum: 0,
|
|
232
|
-
isValid: true,
|
|
233
|
-
};
|
|
234
|
-
if (Object.keys(schema).length === 0) {
|
|
235
|
-
return paramResult;
|
|
236
|
-
}
|
|
237
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
238
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
239
|
-
paramResult.isValid = false;
|
|
240
|
-
return paramResult;
|
|
241
|
-
}
|
|
242
|
-
if (schema.type === "string" ||
|
|
243
|
-
schema.type === "integer" ||
|
|
244
|
-
schema.type === "boolean" ||
|
|
245
|
-
schema.type === "number") {
|
|
246
|
-
if (isRequiredWithoutDefault) {
|
|
247
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
else if (schema.type === "object") {
|
|
254
|
-
const { properties } = schema;
|
|
255
|
-
for (const property in properties) {
|
|
256
|
-
let isRequired = false;
|
|
257
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
258
|
-
isRequired = true;
|
|
259
|
-
}
|
|
260
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
261
|
-
paramResult.requiredNum += result.requiredNum;
|
|
262
|
-
paramResult.optionalNum += result.optionalNum;
|
|
263
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
268
|
-
paramResult.isValid = false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return paramResult;
|
|
272
|
-
}
|
|
273
194
|
static containMultipleMediaTypes(bodyObject) {
|
|
274
195
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
275
196
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Checks if the given API is supported.
|
|
278
|
-
* @param {string} method - The HTTP method of the API.
|
|
279
|
-
* @param {string} path - The path of the API.
|
|
280
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
281
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
282
|
-
* @description The following APIs are supported:
|
|
283
|
-
* 1. only support Get/Post operation without auth property
|
|
284
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
285
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
286
|
-
* 4. request body + required parameters <= 1
|
|
287
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
288
|
-
* 6. only support request body with “application/json” content type
|
|
289
|
-
*/
|
|
290
|
-
static isSupportedApi(method, path, spec, options) {
|
|
291
|
-
var _a;
|
|
292
|
-
const pathObj = spec.paths[path];
|
|
293
|
-
method = method.toLocaleLowerCase();
|
|
294
|
-
if (pathObj) {
|
|
295
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
296
|
-
const securities = pathObj[method].security;
|
|
297
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
298
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
299
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
300
|
-
if (!isTeamsAi) {
|
|
301
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
302
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
const operationObject = pathObj[method];
|
|
307
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
const paramObject = operationObject.parameters;
|
|
311
|
-
const requestBody = operationObject.requestBody;
|
|
312
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
313
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
317
|
-
if (Object.keys(responseJson).length === 0) {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
// Teams AI project doesn't care about request parameters/body
|
|
321
|
-
if (isTeamsAi) {
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
let requestBodyParamResult = {
|
|
325
|
-
requiredNum: 0,
|
|
326
|
-
optionalNum: 0,
|
|
327
|
-
isValid: true,
|
|
328
|
-
};
|
|
329
|
-
if (requestJsonBody) {
|
|
330
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
331
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
335
|
-
}
|
|
336
|
-
if (!requestBodyParamResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
340
|
-
if (!paramResult.isValid) {
|
|
341
|
-
return false;
|
|
342
|
-
}
|
|
343
|
-
// Copilot support arbitrary parameters
|
|
344
|
-
if (isCopilot) {
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
348
|
-
if (options.allowMultipleParameters &&
|
|
349
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
350
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
else if (requestBodyParamResult.requiredNum +
|
|
356
|
-
requestBodyParamResult.optionalNum +
|
|
357
|
-
paramResult.requiredNum +
|
|
358
|
-
paramResult.optionalNum ===
|
|
359
|
-
0) {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
return true;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
370
|
-
if (authSchemeArray.length === 0) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
374
|
-
// Currently we don't support multiple auth in one operation
|
|
375
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
for (const auths of authSchemeArray) {
|
|
379
|
-
if (auths.length === 1) {
|
|
380
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
381
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
382
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
197
|
static isBearerTokenAuth(authScheme) {
|
|
391
198
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
392
199
|
}
|
|
@@ -394,10 +201,9 @@ class Utils {
|
|
|
394
201
|
return authScheme.type === "apiKey";
|
|
395
202
|
}
|
|
396
203
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return false;
|
|
204
|
+
return !!(authScheme.type === "oauth2" &&
|
|
205
|
+
authScheme.flows &&
|
|
206
|
+
authScheme.flows.authorizationCode);
|
|
401
207
|
}
|
|
402
208
|
static getAuthArray(securities, spec) {
|
|
403
209
|
var _a;
|
|
@@ -422,17 +228,39 @@ class Utils {
|
|
|
422
228
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
423
229
|
return result;
|
|
424
230
|
}
|
|
231
|
+
static getAuthInfo(spec) {
|
|
232
|
+
let authInfo = undefined;
|
|
233
|
+
for (const url in spec.paths) {
|
|
234
|
+
for (const method in spec.paths[url]) {
|
|
235
|
+
const operation = spec.paths[url][method];
|
|
236
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
237
|
+
if (authArray && authArray.length > 0) {
|
|
238
|
+
const currentAuth = authArray[0][0];
|
|
239
|
+
if (!authInfo) {
|
|
240
|
+
authInfo = authArray[0][0];
|
|
241
|
+
}
|
|
242
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
243
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return authInfo;
|
|
249
|
+
}
|
|
425
250
|
static updateFirstLetter(str) {
|
|
426
251
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
427
252
|
}
|
|
428
|
-
static getResponseJson(operationObject
|
|
253
|
+
static getResponseJson(operationObject) {
|
|
429
254
|
var _a, _b;
|
|
430
255
|
let json = {};
|
|
256
|
+
let multipleMediaType = false;
|
|
431
257
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
432
258
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
433
259
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
260
|
+
multipleMediaType = false;
|
|
434
261
|
json = responseObject.content["application/json"];
|
|
435
|
-
if (
|
|
262
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
263
|
+
multipleMediaType = true;
|
|
436
264
|
json = {};
|
|
437
265
|
}
|
|
438
266
|
else {
|
|
@@ -440,7 +268,7 @@ class Utils {
|
|
|
440
268
|
}
|
|
441
269
|
}
|
|
442
270
|
}
|
|
443
|
-
return json;
|
|
271
|
+
return { json, multipleMediaType };
|
|
444
272
|
}
|
|
445
273
|
static convertPathToCamelCase(path) {
|
|
446
274
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -460,10 +288,10 @@ class Utils {
|
|
|
460
288
|
return undefined;
|
|
461
289
|
}
|
|
462
290
|
}
|
|
463
|
-
static
|
|
291
|
+
static resolveEnv(str) {
|
|
464
292
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
465
|
-
let matches = placeHolderReg.exec(
|
|
466
|
-
let
|
|
293
|
+
let matches = placeHolderReg.exec(str);
|
|
294
|
+
let newStr = str;
|
|
467
295
|
while (matches != null) {
|
|
468
296
|
const envVar = matches[1];
|
|
469
297
|
const envVal = process.env[envVar];
|
|
@@ -471,17 +299,17 @@ class Utils {
|
|
|
471
299
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
472
300
|
}
|
|
473
301
|
else {
|
|
474
|
-
|
|
302
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
475
303
|
}
|
|
476
|
-
matches = placeHolderReg.exec(
|
|
304
|
+
matches = placeHolderReg.exec(str);
|
|
477
305
|
}
|
|
478
|
-
return
|
|
306
|
+
return newStr;
|
|
479
307
|
}
|
|
480
308
|
static checkServerUrl(servers) {
|
|
481
309
|
const errors = [];
|
|
482
310
|
let serverUrl;
|
|
483
311
|
try {
|
|
484
|
-
serverUrl = Utils.
|
|
312
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
485
313
|
}
|
|
486
314
|
catch (err) {
|
|
487
315
|
errors.push({
|
|
@@ -512,6 +340,7 @@ class Utils {
|
|
|
512
340
|
return errors;
|
|
513
341
|
}
|
|
514
342
|
static validateServer(spec, options) {
|
|
343
|
+
var _a;
|
|
515
344
|
const errors = [];
|
|
516
345
|
let hasTopLevelServers = false;
|
|
517
346
|
let hasPathLevelServers = false;
|
|
@@ -532,7 +361,7 @@ class Utils {
|
|
|
532
361
|
}
|
|
533
362
|
for (const method in methods) {
|
|
534
363
|
const operationObject = methods[method];
|
|
535
|
-
if (
|
|
364
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
536
365
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
537
366
|
hasOperationLevelServers = true;
|
|
538
367
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -575,6 +404,7 @@ class Utils {
|
|
|
575
404
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
576
405
|
}
|
|
577
406
|
if (isRequired && schema.default === undefined) {
|
|
407
|
+
parameter.isRequired = true;
|
|
578
408
|
requiredParams.push(parameter);
|
|
579
409
|
}
|
|
580
410
|
else {
|
|
@@ -638,6 +468,7 @@ class Utils {
|
|
|
638
468
|
}
|
|
639
469
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
640
470
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
471
|
+
parameter.isRequired = true;
|
|
641
472
|
requiredParams.push(parameter);
|
|
642
473
|
}
|
|
643
474
|
else {
|
|
@@ -657,13 +488,7 @@ class Utils {
|
|
|
657
488
|
}
|
|
658
489
|
}
|
|
659
490
|
const operationId = operationItem.operationId;
|
|
660
|
-
const parameters = [];
|
|
661
|
-
if (requiredParams.length !== 0) {
|
|
662
|
-
parameters.push(...requiredParams);
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
parameters.push(optionalParams[0]);
|
|
666
|
-
}
|
|
491
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
667
492
|
const command = {
|
|
668
493
|
context: ["compose"],
|
|
669
494
|
type: "query",
|
|
@@ -672,155 +497,882 @@ class Utils {
|
|
|
672
497
|
parameters: parameters,
|
|
673
498
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
674
499
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
500
|
+
return command;
|
|
501
|
+
}
|
|
502
|
+
static format(str, ...args) {
|
|
503
|
+
let index = 0;
|
|
504
|
+
return str.replace(/%s/g, () => {
|
|
505
|
+
const arg = args[index++];
|
|
506
|
+
return arg !== undefined ? arg : "";
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
510
|
+
if (!authName) {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
514
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
515
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
682
516
|
}
|
|
683
|
-
return
|
|
517
|
+
return safeRegistrationIdEnvName;
|
|
684
518
|
}
|
|
685
|
-
static
|
|
686
|
-
const
|
|
519
|
+
static getServerObject(spec, method, path) {
|
|
520
|
+
const pathObj = spec.paths[path];
|
|
521
|
+
const operationObject = pathObj[method];
|
|
522
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
523
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
524
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
525
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
526
|
+
return serverUrl;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Copyright (c) Microsoft Corporation.
|
|
531
|
+
class Validator {
|
|
532
|
+
listAPIs() {
|
|
533
|
+
var _a;
|
|
534
|
+
if (this.apiMap) {
|
|
535
|
+
return this.apiMap;
|
|
536
|
+
}
|
|
537
|
+
const paths = this.spec.paths;
|
|
687
538
|
const result = {};
|
|
688
539
|
for (const path in paths) {
|
|
689
540
|
const methods = paths[path];
|
|
690
541
|
for (const method in methods) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
542
|
+
const operationObject = methods[method];
|
|
543
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
544
|
+
const validateResult = this.validateAPI(method, path);
|
|
545
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
546
|
+
operation: operationObject,
|
|
547
|
+
isValid: validateResult.isValid,
|
|
548
|
+
reason: validateResult.reason,
|
|
549
|
+
};
|
|
694
550
|
}
|
|
695
551
|
}
|
|
696
552
|
}
|
|
553
|
+
this.apiMap = result;
|
|
697
554
|
return result;
|
|
698
555
|
}
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
556
|
+
validateSpecVersion() {
|
|
557
|
+
const result = { errors: [], warnings: [] };
|
|
558
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
559
|
+
result.errors.push({
|
|
560
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
561
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
562
|
+
data: this.spec.openapi,
|
|
706
563
|
});
|
|
707
564
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
validateSpecServer() {
|
|
568
|
+
const result = { errors: [], warnings: [] };
|
|
569
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
570
|
+
result.errors.push(...serverErrors);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
validateSpecNoSupportAPI() {
|
|
574
|
+
const result = { errors: [], warnings: [] };
|
|
575
|
+
const apiMap = this.listAPIs();
|
|
576
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
577
|
+
if (validAPIs.length === 0) {
|
|
578
|
+
const data = [];
|
|
579
|
+
for (const key in apiMap) {
|
|
580
|
+
const { reason } = apiMap[key];
|
|
581
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
582
|
+
data.push(apiInvalidReason);
|
|
583
|
+
}
|
|
584
|
+
result.errors.push({
|
|
725
585
|
type: ErrorType.NoSupportedApi,
|
|
726
586
|
content: ConstantString.NoSupportedApi,
|
|
587
|
+
data,
|
|
727
588
|
});
|
|
728
589
|
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
validateSpecOperationId() {
|
|
593
|
+
const result = { errors: [], warnings: [] };
|
|
594
|
+
const apiMap = this.listAPIs();
|
|
729
595
|
// OperationId missing
|
|
730
596
|
const apisMissingOperationId = [];
|
|
731
597
|
for (const key in apiMap) {
|
|
732
|
-
const
|
|
733
|
-
if (!
|
|
598
|
+
const { operation } = apiMap[key];
|
|
599
|
+
if (!operation.operationId) {
|
|
734
600
|
apisMissingOperationId.push(key);
|
|
735
601
|
}
|
|
736
602
|
}
|
|
737
603
|
if (apisMissingOperationId.length > 0) {
|
|
738
|
-
warnings.push({
|
|
604
|
+
result.warnings.push({
|
|
739
605
|
type: WarningType.OperationIdMissing,
|
|
740
606
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
741
607
|
data: apisMissingOperationId,
|
|
742
608
|
});
|
|
743
609
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
610
|
+
return result;
|
|
611
|
+
}
|
|
612
|
+
validateMethodAndPath(method, path) {
|
|
613
|
+
const result = { isValid: true, reason: [] };
|
|
614
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
615
|
+
result.isValid = false;
|
|
616
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
617
|
+
return result;
|
|
747
618
|
}
|
|
748
|
-
|
|
749
|
-
|
|
619
|
+
const pathObj = this.spec.paths[path];
|
|
620
|
+
if (!pathObj || !pathObj[method]) {
|
|
621
|
+
result.isValid = false;
|
|
622
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
623
|
+
return result;
|
|
750
624
|
}
|
|
751
|
-
return
|
|
752
|
-
status,
|
|
753
|
-
warnings,
|
|
754
|
-
errors,
|
|
755
|
-
};
|
|
625
|
+
return result;
|
|
756
626
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
627
|
+
validateResponse(method, path) {
|
|
628
|
+
const result = { isValid: true, reason: [] };
|
|
629
|
+
const operationObject = this.spec.paths[path][method];
|
|
630
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
631
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
632
|
+
// only support response body only contains “application/json” content type
|
|
633
|
+
if (multipleMediaType) {
|
|
634
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
635
|
+
}
|
|
636
|
+
else if (Object.keys(json).length === 0) {
|
|
637
|
+
// response body should not be empty
|
|
638
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
763
642
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
643
|
+
validateServer(method, path) {
|
|
644
|
+
const result = { isValid: true, reason: [] };
|
|
645
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
646
|
+
if (!serverObj) {
|
|
647
|
+
// should contain server URL
|
|
648
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
767
649
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
650
|
+
else {
|
|
651
|
+
// server url should be absolute url with https protocol
|
|
652
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
653
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
771
654
|
}
|
|
772
|
-
return
|
|
655
|
+
return result;
|
|
773
656
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
657
|
+
validateAuth(method, path) {
|
|
658
|
+
const pathObj = this.spec.paths[path];
|
|
659
|
+
const operationObject = pathObj[method];
|
|
660
|
+
const securities = operationObject.security;
|
|
661
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
662
|
+
if (authSchemeArray.length === 0) {
|
|
663
|
+
return { isValid: true, reason: [] };
|
|
664
|
+
}
|
|
665
|
+
if (this.options.allowAPIKeyAuth ||
|
|
666
|
+
this.options.allowOauth2 ||
|
|
667
|
+
this.options.allowBearerTokenAuth) {
|
|
668
|
+
// Currently we don't support multiple auth in one operation
|
|
669
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
670
|
+
return {
|
|
671
|
+
isValid: false,
|
|
672
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
for (const auths of authSchemeArray) {
|
|
676
|
+
if (auths.length === 1) {
|
|
677
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
678
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
679
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
680
|
+
return { isValid: true, reason: [] };
|
|
792
681
|
}
|
|
793
682
|
}
|
|
794
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
795
|
-
// Add the operationId if missing
|
|
796
|
-
if (!newPaths[path][methodName].operationId) {
|
|
797
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
798
|
-
}
|
|
799
683
|
}
|
|
800
|
-
newSpec.paths = newPaths;
|
|
801
|
-
return newSpec;
|
|
802
|
-
}
|
|
803
|
-
catch (err) {
|
|
804
|
-
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
805
684
|
}
|
|
685
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
686
|
+
}
|
|
687
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
688
|
+
var _a;
|
|
689
|
+
const paramResult = {
|
|
690
|
+
requiredNum: 0,
|
|
691
|
+
optionalNum: 0,
|
|
692
|
+
isValid: true,
|
|
693
|
+
reason: [],
|
|
694
|
+
};
|
|
695
|
+
if (Object.keys(schema).length === 0) {
|
|
696
|
+
return paramResult;
|
|
697
|
+
}
|
|
698
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
699
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
700
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
701
|
+
paramResult.isValid = false;
|
|
702
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
703
|
+
return paramResult;
|
|
704
|
+
}
|
|
705
|
+
if (schema.type === "string" ||
|
|
706
|
+
schema.type === "integer" ||
|
|
707
|
+
schema.type === "boolean" ||
|
|
708
|
+
schema.type === "number") {
|
|
709
|
+
if (isRequiredWithoutDefault) {
|
|
710
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (schema.type === "object") {
|
|
717
|
+
const { properties } = schema;
|
|
718
|
+
for (const property in properties) {
|
|
719
|
+
let isRequired = false;
|
|
720
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
721
|
+
isRequired = true;
|
|
722
|
+
}
|
|
723
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
724
|
+
paramResult.requiredNum += result.requiredNum;
|
|
725
|
+
paramResult.optionalNum += result.optionalNum;
|
|
726
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
727
|
+
paramResult.reason.push(...result.reason);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
732
|
+
paramResult.isValid = false;
|
|
733
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return paramResult;
|
|
737
|
+
}
|
|
738
|
+
checkParamSchema(paramObject) {
|
|
739
|
+
const paramResult = {
|
|
740
|
+
requiredNum: 0,
|
|
741
|
+
optionalNum: 0,
|
|
742
|
+
isValid: true,
|
|
743
|
+
reason: [],
|
|
744
|
+
};
|
|
745
|
+
if (!paramObject) {
|
|
746
|
+
return paramResult;
|
|
747
|
+
}
|
|
748
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
749
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
750
|
+
const param = paramObject[i];
|
|
751
|
+
const schema = param.schema;
|
|
752
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
753
|
+
paramResult.isValid = false;
|
|
754
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
758
|
+
if (isCopilot) {
|
|
759
|
+
if (isRequiredWithoutDefault) {
|
|
760
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
768
|
+
if (isRequiredWithoutDefault) {
|
|
769
|
+
paramResult.isValid = false;
|
|
770
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (schema.type !== "boolean" &&
|
|
775
|
+
schema.type !== "string" &&
|
|
776
|
+
schema.type !== "number" &&
|
|
777
|
+
schema.type !== "integer") {
|
|
778
|
+
if (isRequiredWithoutDefault) {
|
|
779
|
+
paramResult.isValid = false;
|
|
780
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
781
|
+
}
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (param.in === "query" || param.in === "path") {
|
|
785
|
+
if (isRequiredWithoutDefault) {
|
|
786
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return paramResult;
|
|
794
|
+
}
|
|
795
|
+
hasNestedObjectInSchema(schema) {
|
|
796
|
+
if (schema.type === "object") {
|
|
797
|
+
for (const property in schema.properties) {
|
|
798
|
+
const nestedSchema = schema.properties[property];
|
|
799
|
+
if (nestedSchema.type === "object") {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Copyright (c) Microsoft Corporation.
|
|
809
|
+
class CopilotValidator extends Validator {
|
|
810
|
+
constructor(spec, options) {
|
|
811
|
+
super();
|
|
812
|
+
this.projectType = ProjectType.Copilot;
|
|
813
|
+
this.options = options;
|
|
814
|
+
this.spec = spec;
|
|
815
|
+
}
|
|
816
|
+
validateSpec() {
|
|
817
|
+
const result = { errors: [], warnings: [] };
|
|
818
|
+
// validate spec version
|
|
819
|
+
let validationResult = this.validateSpecVersion();
|
|
820
|
+
result.errors.push(...validationResult.errors);
|
|
821
|
+
// validate spec server
|
|
822
|
+
validationResult = this.validateSpecServer();
|
|
823
|
+
result.errors.push(...validationResult.errors);
|
|
824
|
+
// validate no supported API
|
|
825
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
826
|
+
result.errors.push(...validationResult.errors);
|
|
827
|
+
// validate operationId missing
|
|
828
|
+
validationResult = this.validateSpecOperationId();
|
|
829
|
+
result.warnings.push(...validationResult.warnings);
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
validateAPI(method, path) {
|
|
833
|
+
const result = { isValid: true, reason: [] };
|
|
834
|
+
method = method.toLocaleLowerCase();
|
|
835
|
+
// validate method and path
|
|
836
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
837
|
+
if (!methodAndPathResult.isValid) {
|
|
838
|
+
return methodAndPathResult;
|
|
839
|
+
}
|
|
840
|
+
const operationObject = this.spec.paths[path][method];
|
|
841
|
+
// validate auth
|
|
842
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
843
|
+
result.reason.push(...authCheckResult.reason);
|
|
844
|
+
// validate operationId
|
|
845
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
846
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
847
|
+
}
|
|
848
|
+
// validate server
|
|
849
|
+
const validateServerResult = this.validateServer(method, path);
|
|
850
|
+
result.reason.push(...validateServerResult.reason);
|
|
851
|
+
// validate response
|
|
852
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
853
|
+
result.reason.push(...validateResponseResult.reason);
|
|
854
|
+
// validate requestBody
|
|
855
|
+
const requestBody = operationObject.requestBody;
|
|
856
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
857
|
+
if (requestJsonBody) {
|
|
858
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
859
|
+
if (requestBodySchema.type !== "object") {
|
|
860
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
861
|
+
}
|
|
862
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
863
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
864
|
+
}
|
|
865
|
+
// validate parameters
|
|
866
|
+
const paramObject = operationObject.parameters;
|
|
867
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
868
|
+
result.reason.push(...paramResult.reason);
|
|
869
|
+
if (result.reason.length > 0) {
|
|
870
|
+
result.isValid = false;
|
|
871
|
+
}
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Copyright (c) Microsoft Corporation.
|
|
877
|
+
class SMEValidator extends Validator {
|
|
878
|
+
constructor(spec, options) {
|
|
879
|
+
super();
|
|
880
|
+
this.projectType = ProjectType.SME;
|
|
881
|
+
this.options = options;
|
|
882
|
+
this.spec = spec;
|
|
883
|
+
}
|
|
884
|
+
validateSpec() {
|
|
885
|
+
const result = { errors: [], warnings: [] };
|
|
886
|
+
// validate spec version
|
|
887
|
+
let validationResult = this.validateSpecVersion();
|
|
888
|
+
result.errors.push(...validationResult.errors);
|
|
889
|
+
// validate spec server
|
|
890
|
+
validationResult = this.validateSpecServer();
|
|
891
|
+
result.errors.push(...validationResult.errors);
|
|
892
|
+
// validate no supported API
|
|
893
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
894
|
+
result.errors.push(...validationResult.errors);
|
|
895
|
+
// validate operationId missing
|
|
896
|
+
if (this.options.allowMissingId) {
|
|
897
|
+
validationResult = this.validateSpecOperationId();
|
|
898
|
+
result.warnings.push(...validationResult.warnings);
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
validateAPI(method, path) {
|
|
903
|
+
const result = { isValid: true, reason: [] };
|
|
904
|
+
method = method.toLocaleLowerCase();
|
|
905
|
+
// validate method and path
|
|
906
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
907
|
+
if (!methodAndPathResult.isValid) {
|
|
908
|
+
return methodAndPathResult;
|
|
909
|
+
}
|
|
910
|
+
const operationObject = this.spec.paths[path][method];
|
|
911
|
+
// validate auth
|
|
912
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
913
|
+
result.reason.push(...authCheckResult.reason);
|
|
914
|
+
// validate operationId
|
|
915
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
916
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
917
|
+
}
|
|
918
|
+
// validate server
|
|
919
|
+
const validateServerResult = this.validateServer(method, path);
|
|
920
|
+
result.reason.push(...validateServerResult.reason);
|
|
921
|
+
// validate response
|
|
922
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
923
|
+
result.reason.push(...validateResponseResult.reason);
|
|
924
|
+
let postBodyResult = {
|
|
925
|
+
requiredNum: 0,
|
|
926
|
+
optionalNum: 0,
|
|
927
|
+
isValid: true,
|
|
928
|
+
reason: [],
|
|
929
|
+
};
|
|
930
|
+
// validate requestBody
|
|
931
|
+
const requestBody = operationObject.requestBody;
|
|
932
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
933
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
934
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
935
|
+
}
|
|
936
|
+
if (requestJsonBody) {
|
|
937
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
938
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
939
|
+
result.reason.push(...postBodyResult.reason);
|
|
940
|
+
}
|
|
941
|
+
// validate parameters
|
|
942
|
+
const paramObject = operationObject.parameters;
|
|
943
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
944
|
+
result.reason.push(...paramResult.reason);
|
|
945
|
+
// validate total parameters count
|
|
946
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
947
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
948
|
+
result.reason.push(...paramCountResult.reason);
|
|
949
|
+
}
|
|
950
|
+
if (result.reason.length > 0) {
|
|
951
|
+
result.isValid = false;
|
|
952
|
+
}
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
956
|
+
const result = { isValid: true, reason: [] };
|
|
957
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
958
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
959
|
+
if (totalRequiredParams > 1) {
|
|
960
|
+
if (!this.options.allowMultipleParameters ||
|
|
961
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
962
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
else if (totalParams === 0) {
|
|
966
|
+
result.reason.push(ErrorType.NoParameter);
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
972
|
+
|
|
973
|
+
// Copyright (c) Microsoft Corporation.
|
|
974
|
+
class TeamsAIValidator extends Validator {
|
|
975
|
+
constructor(spec, options) {
|
|
976
|
+
super();
|
|
977
|
+
this.projectType = ProjectType.TeamsAi;
|
|
978
|
+
this.options = options;
|
|
979
|
+
this.spec = spec;
|
|
980
|
+
}
|
|
981
|
+
validateSpec() {
|
|
982
|
+
const result = { errors: [], warnings: [] };
|
|
983
|
+
// validate spec server
|
|
984
|
+
let validationResult = this.validateSpecServer();
|
|
985
|
+
result.errors.push(...validationResult.errors);
|
|
986
|
+
// validate no supported API
|
|
987
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
988
|
+
result.errors.push(...validationResult.errors);
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
validateAPI(method, path) {
|
|
992
|
+
const result = { isValid: true, reason: [] };
|
|
993
|
+
method = method.toLocaleLowerCase();
|
|
994
|
+
// validate method and path
|
|
995
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
996
|
+
if (!methodAndPathResult.isValid) {
|
|
997
|
+
return methodAndPathResult;
|
|
998
|
+
}
|
|
999
|
+
const operationObject = this.spec.paths[path][method];
|
|
1000
|
+
// validate operationId
|
|
1001
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1002
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1003
|
+
}
|
|
1004
|
+
// validate server
|
|
1005
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1006
|
+
result.reason.push(...validateServerResult.reason);
|
|
1007
|
+
if (result.reason.length > 0) {
|
|
1008
|
+
result.isValid = false;
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
class ValidatorFactory {
|
|
1015
|
+
static create(spec, options) {
|
|
1016
|
+
var _a;
|
|
1017
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1018
|
+
switch (type) {
|
|
1019
|
+
case ProjectType.SME:
|
|
1020
|
+
return new SMEValidator(spec, options);
|
|
1021
|
+
case ProjectType.Copilot:
|
|
1022
|
+
return new CopilotValidator(spec, options);
|
|
1023
|
+
case ProjectType.TeamsAi:
|
|
1024
|
+
return new TeamsAIValidator(spec, options);
|
|
1025
|
+
default:
|
|
1026
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Copyright (c) Microsoft Corporation.
|
|
1032
|
+
class SpecFilter {
|
|
1033
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1034
|
+
var _a;
|
|
1035
|
+
try {
|
|
1036
|
+
const newSpec = Object.assign({}, unResolveSpec);
|
|
1037
|
+
const newPaths = {};
|
|
1038
|
+
for (const filterItem of filter) {
|
|
1039
|
+
const [method, path] = filterItem.split(" ");
|
|
1040
|
+
const methodName = method.toLowerCase();
|
|
1041
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1042
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1043
|
+
pathObj &&
|
|
1044
|
+
pathObj[methodName]) {
|
|
1045
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1046
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1047
|
+
if (!validateResult.isValid) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (!newPaths[path]) {
|
|
1051
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1052
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1053
|
+
delete newPaths[path][m];
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1057
|
+
// Add the operationId if missing
|
|
1058
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1059
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
newSpec.paths = newPaths;
|
|
1064
|
+
return newSpec;
|
|
1065
|
+
}
|
|
1066
|
+
catch (err) {
|
|
1067
|
+
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Copyright (c) Microsoft Corporation.
|
|
1073
|
+
class AdaptiveCardGenerator {
|
|
1074
|
+
static generateAdaptiveCard(operationItem) {
|
|
1075
|
+
try {
|
|
1076
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1077
|
+
let cardBody = [];
|
|
1078
|
+
let schema = json.schema;
|
|
1079
|
+
let jsonPath = "$";
|
|
1080
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
1081
|
+
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1082
|
+
if (jsonPath !== "$") {
|
|
1083
|
+
schema = schema.properties[jsonPath];
|
|
1084
|
+
}
|
|
1085
|
+
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1086
|
+
}
|
|
1087
|
+
// if no schema, try to use example value
|
|
1088
|
+
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1089
|
+
cardBody = [
|
|
1090
|
+
{
|
|
1091
|
+
type: ConstantString.TextBlockType,
|
|
1092
|
+
text: "${jsonStringify($root)}",
|
|
1093
|
+
wrap: true,
|
|
1094
|
+
},
|
|
1095
|
+
];
|
|
1096
|
+
}
|
|
1097
|
+
// if no example value, use default success response
|
|
1098
|
+
if (cardBody.length === 0) {
|
|
1099
|
+
cardBody = [
|
|
1100
|
+
{
|
|
1101
|
+
type: ConstantString.TextBlockType,
|
|
1102
|
+
text: "success",
|
|
1103
|
+
wrap: true,
|
|
1104
|
+
},
|
|
1105
|
+
];
|
|
1106
|
+
}
|
|
1107
|
+
const fullCard = {
|
|
1108
|
+
type: ConstantString.AdaptiveCardType,
|
|
1109
|
+
$schema: ConstantString.AdaptiveCardSchema,
|
|
1110
|
+
version: ConstantString.AdaptiveCardVersion,
|
|
1111
|
+
body: cardBody,
|
|
1112
|
+
};
|
|
1113
|
+
return [fullCard, jsonPath];
|
|
1114
|
+
}
|
|
1115
|
+
catch (err) {
|
|
1116
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1120
|
+
if (schema.type === "array") {
|
|
1121
|
+
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1122
|
+
if (Object.keys(schema.items).length === 0) {
|
|
1123
|
+
return [
|
|
1124
|
+
{
|
|
1125
|
+
type: ConstantString.TextBlockType,
|
|
1126
|
+
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1127
|
+
wrap: true,
|
|
1128
|
+
},
|
|
1129
|
+
];
|
|
1130
|
+
}
|
|
1131
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1132
|
+
const template = {
|
|
1133
|
+
type: ConstantString.ContainerType,
|
|
1134
|
+
$data: name ? `\${${name}}` : "${$root}",
|
|
1135
|
+
items: Array(),
|
|
1136
|
+
};
|
|
1137
|
+
template.items.push(...obj);
|
|
1138
|
+
return [template];
|
|
1139
|
+
}
|
|
1140
|
+
// some schema may not contain type but contain properties
|
|
1141
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1142
|
+
const { properties } = schema;
|
|
1143
|
+
const result = [];
|
|
1144
|
+
for (const property in properties) {
|
|
1145
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1146
|
+
result.push(...obj);
|
|
1147
|
+
}
|
|
1148
|
+
if (schema.additionalProperties) {
|
|
1149
|
+
// TODO: better ways to handler warnings.
|
|
1150
|
+
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1151
|
+
}
|
|
1152
|
+
return result;
|
|
1153
|
+
}
|
|
1154
|
+
if (schema.type === "string" ||
|
|
1155
|
+
schema.type === "integer" ||
|
|
1156
|
+
schema.type === "boolean" ||
|
|
1157
|
+
schema.type === "number") {
|
|
1158
|
+
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1159
|
+
// string in root: "ddd"
|
|
1160
|
+
let text = "result: ${$root}";
|
|
1161
|
+
if (name) {
|
|
1162
|
+
// object { id: "1" }
|
|
1163
|
+
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1164
|
+
if (parentArrayName) {
|
|
1165
|
+
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1166
|
+
text = `${parentArrayName}.${text}`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
else if (parentArrayName) {
|
|
1170
|
+
// string array: photoUrls: ["1", "2"]
|
|
1171
|
+
text = `${parentArrayName}: ` + "${$data}";
|
|
1172
|
+
}
|
|
1173
|
+
return [
|
|
1174
|
+
{
|
|
1175
|
+
type: ConstantString.TextBlockType,
|
|
1176
|
+
text,
|
|
1177
|
+
wrap: true,
|
|
1178
|
+
},
|
|
1179
|
+
];
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
if (name) {
|
|
1183
|
+
return [
|
|
1184
|
+
{
|
|
1185
|
+
type: "Image",
|
|
1186
|
+
url: `\${${name}}`,
|
|
1187
|
+
$when: `\${${name} != null}`,
|
|
1188
|
+
},
|
|
1189
|
+
];
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
return [
|
|
1193
|
+
{
|
|
1194
|
+
type: "Image",
|
|
1195
|
+
url: "${$data}",
|
|
1196
|
+
$when: "${$data != null}",
|
|
1197
|
+
},
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1203
|
+
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1204
|
+
}
|
|
1205
|
+
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1206
|
+
}
|
|
1207
|
+
// Find the first array property in the response schema object with the well-known name
|
|
1208
|
+
static getResponseJsonPathFromSchema(schema) {
|
|
1209
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1210
|
+
const { properties } = schema;
|
|
1211
|
+
for (const property in properties) {
|
|
1212
|
+
const schema = properties[property];
|
|
1213
|
+
if (schema.type === "array" &&
|
|
1214
|
+
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1215
|
+
return property;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return "$";
|
|
1220
|
+
}
|
|
1221
|
+
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1222
|
+
const propertyName = name ? name : parentArrayName;
|
|
1223
|
+
return (!!propertyName &&
|
|
1224
|
+
schema.type === "string" &&
|
|
1225
|
+
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1226
|
+
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Copyright (c) Microsoft Corporation.
|
|
1231
|
+
function wrapAdaptiveCard(card, jsonPath) {
|
|
1232
|
+
const result = {
|
|
1233
|
+
version: ConstantString.WrappedCardVersion,
|
|
1234
|
+
$schema: ConstantString.WrappedCardSchema,
|
|
1235
|
+
jsonPath: jsonPath,
|
|
1236
|
+
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1237
|
+
responseCardTemplate: card,
|
|
1238
|
+
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1239
|
+
};
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1242
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1243
|
+
const props = inferProperties(card);
|
|
1244
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1245
|
+
const result = {
|
|
1246
|
+
data_path: dataPath,
|
|
1247
|
+
};
|
|
1248
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1249
|
+
result.properties = {};
|
|
1250
|
+
if (props.title) {
|
|
1251
|
+
result.properties.title = "$." + props.title;
|
|
1252
|
+
}
|
|
1253
|
+
if (props.subtitle) {
|
|
1254
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1255
|
+
}
|
|
1256
|
+
if (props.imageUrl) {
|
|
1257
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
result.static_template = card;
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1265
|
+
* The preview card template includes a title and an optional subtitle and image.
|
|
1266
|
+
* It populates the preview card template with the first text block that matches
|
|
1267
|
+
* each well-known name, in the order of title, subtitle, and image.
|
|
1268
|
+
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1269
|
+
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1270
|
+
* @param card The Adaptive Card to infer the preview card template from.
|
|
1271
|
+
* @param jsonPath The JSON path to the root object in the card body.
|
|
1272
|
+
* @returns The inferred preview card template.
|
|
1273
|
+
*/
|
|
1274
|
+
function inferPreviewCardTemplate(card) {
|
|
1275
|
+
const result = {
|
|
1276
|
+
title: "result",
|
|
1277
|
+
};
|
|
1278
|
+
const inferredProperties = inferProperties(card);
|
|
1279
|
+
if (inferredProperties.title) {
|
|
1280
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1281
|
+
}
|
|
1282
|
+
if (inferredProperties.subtitle) {
|
|
1283
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1284
|
+
}
|
|
1285
|
+
if (inferredProperties.imageUrl) {
|
|
1286
|
+
result.image = {
|
|
1287
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1288
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1289
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return result;
|
|
1293
|
+
}
|
|
1294
|
+
function inferProperties(card) {
|
|
1295
|
+
var _a;
|
|
1296
|
+
const result = {};
|
|
1297
|
+
const nameSet = new Set();
|
|
1298
|
+
let rootObject;
|
|
1299
|
+
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1300
|
+
rootObject = card.body[0].items;
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
rootObject = card.body;
|
|
1304
|
+
}
|
|
1305
|
+
for (const element of rootObject) {
|
|
1306
|
+
if (element.type === ConstantString.TextBlockType) {
|
|
1307
|
+
const textElement = element;
|
|
1308
|
+
const index = textElement.text.indexOf("${if(");
|
|
1309
|
+
if (index > 0) {
|
|
1310
|
+
const text = textElement.text.substring(index);
|
|
1311
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1312
|
+
const property = match ? match[1] : "";
|
|
1313
|
+
if (property) {
|
|
1314
|
+
nameSet.add(property);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1319
|
+
const imageElement = element;
|
|
1320
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1321
|
+
const property = match ? match[1] : "";
|
|
1322
|
+
if (property) {
|
|
1323
|
+
nameSet.add(property);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
for (const name of nameSet) {
|
|
1328
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1329
|
+
result.title = name;
|
|
1330
|
+
nameSet.delete(name);
|
|
1331
|
+
}
|
|
1332
|
+
else if (!result.subtitle &&
|
|
1333
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1334
|
+
result.subtitle = name;
|
|
1335
|
+
nameSet.delete(name);
|
|
1336
|
+
}
|
|
1337
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1338
|
+
result.imageUrl = name;
|
|
1339
|
+
nameSet.delete(name);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const name of nameSet) {
|
|
1343
|
+
if (!result.title) {
|
|
1344
|
+
result.title = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1346
|
+
}
|
|
1347
|
+
else if (!result.subtitle) {
|
|
1348
|
+
result.subtitle = name;
|
|
1349
|
+
nameSet.delete(name);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (!result.title && result.subtitle) {
|
|
1353
|
+
result.title = result.subtitle;
|
|
1354
|
+
delete result.subtitle;
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Copyright (c) Microsoft Corporation.
|
|
1360
|
+
class ManifestUpdater {
|
|
1361
|
+
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
|
|
1362
|
+
const manifest = await fs.readJSON(manifestPath);
|
|
1363
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1364
|
+
manifest.plugins = [
|
|
1365
|
+
{
|
|
1366
|
+
file: apiPluginRelativePath,
|
|
1367
|
+
id: ConstantString.DefaultPluginId,
|
|
1368
|
+
},
|
|
1369
|
+
];
|
|
1370
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1371
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1372
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1373
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
|
|
1374
|
+
return [manifest, apiPlugin];
|
|
806
1375
|
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Copyright (c) Microsoft Corporation.
|
|
810
|
-
class ManifestUpdater {
|
|
811
|
-
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
|
|
812
|
-
const manifest = await fs.readJSON(manifestPath);
|
|
813
|
-
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
814
|
-
manifest.plugins = [
|
|
815
|
-
{
|
|
816
|
-
pluginFile: apiPluginRelativePath,
|
|
817
|
-
},
|
|
818
|
-
];
|
|
819
|
-
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
820
|
-
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
821
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
|
|
822
|
-
return [manifest, apiPlugin];
|
|
823
|
-
}
|
|
824
1376
|
static updateManifestDescription(manifest, spec) {
|
|
825
1377
|
var _a, _b;
|
|
826
1378
|
manifest.description = {
|
|
@@ -830,23 +1382,55 @@ class ManifestUpdater {
|
|
|
830
1382
|
}
|
|
831
1383
|
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
832
1384
|
let parameter;
|
|
833
|
-
if (schema.type === "
|
|
1385
|
+
if (schema.type === "array") {
|
|
1386
|
+
const items = schema.items;
|
|
1387
|
+
parameter = {
|
|
1388
|
+
type: "array",
|
|
1389
|
+
items: ManifestUpdater.mapOpenAPISchemaToFuncParam(items, method, pathUrl),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
else if (schema.type === "string" ||
|
|
834
1393
|
schema.type === "boolean" ||
|
|
835
1394
|
schema.type === "integer" ||
|
|
836
|
-
schema.type === "number"
|
|
837
|
-
|
|
838
|
-
|
|
1395
|
+
schema.type === "number") {
|
|
1396
|
+
parameter = {
|
|
1397
|
+
type: schema.type,
|
|
1398
|
+
};
|
|
839
1399
|
}
|
|
840
1400
|
else {
|
|
841
1401
|
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
|
|
842
1402
|
}
|
|
1403
|
+
if (schema.enum) {
|
|
1404
|
+
parameter.enum = schema.enum;
|
|
1405
|
+
}
|
|
1406
|
+
if (schema.description) {
|
|
1407
|
+
parameter.description = schema.description;
|
|
1408
|
+
}
|
|
1409
|
+
if (schema.default) {
|
|
1410
|
+
parameter.default = schema.default;
|
|
1411
|
+
}
|
|
843
1412
|
return parameter;
|
|
844
1413
|
}
|
|
845
|
-
static generatePluginManifestSchema(spec, specRelativePath, options) {
|
|
846
|
-
var _a, _b, _c;
|
|
1414
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
|
|
1415
|
+
var _a, _b, _c, _d, _e;
|
|
847
1416
|
const functions = [];
|
|
848
1417
|
const functionNames = [];
|
|
1418
|
+
const conversationStarters = [];
|
|
849
1419
|
const paths = spec.paths;
|
|
1420
|
+
const pluginAuthObj = {
|
|
1421
|
+
type: "None",
|
|
1422
|
+
};
|
|
1423
|
+
if (authInfo) {
|
|
1424
|
+
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
|
|
1425
|
+
pluginAuthObj.type = "OAuthPluginVault";
|
|
1426
|
+
}
|
|
1427
|
+
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
|
|
1428
|
+
pluginAuthObj.type = "ApiKeyPluginVault";
|
|
1429
|
+
}
|
|
1430
|
+
if (pluginAuthObj.type !== "None") {
|
|
1431
|
+
pluginAuthObj.reference_id = `${Utils.getSafeRegistrationIdEnvName(authInfo.name)}_REGISTRATION_ID`;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
850
1434
|
for (const pathUrl in paths) {
|
|
851
1435
|
const pathItem = paths[pathUrl];
|
|
852
1436
|
if (pathItem) {
|
|
@@ -854,6 +1438,7 @@ class ManifestUpdater {
|
|
|
854
1438
|
for (const method in operations) {
|
|
855
1439
|
if (options.allowMethods.includes(method)) {
|
|
856
1440
|
const operationItem = operations[method];
|
|
1441
|
+
const confirmationBodies = [];
|
|
857
1442
|
if (operationItem) {
|
|
858
1443
|
const operationId = operationItem.operationId;
|
|
859
1444
|
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
@@ -869,6 +1454,7 @@ class ManifestUpdater {
|
|
|
869
1454
|
const param = paramObject[i];
|
|
870
1455
|
const schema = param.schema;
|
|
871
1456
|
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1457
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
|
|
872
1458
|
if (param.required) {
|
|
873
1459
|
parameters.required.push(param.name);
|
|
874
1460
|
}
|
|
@@ -887,6 +1473,7 @@ class ManifestUpdater {
|
|
|
887
1473
|
for (const property in requestBodySchema.properties) {
|
|
888
1474
|
const schema = requestBodySchema.properties[property];
|
|
889
1475
|
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1476
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
|
|
890
1477
|
}
|
|
891
1478
|
}
|
|
892
1479
|
else {
|
|
@@ -896,33 +1483,105 @@ class ManifestUpdater {
|
|
|
896
1483
|
const funcObj = {
|
|
897
1484
|
name: operationId,
|
|
898
1485
|
description: description,
|
|
899
|
-
parameters: parameters,
|
|
900
1486
|
};
|
|
1487
|
+
if (paramObject || requestBody) {
|
|
1488
|
+
funcObj.parameters = parameters;
|
|
1489
|
+
}
|
|
1490
|
+
if (options.allowResponseSemantics) {
|
|
1491
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1492
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1493
|
+
funcObj.capabilities = {
|
|
1494
|
+
response_semantics: responseSemantic,
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
|
|
1498
|
+
if (!funcObj.capabilities) {
|
|
1499
|
+
funcObj.capabilities = {};
|
|
1500
|
+
}
|
|
1501
|
+
funcObj.capabilities.confirmation = {
|
|
1502
|
+
type: "AdaptiveCard",
|
|
1503
|
+
title: (_c = operationItem.summary) !== null && _c !== void 0 ? _c : description,
|
|
1504
|
+
};
|
|
1505
|
+
if (confirmationBodies.length > 0) {
|
|
1506
|
+
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
901
1509
|
functions.push(funcObj);
|
|
902
1510
|
functionNames.push(operationId);
|
|
1511
|
+
if (description) {
|
|
1512
|
+
conversationStarters.push(description);
|
|
1513
|
+
}
|
|
903
1514
|
}
|
|
904
1515
|
}
|
|
905
1516
|
}
|
|
906
1517
|
}
|
|
907
1518
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1519
|
+
let apiPlugin;
|
|
1520
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1521
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
apiPlugin = {
|
|
1525
|
+
schema_version: "v2.1",
|
|
1526
|
+
name_for_human: "",
|
|
1527
|
+
description_for_human: "",
|
|
1528
|
+
namespace: "",
|
|
1529
|
+
functions: [],
|
|
1530
|
+
runtimes: [],
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1534
|
+
for (const func of functions) {
|
|
1535
|
+
const index = (_d = apiPlugin.functions) === null || _d === void 0 ? void 0 : _d.findIndex((f) => f.name === func.name);
|
|
1536
|
+
if (index === -1) {
|
|
1537
|
+
apiPlugin.functions.push(func);
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
apiPlugin.functions[index] = func;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1544
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => {
|
|
1545
|
+
var _a, _b;
|
|
1546
|
+
return runtime.spec.url === specRelativePath &&
|
|
1547
|
+
runtime.type === "OpenApi" &&
|
|
1548
|
+
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
|
|
1549
|
+
});
|
|
1550
|
+
if (index === -1) {
|
|
1551
|
+
apiPlugin.runtimes.push({
|
|
1552
|
+
type: "OpenApi",
|
|
1553
|
+
auth: pluginAuthObj,
|
|
1554
|
+
spec: {
|
|
1555
|
+
url: specRelativePath,
|
|
923
1556
|
},
|
|
924
|
-
|
|
925
|
-
|
|
1557
|
+
run_for_functions: functionNames,
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1562
|
+
}
|
|
1563
|
+
if (!apiPlugin.name_for_human) {
|
|
1564
|
+
apiPlugin.name_for_human = appName;
|
|
1565
|
+
}
|
|
1566
|
+
if (!apiPlugin.namespace) {
|
|
1567
|
+
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
|
|
1568
|
+
}
|
|
1569
|
+
if (!apiPlugin.description_for_human) {
|
|
1570
|
+
apiPlugin.description_for_human =
|
|
1571
|
+
(_e = spec.info.description) !== null && _e !== void 0 ? _e : "<Please add description of the plugin>";
|
|
1572
|
+
}
|
|
1573
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1574
|
+
if (!apiPlugin.capabilities) {
|
|
1575
|
+
apiPlugin.capabilities = {
|
|
1576
|
+
localization: {},
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1580
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1581
|
+
.slice(0, 5)
|
|
1582
|
+
.map((text) => ({ text }));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
926
1585
|
return apiPlugin;
|
|
927
1586
|
}
|
|
928
1587
|
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
@@ -991,16 +1650,26 @@ class ManifestUpdater {
|
|
|
991
1650
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
992
1651
|
const operationItem = operations[method];
|
|
993
1652
|
if (operationItem) {
|
|
994
|
-
const
|
|
1653
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1654
|
+
if (command.parameters &&
|
|
1655
|
+
command.parameters.length >= 1 &&
|
|
1656
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1657
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1658
|
+
}
|
|
1659
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1660
|
+
command.parameters = [command.parameters[0]];
|
|
1661
|
+
warnings.push({
|
|
1662
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1663
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1664
|
+
data: command.id,
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
995
1667
|
if (adaptiveCardFolder) {
|
|
996
1668
|
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
997
1669
|
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
998
1670
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
999
1671
|
: "";
|
|
1000
1672
|
}
|
|
1001
|
-
if (warning) {
|
|
1002
|
-
warnings.push(warning);
|
|
1003
|
-
}
|
|
1004
1673
|
commands.push(command);
|
|
1005
1674
|
}
|
|
1006
1675
|
}
|
|
@@ -1014,255 +1683,21 @@ class ManifestUpdater {
|
|
|
1014
1683
|
const relativePath = path.relative(path.dirname(from), to);
|
|
1015
1684
|
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1016
1685
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1024
|
-
let cardBody = [];
|
|
1025
|
-
let schema = json.schema;
|
|
1026
|
-
let jsonPath = "$";
|
|
1027
|
-
if (schema && Object.keys(schema).length > 0) {
|
|
1028
|
-
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1029
|
-
if (jsonPath !== "$") {
|
|
1030
|
-
schema = schema.properties[jsonPath];
|
|
1031
|
-
}
|
|
1032
|
-
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1033
|
-
}
|
|
1034
|
-
// if no schema, try to use example value
|
|
1035
|
-
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1036
|
-
cardBody = [
|
|
1037
|
-
{
|
|
1038
|
-
type: ConstantString.TextBlockType,
|
|
1039
|
-
text: "${jsonStringify($root)}",
|
|
1040
|
-
wrap: true,
|
|
1041
|
-
},
|
|
1042
|
-
];
|
|
1043
|
-
}
|
|
1044
|
-
// if no example value, use default success response
|
|
1045
|
-
if (cardBody.length === 0) {
|
|
1046
|
-
cardBody = [
|
|
1047
|
-
{
|
|
1048
|
-
type: ConstantString.TextBlockType,
|
|
1049
|
-
text: "success",
|
|
1050
|
-
wrap: true,
|
|
1051
|
-
},
|
|
1052
|
-
];
|
|
1053
|
-
}
|
|
1054
|
-
const fullCard = {
|
|
1055
|
-
type: ConstantString.AdaptiveCardType,
|
|
1056
|
-
$schema: ConstantString.AdaptiveCardSchema,
|
|
1057
|
-
version: ConstantString.AdaptiveCardVersion,
|
|
1058
|
-
body: cardBody,
|
|
1059
|
-
};
|
|
1060
|
-
return [fullCard, jsonPath];
|
|
1061
|
-
}
|
|
1062
|
-
catch (err) {
|
|
1063
|
-
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1067
|
-
if (schema.type === "array") {
|
|
1068
|
-
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1069
|
-
if (Object.keys(schema.items).length === 0) {
|
|
1070
|
-
return [
|
|
1071
|
-
{
|
|
1072
|
-
type: ConstantString.TextBlockType,
|
|
1073
|
-
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1074
|
-
wrap: true,
|
|
1075
|
-
},
|
|
1076
|
-
];
|
|
1077
|
-
}
|
|
1078
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1079
|
-
const template = {
|
|
1080
|
-
type: ConstantString.ContainerType,
|
|
1081
|
-
$data: name ? `\${${name}}` : "${$root}",
|
|
1082
|
-
items: Array(),
|
|
1083
|
-
};
|
|
1084
|
-
template.items.push(...obj);
|
|
1085
|
-
return [template];
|
|
1086
|
-
}
|
|
1087
|
-
// some schema may not contain type but contain properties
|
|
1088
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1089
|
-
const { properties } = schema;
|
|
1090
|
-
const result = [];
|
|
1091
|
-
for (const property in properties) {
|
|
1092
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1093
|
-
result.push(...obj);
|
|
1094
|
-
}
|
|
1095
|
-
if (schema.additionalProperties) {
|
|
1096
|
-
// TODO: better ways to handler warnings.
|
|
1097
|
-
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1098
|
-
}
|
|
1099
|
-
return result;
|
|
1100
|
-
}
|
|
1101
|
-
if (schema.type === "string" ||
|
|
1102
|
-
schema.type === "integer" ||
|
|
1103
|
-
schema.type === "boolean" ||
|
|
1104
|
-
schema.type === "number") {
|
|
1105
|
-
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1106
|
-
// string in root: "ddd"
|
|
1107
|
-
let text = "result: ${$root}";
|
|
1108
|
-
if (name) {
|
|
1109
|
-
// object { id: "1" }
|
|
1110
|
-
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1111
|
-
if (parentArrayName) {
|
|
1112
|
-
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1113
|
-
text = `${parentArrayName}.${text}`;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
else if (parentArrayName) {
|
|
1117
|
-
// string array: photoUrls: ["1", "2"]
|
|
1118
|
-
text = `${parentArrayName}: ` + "${$data}";
|
|
1119
|
-
}
|
|
1120
|
-
return [
|
|
1121
|
-
{
|
|
1122
|
-
type: ConstantString.TextBlockType,
|
|
1123
|
-
text,
|
|
1124
|
-
wrap: true,
|
|
1125
|
-
},
|
|
1126
|
-
];
|
|
1127
|
-
}
|
|
1128
|
-
else {
|
|
1129
|
-
if (name) {
|
|
1130
|
-
return [
|
|
1131
|
-
{
|
|
1132
|
-
type: "Image",
|
|
1133
|
-
url: `\${${name}}`,
|
|
1134
|
-
$when: `\${${name} != null}`,
|
|
1135
|
-
},
|
|
1136
|
-
];
|
|
1137
|
-
}
|
|
1138
|
-
else {
|
|
1139
|
-
return [
|
|
1140
|
-
{
|
|
1141
|
-
type: "Image",
|
|
1142
|
-
url: "${$data}",
|
|
1143
|
-
$when: "${$data != null}",
|
|
1144
|
-
},
|
|
1145
|
-
];
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1150
|
-
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1151
|
-
}
|
|
1152
|
-
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1153
|
-
}
|
|
1154
|
-
// Find the first array property in the response schema object with the well-known name
|
|
1155
|
-
static getResponseJsonPathFromSchema(schema) {
|
|
1156
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1157
|
-
const { properties } = schema;
|
|
1158
|
-
for (const property in properties) {
|
|
1159
|
-
const schema = properties[property];
|
|
1160
|
-
if (schema.type === "array" &&
|
|
1161
|
-
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1162
|
-
return property;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return "$";
|
|
1167
|
-
}
|
|
1168
|
-
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1169
|
-
const propertyName = name ? name : parentArrayName;
|
|
1170
|
-
return (!!propertyName &&
|
|
1171
|
-
schema.type === "string" &&
|
|
1172
|
-
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1173
|
-
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// Copyright (c) Microsoft Corporation.
|
|
1178
|
-
function wrapAdaptiveCard(card, jsonPath) {
|
|
1179
|
-
const result = {
|
|
1180
|
-
version: ConstantString.WrappedCardVersion,
|
|
1181
|
-
$schema: ConstantString.WrappedCardSchema,
|
|
1182
|
-
jsonPath: jsonPath,
|
|
1183
|
-
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1184
|
-
responseCardTemplate: card,
|
|
1185
|
-
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1186
|
-
};
|
|
1187
|
-
return result;
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1191
|
-
* The preview card template includes a title and an optional subtitle and image.
|
|
1192
|
-
* It populates the preview card template with the first text block that matches
|
|
1193
|
-
* each well-known name, in the order of title, subtitle, and image.
|
|
1194
|
-
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1195
|
-
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1196
|
-
* @param card The Adaptive Card to infer the preview card template from.
|
|
1197
|
-
* @param jsonPath The JSON path to the root object in the card body.
|
|
1198
|
-
* @returns The inferred preview card template.
|
|
1199
|
-
*/
|
|
1200
|
-
function inferPreviewCardTemplate(card) {
|
|
1201
|
-
var _a;
|
|
1202
|
-
const result = {
|
|
1203
|
-
title: "",
|
|
1204
|
-
};
|
|
1205
|
-
const textBlockElements = new Set();
|
|
1206
|
-
let rootObject;
|
|
1207
|
-
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1208
|
-
rootObject = card.body[0].items;
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
rootObject = card.body;
|
|
1212
|
-
}
|
|
1213
|
-
for (const element of rootObject) {
|
|
1214
|
-
if (element.type === ConstantString.TextBlockType) {
|
|
1215
|
-
const textElement = element;
|
|
1216
|
-
const index = textElement.text.indexOf("${if(");
|
|
1217
|
-
if (index > 0) {
|
|
1218
|
-
textElement.text = textElement.text.substring(index);
|
|
1219
|
-
textBlockElements.add(textElement);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
for (const element of textBlockElements) {
|
|
1224
|
-
const text = element.text;
|
|
1225
|
-
if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
|
|
1226
|
-
result.title = text;
|
|
1227
|
-
textBlockElements.delete(element);
|
|
1228
|
-
}
|
|
1229
|
-
else if (!result.subtitle &&
|
|
1230
|
-
Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
|
|
1231
|
-
result.subtitle = text;
|
|
1232
|
-
textBlockElements.delete(element);
|
|
1233
|
-
}
|
|
1234
|
-
else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
|
|
1235
|
-
const match = text.match(/\${if\(([^,]+),/);
|
|
1236
|
-
const property = match ? match[1] : "";
|
|
1237
|
-
if (property) {
|
|
1238
|
-
result.image = {
|
|
1239
|
-
url: `\${${property}}`,
|
|
1240
|
-
alt: text,
|
|
1241
|
-
$when: `\${${property} != null}`,
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
textBlockElements.delete(element);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
for (const element of textBlockElements) {
|
|
1248
|
-
const text = element.text;
|
|
1249
|
-
if (!result.title) {
|
|
1250
|
-
result.title = text;
|
|
1251
|
-
textBlockElements.delete(element);
|
|
1252
|
-
}
|
|
1253
|
-
else if (!result.subtitle) {
|
|
1254
|
-
result.subtitle = text;
|
|
1255
|
-
textBlockElements.delete(element);
|
|
1686
|
+
static removeEnvs(str) {
|
|
1687
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1688
|
+
const matches = placeHolderReg.exec(str);
|
|
1689
|
+
let newStr = str;
|
|
1690
|
+
if (matches != null) {
|
|
1691
|
+
newStr = newStr.replace(matches[0], "");
|
|
1256
1692
|
}
|
|
1693
|
+
return newStr;
|
|
1257
1694
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
delete result.subtitle;
|
|
1695
|
+
static removeAllSpecialCharacters(str) {
|
|
1696
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1261
1697
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1698
|
+
static getConfirmationBodyItem(paramName) {
|
|
1699
|
+
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
|
|
1264
1700
|
}
|
|
1265
|
-
return result;
|
|
1266
1701
|
}
|
|
1267
1702
|
|
|
1268
1703
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -1284,6 +1719,9 @@ class SpecParser {
|
|
|
1284
1719
|
allowMultipleParameters: false,
|
|
1285
1720
|
allowOauth2: false,
|
|
1286
1721
|
allowMethods: ["get", "post"],
|
|
1722
|
+
allowConversationStarters: false,
|
|
1723
|
+
allowResponseSemantics: false,
|
|
1724
|
+
allowConfirmation: false,
|
|
1287
1725
|
projectType: ProjectType.SME,
|
|
1288
1726
|
};
|
|
1289
1727
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -1308,6 +1746,8 @@ class SpecParser {
|
|
|
1308
1746
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1309
1747
|
};
|
|
1310
1748
|
}
|
|
1749
|
+
const errors = [];
|
|
1750
|
+
const warnings = [];
|
|
1311
1751
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1312
1752
|
return {
|
|
1313
1753
|
status: ValidationStatus.Error,
|
|
@@ -1317,7 +1757,38 @@ class SpecParser {
|
|
|
1317
1757
|
],
|
|
1318
1758
|
};
|
|
1319
1759
|
}
|
|
1320
|
-
|
|
1760
|
+
// Remote reference not supported
|
|
1761
|
+
const refPaths = this.parser.$refs.paths();
|
|
1762
|
+
// refPaths [0] is the current spec file path
|
|
1763
|
+
if (refPaths.length > 1) {
|
|
1764
|
+
errors.push({
|
|
1765
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1766
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1767
|
+
data: refPaths,
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1771
|
+
warnings.push({
|
|
1772
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1773
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
const validator = this.getValidator(this.spec);
|
|
1777
|
+
const validationResult = validator.validateSpec();
|
|
1778
|
+
warnings.push(...validationResult.warnings);
|
|
1779
|
+
errors.push(...validationResult.errors);
|
|
1780
|
+
let status = ValidationStatus.Valid;
|
|
1781
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1782
|
+
status = ValidationStatus.Warning;
|
|
1783
|
+
}
|
|
1784
|
+
else if (errors.length > 0) {
|
|
1785
|
+
status = ValidationStatus.Error;
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
status: status,
|
|
1789
|
+
warnings: warnings,
|
|
1790
|
+
errors: errors,
|
|
1791
|
+
};
|
|
1321
1792
|
}
|
|
1322
1793
|
catch (err) {
|
|
1323
1794
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1337,39 +1808,40 @@ class SpecParser {
|
|
|
1337
1808
|
try {
|
|
1338
1809
|
await this.loadSpec();
|
|
1339
1810
|
const spec = this.spec;
|
|
1340
|
-
const apiMap = this.
|
|
1341
|
-
const result =
|
|
1811
|
+
const apiMap = this.getAPIs(spec);
|
|
1812
|
+
const result = {
|
|
1813
|
+
APIs: [],
|
|
1814
|
+
allAPICount: 0,
|
|
1815
|
+
validAPICount: 0,
|
|
1816
|
+
};
|
|
1342
1817
|
for (const apiKey in apiMap) {
|
|
1818
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1819
|
+
const [method, path] = apiKey.split(" ");
|
|
1820
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1343
1821
|
const apiResult = {
|
|
1344
|
-
api:
|
|
1822
|
+
api: apiKey,
|
|
1345
1823
|
server: "",
|
|
1346
|
-
operationId:
|
|
1824
|
+
operationId: operationId,
|
|
1825
|
+
isValid: isValid,
|
|
1826
|
+
reason: reason,
|
|
1347
1827
|
};
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
if (!operationId) {
|
|
1360
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1361
|
-
}
|
|
1362
|
-
apiResult.operationId = operationId;
|
|
1363
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1364
|
-
for (const auths of authArray) {
|
|
1365
|
-
if (auths.length === 1) {
|
|
1366
|
-
apiResult.auth = auths[0].authScheme;
|
|
1367
|
-
break;
|
|
1828
|
+
if (isValid) {
|
|
1829
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1830
|
+
if (serverObj) {
|
|
1831
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1832
|
+
}
|
|
1833
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1834
|
+
for (const auths of authArray) {
|
|
1835
|
+
if (auths.length === 1) {
|
|
1836
|
+
apiResult.auth = auths[0];
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1368
1839
|
}
|
|
1369
1840
|
}
|
|
1370
|
-
apiResult
|
|
1371
|
-
result.push(apiResult);
|
|
1841
|
+
result.APIs.push(apiResult);
|
|
1372
1842
|
}
|
|
1843
|
+
result.allAPICount = result.APIs.length;
|
|
1844
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1373
1845
|
return result;
|
|
1374
1846
|
}
|
|
1375
1847
|
catch (err) {
|
|
@@ -1422,18 +1894,12 @@ class SpecParser {
|
|
|
1422
1894
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1423
1895
|
const newUnResolvedSpec = newSpecs[0];
|
|
1424
1896
|
const newSpec = newSpecs[1];
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1428
|
-
}
|
|
1429
|
-
else {
|
|
1430
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1431
|
-
}
|
|
1432
|
-
await fs.outputFile(outputSpecPath, resultStr);
|
|
1897
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1898
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1433
1899
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1434
1900
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1435
1901
|
}
|
|
1436
|
-
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
|
|
1902
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1437
1903
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1438
1904
|
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1439
1905
|
}
|
|
@@ -1461,32 +1927,11 @@ class SpecParser {
|
|
|
1461
1927
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1462
1928
|
const newUnResolvedSpec = newSpecs[0];
|
|
1463
1929
|
const newSpec = newSpecs[1];
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
for (const method in newSpec.paths[url]) {
|
|
1468
|
-
const operation = newSpec.paths[url][method];
|
|
1469
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1470
|
-
if (authArray && authArray.length > 0) {
|
|
1471
|
-
authSet.add(authArray[0][0]);
|
|
1472
|
-
if (authSet.size > 1) {
|
|
1473
|
-
hasMultipleAuth = true;
|
|
1474
|
-
break;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
|
|
1480
|
-
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
1481
|
-
}
|
|
1482
|
-
let resultStr;
|
|
1483
|
-
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1484
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1485
|
-
}
|
|
1486
|
-
else {
|
|
1487
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1930
|
+
let authInfo = undefined;
|
|
1931
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1932
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1488
1933
|
}
|
|
1489
|
-
await
|
|
1934
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1490
1935
|
if (adaptiveCardFolder) {
|
|
1491
1936
|
for (const url in newSpec.paths) {
|
|
1492
1937
|
for (const method in newSpec.paths[url]) {
|
|
@@ -1516,7 +1961,6 @@ class SpecParser {
|
|
|
1516
1961
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1517
1962
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1518
1963
|
}
|
|
1519
|
-
const authInfo = Array.from(authSet)[0];
|
|
1520
1964
|
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1521
1965
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1522
1966
|
result.warnings.push(...warnings);
|
|
@@ -1542,13 +1986,28 @@ class SpecParser {
|
|
|
1542
1986
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1543
1987
|
}
|
|
1544
1988
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1989
|
+
getAPIs(spec) {
|
|
1990
|
+
const validator = this.getValidator(spec);
|
|
1991
|
+
const apiMap = validator.listAPIs();
|
|
1992
|
+
return apiMap;
|
|
1993
|
+
}
|
|
1994
|
+
getValidator(spec) {
|
|
1995
|
+
if (this.validator) {
|
|
1996
|
+
return this.validator;
|
|
1548
1997
|
}
|
|
1549
|
-
const
|
|
1550
|
-
this.
|
|
1551
|
-
return
|
|
1998
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1999
|
+
this.validator = validator;
|
|
2000
|
+
return validator;
|
|
2001
|
+
}
|
|
2002
|
+
async saveFilterSpec(outputSpecPath, unResolvedSpec) {
|
|
2003
|
+
let resultStr;
|
|
2004
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
2005
|
+
resultStr = jsyaml.dump(unResolvedSpec);
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
resultStr = JSON.stringify(unResolvedSpec, null, 2);
|
|
2009
|
+
}
|
|
2010
|
+
await fs.outputFile(outputSpecPath, resultStr);
|
|
1552
2011
|
}
|
|
1553
2012
|
}
|
|
1554
2013
|
|