@microsoft/m365-spec-parser 0.1.1-alpha.e17ffd4d1.0 → 0.1.1-alpha.e1b11d5b2.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 +627 -338
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1067 -636
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +627 -338
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1079 -644
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +8 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +79 -16
- package/dist/src/manifestUpdater.d.ts +7 -4
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +15 -32
- package/package.json +3 -4
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 = {}));
|
|
@@ -67,7 +83,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
67
83
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
68
84
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
69
85
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
70
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
86
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
71
87
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
72
88
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
73
89
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -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,9 +103,14 @@ 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
|
-
ConstantString.RegistrationIdPostfix =
|
|
91
|
-
|
|
108
|
+
ConstantString.RegistrationIdPostfix = {
|
|
109
|
+
apiKey: "REGISTRATION_ID",
|
|
110
|
+
oauth2: "CONFIGURATION_ID",
|
|
111
|
+
http: "REGISTRATION_ID",
|
|
112
|
+
openIdConnect: "REGISTRATION_ID",
|
|
113
|
+
};
|
|
92
114
|
ConstantString.ResponseCodeFor20X = [
|
|
93
115
|
"200",
|
|
94
116
|
"201",
|
|
@@ -147,9 +169,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
147
169
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
148
170
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
149
171
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
172
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
150
173
|
ConstantString.CommandTitleMaxLens = 32;
|
|
151
174
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
152
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
175
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
176
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
153
177
|
|
|
154
178
|
// Copyright (c) Microsoft Corporation.
|
|
155
179
|
class SpecParserError extends Error {
|
|
@@ -172,253 +196,33 @@ class Utils {
|
|
|
172
196
|
}
|
|
173
197
|
return false;
|
|
174
198
|
}
|
|
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
199
|
static containMultipleMediaTypes(bodyObject) {
|
|
274
200
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
275
201
|
}
|
|
276
|
-
|
|
277
|
-
|
|
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(authSchemaArray, options) {
|
|
370
|
-
if (authSchemaArray.length === 0) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
374
|
-
// Currently we don't support multiple auth in one operation
|
|
375
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
for (const auths of authSchemaArray) {
|
|
379
|
-
if (auths.length === 1) {
|
|
380
|
-
if (!options.allowOauth2 &&
|
|
381
|
-
options.allowAPIKeyAuth &&
|
|
382
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
else if (!options.allowAPIKeyAuth &&
|
|
386
|
-
options.allowOauth2 &&
|
|
387
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema)) {
|
|
388
|
-
return true;
|
|
389
|
-
}
|
|
390
|
-
else if (options.allowAPIKeyAuth &&
|
|
391
|
-
options.allowOauth2 &&
|
|
392
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
393
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema))) {
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return false;
|
|
202
|
+
static isBearerTokenAuth(authScheme) {
|
|
203
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
400
204
|
}
|
|
401
|
-
static isAPIKeyAuth(
|
|
402
|
-
return
|
|
205
|
+
static isAPIKeyAuth(authScheme) {
|
|
206
|
+
return authScheme.type === "apiKey";
|
|
403
207
|
}
|
|
404
|
-
static isOAuthWithAuthCodeFlow(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return false;
|
|
208
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
209
|
+
return !!(authScheme.type === "oauth2" &&
|
|
210
|
+
authScheme.flows &&
|
|
211
|
+
authScheme.flows.authorizationCode);
|
|
409
212
|
}
|
|
410
213
|
static getAuthArray(securities, spec) {
|
|
411
214
|
var _a;
|
|
412
215
|
const result = [];
|
|
413
216
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
217
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
218
|
+
if (securitiesArr && securitySchemas) {
|
|
219
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
220
|
+
const security = securitiesArr[i];
|
|
417
221
|
const authArray = [];
|
|
418
222
|
for (const name in security) {
|
|
419
223
|
const auth = securitySchemas[name];
|
|
420
224
|
authArray.push({
|
|
421
|
-
|
|
225
|
+
authScheme: auth,
|
|
422
226
|
name: name,
|
|
423
227
|
});
|
|
424
228
|
}
|
|
@@ -430,17 +234,39 @@ class Utils {
|
|
|
430
234
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
431
235
|
return result;
|
|
432
236
|
}
|
|
237
|
+
static getAuthInfo(spec) {
|
|
238
|
+
let authInfo = undefined;
|
|
239
|
+
for (const url in spec.paths) {
|
|
240
|
+
for (const method in spec.paths[url]) {
|
|
241
|
+
const operation = spec.paths[url][method];
|
|
242
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
243
|
+
if (authArray && authArray.length > 0) {
|
|
244
|
+
const currentAuth = authArray[0][0];
|
|
245
|
+
if (!authInfo) {
|
|
246
|
+
authInfo = authArray[0][0];
|
|
247
|
+
}
|
|
248
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
249
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return authInfo;
|
|
255
|
+
}
|
|
433
256
|
static updateFirstLetter(str) {
|
|
434
257
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
435
258
|
}
|
|
436
|
-
static getResponseJson(operationObject
|
|
259
|
+
static getResponseJson(operationObject) {
|
|
437
260
|
var _a, _b;
|
|
438
261
|
let json = {};
|
|
262
|
+
let multipleMediaType = false;
|
|
439
263
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
440
264
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
441
265
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
266
|
+
multipleMediaType = false;
|
|
442
267
|
json = responseObject.content["application/json"];
|
|
443
|
-
if (
|
|
268
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
269
|
+
multipleMediaType = true;
|
|
444
270
|
json = {};
|
|
445
271
|
}
|
|
446
272
|
else {
|
|
@@ -448,7 +274,7 @@ class Utils {
|
|
|
448
274
|
}
|
|
449
275
|
}
|
|
450
276
|
}
|
|
451
|
-
return json;
|
|
277
|
+
return { json, multipleMediaType };
|
|
452
278
|
}
|
|
453
279
|
static convertPathToCamelCase(path) {
|
|
454
280
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -468,10 +294,10 @@ class Utils {
|
|
|
468
294
|
return undefined;
|
|
469
295
|
}
|
|
470
296
|
}
|
|
471
|
-
static
|
|
297
|
+
static resolveEnv(str) {
|
|
472
298
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
473
|
-
let matches = placeHolderReg.exec(
|
|
474
|
-
let
|
|
299
|
+
let matches = placeHolderReg.exec(str);
|
|
300
|
+
let newStr = str;
|
|
475
301
|
while (matches != null) {
|
|
476
302
|
const envVar = matches[1];
|
|
477
303
|
const envVal = process.env[envVar];
|
|
@@ -479,17 +305,17 @@ class Utils {
|
|
|
479
305
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
480
306
|
}
|
|
481
307
|
else {
|
|
482
|
-
|
|
308
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
483
309
|
}
|
|
484
|
-
matches = placeHolderReg.exec(
|
|
310
|
+
matches = placeHolderReg.exec(str);
|
|
485
311
|
}
|
|
486
|
-
return
|
|
312
|
+
return newStr;
|
|
487
313
|
}
|
|
488
314
|
static checkServerUrl(servers) {
|
|
489
315
|
const errors = [];
|
|
490
316
|
let serverUrl;
|
|
491
317
|
try {
|
|
492
|
-
serverUrl = Utils.
|
|
318
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
493
319
|
}
|
|
494
320
|
catch (err) {
|
|
495
321
|
errors.push({
|
|
@@ -520,6 +346,7 @@ class Utils {
|
|
|
520
346
|
return errors;
|
|
521
347
|
}
|
|
522
348
|
static validateServer(spec, options) {
|
|
349
|
+
var _a;
|
|
523
350
|
const errors = [];
|
|
524
351
|
let hasTopLevelServers = false;
|
|
525
352
|
let hasPathLevelServers = false;
|
|
@@ -540,7 +367,7 @@ class Utils {
|
|
|
540
367
|
}
|
|
541
368
|
for (const method in methods) {
|
|
542
369
|
const operationObject = methods[method];
|
|
543
|
-
if (
|
|
370
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
544
371
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
545
372
|
hasOperationLevelServers = true;
|
|
546
373
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -583,6 +410,7 @@ class Utils {
|
|
|
583
410
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
584
411
|
}
|
|
585
412
|
if (isRequired && schema.default === undefined) {
|
|
413
|
+
parameter.isRequired = true;
|
|
586
414
|
requiredParams.push(parameter);
|
|
587
415
|
}
|
|
588
416
|
else {
|
|
@@ -646,6 +474,7 @@ class Utils {
|
|
|
646
474
|
}
|
|
647
475
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
648
476
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
477
|
+
parameter.isRequired = true;
|
|
649
478
|
requiredParams.push(parameter);
|
|
650
479
|
}
|
|
651
480
|
else {
|
|
@@ -665,13 +494,7 @@ class Utils {
|
|
|
665
494
|
}
|
|
666
495
|
}
|
|
667
496
|
const operationId = operationItem.operationId;
|
|
668
|
-
const parameters = [];
|
|
669
|
-
if (requiredParams.length !== 0) {
|
|
670
|
-
parameters.push(...requiredParams);
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
parameters.push(optionalParams[0]);
|
|
674
|
-
}
|
|
497
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
675
498
|
const command = {
|
|
676
499
|
context: ["compose"],
|
|
677
500
|
type: "query",
|
|
@@ -680,348 +503,575 @@ class Utils {
|
|
|
680
503
|
parameters: parameters,
|
|
681
504
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
682
505
|
};
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
506
|
+
return command;
|
|
507
|
+
}
|
|
508
|
+
static format(str, ...args) {
|
|
509
|
+
let index = 0;
|
|
510
|
+
return str.replace(/%s/g, () => {
|
|
511
|
+
const arg = args[index++];
|
|
512
|
+
return arg !== undefined ? arg : "";
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
516
|
+
if (!authName) {
|
|
517
|
+
return "";
|
|
690
518
|
}
|
|
691
|
-
|
|
519
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
520
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
521
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
522
|
+
}
|
|
523
|
+
return safeRegistrationIdEnvName;
|
|
692
524
|
}
|
|
693
|
-
static
|
|
694
|
-
const
|
|
525
|
+
static getServerObject(spec, method, path) {
|
|
526
|
+
const pathObj = spec.paths[path];
|
|
527
|
+
const operationObject = pathObj[method];
|
|
528
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
529
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
530
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
531
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
532
|
+
return serverUrl;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Copyright (c) Microsoft Corporation.
|
|
537
|
+
class Validator {
|
|
538
|
+
listAPIs() {
|
|
539
|
+
var _a;
|
|
540
|
+
if (this.apiMap) {
|
|
541
|
+
return this.apiMap;
|
|
542
|
+
}
|
|
543
|
+
const paths = this.spec.paths;
|
|
695
544
|
const result = {};
|
|
696
545
|
for (const path in paths) {
|
|
697
546
|
const methods = paths[path];
|
|
698
547
|
for (const method in methods) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
548
|
+
const operationObject = methods[method];
|
|
549
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
550
|
+
const validateResult = this.validateAPI(method, path);
|
|
551
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
552
|
+
operation: operationObject,
|
|
553
|
+
isValid: validateResult.isValid,
|
|
554
|
+
reason: validateResult.reason,
|
|
555
|
+
};
|
|
702
556
|
}
|
|
703
557
|
}
|
|
704
558
|
}
|
|
559
|
+
this.apiMap = result;
|
|
705
560
|
return result;
|
|
706
561
|
}
|
|
707
|
-
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
// Server validation
|
|
717
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
718
|
-
errors.push(...serverErrors);
|
|
719
|
-
// Remote reference not supported
|
|
720
|
-
const refPaths = parser.$refs.paths();
|
|
721
|
-
// refPaths [0] is the current spec file path
|
|
722
|
-
if (refPaths.length > 1) {
|
|
723
|
-
errors.push({
|
|
724
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
725
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
726
|
-
data: refPaths,
|
|
562
|
+
validateSpecVersion() {
|
|
563
|
+
const result = { errors: [], warnings: [] };
|
|
564
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
565
|
+
result.errors.push({
|
|
566
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
567
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
568
|
+
data: this.spec.openapi,
|
|
727
569
|
});
|
|
728
570
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
validateSpecServer() {
|
|
574
|
+
const result = { errors: [], warnings: [] };
|
|
575
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
576
|
+
result.errors.push(...serverErrors);
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
validateSpecNoSupportAPI() {
|
|
580
|
+
const result = { errors: [], warnings: [] };
|
|
581
|
+
const apiMap = this.listAPIs();
|
|
582
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
583
|
+
if (validAPIs.length === 0) {
|
|
584
|
+
const data = [];
|
|
585
|
+
for (const key in apiMap) {
|
|
586
|
+
const { reason } = apiMap[key];
|
|
587
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
588
|
+
data.push(apiInvalidReason);
|
|
589
|
+
}
|
|
590
|
+
result.errors.push({
|
|
733
591
|
type: ErrorType.NoSupportedApi,
|
|
734
592
|
content: ConstantString.NoSupportedApi,
|
|
593
|
+
data,
|
|
735
594
|
});
|
|
736
595
|
}
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
validateSpecOperationId() {
|
|
599
|
+
const result = { errors: [], warnings: [] };
|
|
600
|
+
const apiMap = this.listAPIs();
|
|
737
601
|
// OperationId missing
|
|
738
602
|
const apisMissingOperationId = [];
|
|
739
603
|
for (const key in apiMap) {
|
|
740
|
-
const
|
|
741
|
-
if (!
|
|
604
|
+
const { operation } = apiMap[key];
|
|
605
|
+
if (!operation.operationId) {
|
|
742
606
|
apisMissingOperationId.push(key);
|
|
743
607
|
}
|
|
744
608
|
}
|
|
745
609
|
if (apisMissingOperationId.length > 0) {
|
|
746
|
-
warnings.push({
|
|
610
|
+
result.warnings.push({
|
|
747
611
|
type: WarningType.OperationIdMissing,
|
|
748
612
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
749
613
|
data: apisMissingOperationId,
|
|
750
614
|
});
|
|
751
615
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
validateMethodAndPath(method, path) {
|
|
619
|
+
const result = { isValid: true, reason: [] };
|
|
620
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
621
|
+
result.isValid = false;
|
|
622
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
623
|
+
return result;
|
|
755
624
|
}
|
|
756
|
-
|
|
757
|
-
|
|
625
|
+
const pathObj = this.spec.paths[path];
|
|
626
|
+
if (!pathObj || !pathObj[method]) {
|
|
627
|
+
result.isValid = false;
|
|
628
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
629
|
+
return result;
|
|
758
630
|
}
|
|
759
|
-
return
|
|
760
|
-
status,
|
|
761
|
-
warnings,
|
|
762
|
-
errors,
|
|
763
|
-
};
|
|
631
|
+
return result;
|
|
764
632
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
633
|
+
validateResponse(method, path) {
|
|
634
|
+
const result = { isValid: true, reason: [] };
|
|
635
|
+
const operationObject = this.spec.paths[path][method];
|
|
636
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
637
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
638
|
+
// only support response body only contains “application/json” content type
|
|
639
|
+
if (multipleMediaType) {
|
|
640
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
641
|
+
}
|
|
642
|
+
else if (Object.keys(json).length === 0) {
|
|
643
|
+
// response body should not be empty
|
|
644
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return result;
|
|
771
648
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
649
|
+
validateServer(method, path) {
|
|
650
|
+
const result = { isValid: true, reason: [] };
|
|
651
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
652
|
+
if (!serverObj) {
|
|
653
|
+
// should contain server URL
|
|
654
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
775
655
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
656
|
+
else {
|
|
657
|
+
// server url should be absolute url with https protocol
|
|
658
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
659
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
779
660
|
}
|
|
780
|
-
return
|
|
661
|
+
return result;
|
|
781
662
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
663
|
+
validateAuth(method, path) {
|
|
664
|
+
const pathObj = this.spec.paths[path];
|
|
665
|
+
const operationObject = pathObj[method];
|
|
666
|
+
const securities = operationObject.security;
|
|
667
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
668
|
+
if (authSchemeArray.length === 0) {
|
|
669
|
+
return { isValid: true, reason: [] };
|
|
670
|
+
}
|
|
671
|
+
if (this.options.allowAPIKeyAuth ||
|
|
672
|
+
this.options.allowOauth2 ||
|
|
673
|
+
this.options.allowBearerTokenAuth) {
|
|
674
|
+
// Currently we don't support multiple auth in one operation
|
|
675
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
676
|
+
return {
|
|
677
|
+
isValid: false,
|
|
678
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
for (const auths of authSchemeArray) {
|
|
682
|
+
if (auths.length === 1) {
|
|
683
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
684
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
685
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
686
|
+
return { isValid: true, reason: [] };
|
|
800
687
|
}
|
|
801
688
|
}
|
|
802
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
803
|
-
// Add the operationId if missing
|
|
804
|
-
if (!newPaths[path][methodName].operationId) {
|
|
805
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
806
|
-
}
|
|
807
689
|
}
|
|
808
|
-
newSpec.paths = newPaths;
|
|
809
|
-
return newSpec;
|
|
810
|
-
}
|
|
811
|
-
catch (err) {
|
|
812
|
-
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
813
690
|
}
|
|
691
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
814
692
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
manifest.plugins = [
|
|
823
|
-
{
|
|
824
|
-
pluginFile: apiPluginRelativePath,
|
|
825
|
-
},
|
|
826
|
-
];
|
|
827
|
-
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
828
|
-
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
829
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
|
|
830
|
-
return [manifest, apiPlugin];
|
|
831
|
-
}
|
|
832
|
-
static updateManifestDescription(manifest, spec) {
|
|
833
|
-
var _a, _b;
|
|
834
|
-
manifest.description = {
|
|
835
|
-
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
836
|
-
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),
|
|
693
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
694
|
+
var _a;
|
|
695
|
+
const paramResult = {
|
|
696
|
+
requiredNum: 0,
|
|
697
|
+
optionalNum: 0,
|
|
698
|
+
isValid: true,
|
|
699
|
+
reason: [],
|
|
837
700
|
};
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
701
|
+
if (Object.keys(schema).length === 0) {
|
|
702
|
+
return paramResult;
|
|
703
|
+
}
|
|
704
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
705
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
706
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
707
|
+
paramResult.isValid = false;
|
|
708
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
709
|
+
return paramResult;
|
|
710
|
+
}
|
|
841
711
|
if (schema.type === "string" ||
|
|
842
|
-
schema.type === "boolean" ||
|
|
843
712
|
schema.type === "integer" ||
|
|
844
|
-
schema.type === "
|
|
845
|
-
schema.type === "
|
|
846
|
-
|
|
713
|
+
schema.type === "boolean" ||
|
|
714
|
+
schema.type === "number") {
|
|
715
|
+
if (isRequiredWithoutDefault) {
|
|
716
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else if (schema.type === "object") {
|
|
723
|
+
const { properties } = schema;
|
|
724
|
+
for (const property in properties) {
|
|
725
|
+
let isRequired = false;
|
|
726
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
727
|
+
isRequired = true;
|
|
728
|
+
}
|
|
729
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
730
|
+
paramResult.requiredNum += result.requiredNum;
|
|
731
|
+
paramResult.optionalNum += result.optionalNum;
|
|
732
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
733
|
+
paramResult.reason.push(...result.reason);
|
|
734
|
+
}
|
|
847
735
|
}
|
|
848
736
|
else {
|
|
849
|
-
|
|
737
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
738
|
+
paramResult.isValid = false;
|
|
739
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
740
|
+
}
|
|
850
741
|
}
|
|
851
|
-
return
|
|
742
|
+
return paramResult;
|
|
852
743
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
name: operationId,
|
|
906
|
-
description: description,
|
|
907
|
-
parameters: parameters,
|
|
908
|
-
};
|
|
909
|
-
functions.push(funcObj);
|
|
910
|
-
functionNames.push(operationId);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
744
|
+
checkParamSchema(paramObject) {
|
|
745
|
+
const paramResult = {
|
|
746
|
+
requiredNum: 0,
|
|
747
|
+
optionalNum: 0,
|
|
748
|
+
isValid: true,
|
|
749
|
+
reason: [],
|
|
750
|
+
};
|
|
751
|
+
if (!paramObject) {
|
|
752
|
+
return paramResult;
|
|
753
|
+
}
|
|
754
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
755
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
756
|
+
const param = paramObject[i];
|
|
757
|
+
const schema = param.schema;
|
|
758
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
759
|
+
paramResult.isValid = false;
|
|
760
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
764
|
+
if (isCopilot) {
|
|
765
|
+
if (isRequiredWithoutDefault) {
|
|
766
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
770
|
+
}
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
774
|
+
if (isRequiredWithoutDefault) {
|
|
775
|
+
paramResult.isValid = false;
|
|
776
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
777
|
+
}
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (schema.type !== "boolean" &&
|
|
781
|
+
schema.type !== "string" &&
|
|
782
|
+
schema.type !== "number" &&
|
|
783
|
+
schema.type !== "integer") {
|
|
784
|
+
if (isRequiredWithoutDefault) {
|
|
785
|
+
paramResult.isValid = false;
|
|
786
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (param.in === "query" || param.in === "path") {
|
|
791
|
+
if (isRequiredWithoutDefault) {
|
|
792
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
913
796
|
}
|
|
914
797
|
}
|
|
915
798
|
}
|
|
916
|
-
|
|
917
|
-
schema_version: "v2",
|
|
918
|
-
name_for_human: spec.info.title,
|
|
919
|
-
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
920
|
-
functions: functions,
|
|
921
|
-
runtimes: [
|
|
922
|
-
{
|
|
923
|
-
type: "OpenApi",
|
|
924
|
-
auth: {
|
|
925
|
-
type: "none", // TODO, support auth in the future
|
|
926
|
-
},
|
|
927
|
-
spec: {
|
|
928
|
-
url: specRelativePath,
|
|
929
|
-
},
|
|
930
|
-
run_for_functions: functionNames,
|
|
931
|
-
},
|
|
932
|
-
],
|
|
933
|
-
};
|
|
934
|
-
return apiPlugin;
|
|
799
|
+
return paramResult;
|
|
935
800
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (options.projectType === ProjectType.SME) {
|
|
943
|
-
const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
944
|
-
const commands = updateResult[0];
|
|
945
|
-
warnings = updateResult[1];
|
|
946
|
-
const composeExtension = {
|
|
947
|
-
composeExtensionType: "apiBased",
|
|
948
|
-
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
949
|
-
commands: commands,
|
|
950
|
-
};
|
|
951
|
-
if (authInfo) {
|
|
952
|
-
let auth = authInfo.authSchema;
|
|
953
|
-
if (Utils.isAPIKeyAuth(auth)) {
|
|
954
|
-
auth = auth;
|
|
955
|
-
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
956
|
-
composeExtension.authorization = {
|
|
957
|
-
authType: "apiSecretServiceAuth",
|
|
958
|
-
apiSecretServiceAuthConfiguration: {
|
|
959
|
-
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
960
|
-
},
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
964
|
-
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
965
|
-
composeExtension.authorization = {
|
|
966
|
-
authType: "oAuth2.0",
|
|
967
|
-
oAuthConfiguration: {
|
|
968
|
-
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
969
|
-
},
|
|
970
|
-
};
|
|
971
|
-
updatedPart.webApplicationInfo = {
|
|
972
|
-
id: "${{AAD_APP_CLIENT_ID}}",
|
|
973
|
-
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
974
|
-
};
|
|
975
|
-
}
|
|
801
|
+
hasNestedObjectInSchema(schema) {
|
|
802
|
+
if (schema.type === "object") {
|
|
803
|
+
for (const property in schema.properties) {
|
|
804
|
+
const nestedSchema = schema.properties[property];
|
|
805
|
+
if (nestedSchema.type === "object") {
|
|
806
|
+
return true;
|
|
976
807
|
}
|
|
977
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
978
808
|
}
|
|
979
|
-
updatedPart.description = originalManifest.description;
|
|
980
|
-
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
981
|
-
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
982
|
-
return [updatedManifest, warnings];
|
|
983
809
|
}
|
|
984
|
-
|
|
985
|
-
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Copyright (c) Microsoft Corporation.
|
|
815
|
+
class CopilotValidator extends Validator {
|
|
816
|
+
constructor(spec, options) {
|
|
817
|
+
super();
|
|
818
|
+
this.projectType = ProjectType.Copilot;
|
|
819
|
+
this.options = options;
|
|
820
|
+
this.spec = spec;
|
|
821
|
+
}
|
|
822
|
+
validateSpec() {
|
|
823
|
+
const result = { errors: [], warnings: [] };
|
|
824
|
+
// validate spec version
|
|
825
|
+
let validationResult = this.validateSpecVersion();
|
|
826
|
+
result.errors.push(...validationResult.errors);
|
|
827
|
+
// validate spec server
|
|
828
|
+
validationResult = this.validateSpecServer();
|
|
829
|
+
result.errors.push(...validationResult.errors);
|
|
830
|
+
// validate no supported API
|
|
831
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
832
|
+
result.errors.push(...validationResult.errors);
|
|
833
|
+
// validate operationId missing
|
|
834
|
+
validationResult = this.validateSpecOperationId();
|
|
835
|
+
result.warnings.push(...validationResult.warnings);
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
validateAPI(method, path) {
|
|
839
|
+
const result = { isValid: true, reason: [] };
|
|
840
|
+
method = method.toLocaleLowerCase();
|
|
841
|
+
// validate method and path
|
|
842
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
843
|
+
if (!methodAndPathResult.isValid) {
|
|
844
|
+
return methodAndPathResult;
|
|
845
|
+
}
|
|
846
|
+
const operationObject = this.spec.paths[path][method];
|
|
847
|
+
// validate auth
|
|
848
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
849
|
+
result.reason.push(...authCheckResult.reason);
|
|
850
|
+
// validate operationId
|
|
851
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
852
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
853
|
+
}
|
|
854
|
+
// validate server
|
|
855
|
+
const validateServerResult = this.validateServer(method, path);
|
|
856
|
+
result.reason.push(...validateServerResult.reason);
|
|
857
|
+
// validate response
|
|
858
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
859
|
+
result.reason.push(...validateResponseResult.reason);
|
|
860
|
+
// validate requestBody
|
|
861
|
+
const requestBody = operationObject.requestBody;
|
|
862
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
863
|
+
if (requestJsonBody) {
|
|
864
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
865
|
+
if (requestBodySchema.type !== "object") {
|
|
866
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
867
|
+
}
|
|
868
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
869
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
870
|
+
}
|
|
871
|
+
// validate parameters
|
|
872
|
+
const paramObject = operationObject.parameters;
|
|
873
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
874
|
+
result.reason.push(...paramResult.reason);
|
|
875
|
+
if (result.reason.length > 0) {
|
|
876
|
+
result.isValid = false;
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Copyright (c) Microsoft Corporation.
|
|
883
|
+
class SMEValidator extends Validator {
|
|
884
|
+
constructor(spec, options) {
|
|
885
|
+
super();
|
|
886
|
+
this.projectType = ProjectType.SME;
|
|
887
|
+
this.options = options;
|
|
888
|
+
this.spec = spec;
|
|
889
|
+
}
|
|
890
|
+
validateSpec() {
|
|
891
|
+
const result = { errors: [], warnings: [] };
|
|
892
|
+
// validate spec version
|
|
893
|
+
let validationResult = this.validateSpecVersion();
|
|
894
|
+
result.errors.push(...validationResult.errors);
|
|
895
|
+
// validate spec server
|
|
896
|
+
validationResult = this.validateSpecServer();
|
|
897
|
+
result.errors.push(...validationResult.errors);
|
|
898
|
+
// validate no supported API
|
|
899
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
900
|
+
result.errors.push(...validationResult.errors);
|
|
901
|
+
// validate operationId missing
|
|
902
|
+
if (this.options.allowMissingId) {
|
|
903
|
+
validationResult = this.validateSpecOperationId();
|
|
904
|
+
result.warnings.push(...validationResult.warnings);
|
|
986
905
|
}
|
|
906
|
+
return result;
|
|
987
907
|
}
|
|
988
|
-
|
|
908
|
+
validateAPI(method, path) {
|
|
909
|
+
const result = { isValid: true, reason: [] };
|
|
910
|
+
method = method.toLocaleLowerCase();
|
|
911
|
+
// validate method and path
|
|
912
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
913
|
+
if (!methodAndPathResult.isValid) {
|
|
914
|
+
return methodAndPathResult;
|
|
915
|
+
}
|
|
916
|
+
const operationObject = this.spec.paths[path][method];
|
|
917
|
+
// validate auth
|
|
918
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
919
|
+
result.reason.push(...authCheckResult.reason);
|
|
920
|
+
// validate operationId
|
|
921
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
922
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
923
|
+
}
|
|
924
|
+
// validate server
|
|
925
|
+
const validateServerResult = this.validateServer(method, path);
|
|
926
|
+
result.reason.push(...validateServerResult.reason);
|
|
927
|
+
// validate response
|
|
928
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
929
|
+
result.reason.push(...validateResponseResult.reason);
|
|
930
|
+
let postBodyResult = {
|
|
931
|
+
requiredNum: 0,
|
|
932
|
+
optionalNum: 0,
|
|
933
|
+
isValid: true,
|
|
934
|
+
reason: [],
|
|
935
|
+
};
|
|
936
|
+
// validate requestBody
|
|
937
|
+
const requestBody = operationObject.requestBody;
|
|
938
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
939
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
940
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
941
|
+
}
|
|
942
|
+
if (requestJsonBody) {
|
|
943
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
944
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
945
|
+
result.reason.push(...postBodyResult.reason);
|
|
946
|
+
}
|
|
947
|
+
// validate parameters
|
|
948
|
+
const paramObject = operationObject.parameters;
|
|
949
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
950
|
+
result.reason.push(...paramResult.reason);
|
|
951
|
+
// validate total parameters count
|
|
952
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
953
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
954
|
+
result.reason.push(...paramCountResult.reason);
|
|
955
|
+
}
|
|
956
|
+
if (result.reason.length > 0) {
|
|
957
|
+
result.isValid = false;
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
962
|
+
const result = { isValid: true, reason: [] };
|
|
963
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
964
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
965
|
+
if (totalRequiredParams > 1) {
|
|
966
|
+
if (!this.options.allowMultipleParameters ||
|
|
967
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
968
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else if (totalParams === 0) {
|
|
972
|
+
result.reason.push(ErrorType.NoParameter);
|
|
973
|
+
}
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
978
|
+
|
|
979
|
+
// Copyright (c) Microsoft Corporation.
|
|
980
|
+
class TeamsAIValidator extends Validator {
|
|
981
|
+
constructor(spec, options) {
|
|
982
|
+
super();
|
|
983
|
+
this.projectType = ProjectType.TeamsAi;
|
|
984
|
+
this.options = options;
|
|
985
|
+
this.spec = spec;
|
|
986
|
+
}
|
|
987
|
+
validateSpec() {
|
|
988
|
+
const result = { errors: [], warnings: [] };
|
|
989
|
+
// validate spec server
|
|
990
|
+
let validationResult = this.validateSpecServer();
|
|
991
|
+
result.errors.push(...validationResult.errors);
|
|
992
|
+
// validate no supported API
|
|
993
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
994
|
+
result.errors.push(...validationResult.errors);
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
validateAPI(method, path) {
|
|
998
|
+
const result = { isValid: true, reason: [] };
|
|
999
|
+
method = method.toLocaleLowerCase();
|
|
1000
|
+
// validate method and path
|
|
1001
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1002
|
+
if (!methodAndPathResult.isValid) {
|
|
1003
|
+
return methodAndPathResult;
|
|
1004
|
+
}
|
|
1005
|
+
const operationObject = this.spec.paths[path][method];
|
|
1006
|
+
// validate operationId
|
|
1007
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1008
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1009
|
+
}
|
|
1010
|
+
// validate server
|
|
1011
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1012
|
+
result.reason.push(...validateServerResult.reason);
|
|
1013
|
+
if (result.reason.length > 0) {
|
|
1014
|
+
result.isValid = false;
|
|
1015
|
+
}
|
|
1016
|
+
return result;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
class ValidatorFactory {
|
|
1021
|
+
static create(spec, options) {
|
|
989
1022
|
var _a;
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1023
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1024
|
+
switch (type) {
|
|
1025
|
+
case ProjectType.SME:
|
|
1026
|
+
return new SMEValidator(spec, options);
|
|
1027
|
+
case ProjectType.Copilot:
|
|
1028
|
+
return new CopilotValidator(spec, options);
|
|
1029
|
+
case ProjectType.TeamsAi:
|
|
1030
|
+
return new TeamsAIValidator(spec, options);
|
|
1031
|
+
default:
|
|
1032
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Copyright (c) Microsoft Corporation.
|
|
1038
|
+
class SpecFilter {
|
|
1039
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1040
|
+
var _a;
|
|
1041
|
+
try {
|
|
1042
|
+
const newSpec = Object.assign({}, unResolveSpec);
|
|
1043
|
+
const newPaths = {};
|
|
1044
|
+
for (const filterItem of filter) {
|
|
1045
|
+
const [method, path] = filterItem.split(" ");
|
|
1046
|
+
const methodName = method.toLowerCase();
|
|
1047
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1048
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1049
|
+
pathObj &&
|
|
1050
|
+
pathObj[methodName]) {
|
|
1051
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1052
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1053
|
+
if (!validateResult.isValid) {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (!newPaths[path]) {
|
|
1057
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1058
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1059
|
+
delete newPaths[path][m];
|
|
1015
1060
|
}
|
|
1016
1061
|
}
|
|
1062
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1063
|
+
// Add the operationId if missing
|
|
1064
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1065
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
1066
|
+
}
|
|
1017
1067
|
}
|
|
1018
1068
|
}
|
|
1069
|
+
newSpec.paths = newPaths;
|
|
1070
|
+
return newSpec;
|
|
1071
|
+
}
|
|
1072
|
+
catch (err) {
|
|
1073
|
+
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
1019
1074
|
}
|
|
1020
|
-
return [commands, warnings];
|
|
1021
|
-
}
|
|
1022
|
-
static getRelativePath(from, to) {
|
|
1023
|
-
const relativePath = path.relative(path.dirname(from), to);
|
|
1024
|
-
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1025
1075
|
}
|
|
1026
1076
|
}
|
|
1027
1077
|
|
|
@@ -1029,7 +1079,7 @@ class ManifestUpdater {
|
|
|
1029
1079
|
class AdaptiveCardGenerator {
|
|
1030
1080
|
static generateAdaptiveCard(operationItem) {
|
|
1031
1081
|
try {
|
|
1032
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1082
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1033
1083
|
let cardBody = [];
|
|
1034
1084
|
let schema = json.schema;
|
|
1035
1085
|
let jsonPath = "$";
|
|
@@ -1195,6 +1245,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1195
1245
|
};
|
|
1196
1246
|
return result;
|
|
1197
1247
|
}
|
|
1248
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1249
|
+
const props = inferProperties(card);
|
|
1250
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1251
|
+
const result = {
|
|
1252
|
+
data_path: dataPath,
|
|
1253
|
+
};
|
|
1254
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1255
|
+
result.properties = {};
|
|
1256
|
+
if (props.title) {
|
|
1257
|
+
result.properties.title = "$." + props.title;
|
|
1258
|
+
}
|
|
1259
|
+
if (props.subtitle) {
|
|
1260
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1261
|
+
}
|
|
1262
|
+
if (props.imageUrl) {
|
|
1263
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
result.static_template = card;
|
|
1267
|
+
return result;
|
|
1268
|
+
}
|
|
1198
1269
|
/**
|
|
1199
1270
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1200
1271
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1207,11 +1278,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1207
1278
|
* @returns The inferred preview card template.
|
|
1208
1279
|
*/
|
|
1209
1280
|
function inferPreviewCardTemplate(card) {
|
|
1210
|
-
var _a;
|
|
1211
1281
|
const result = {
|
|
1212
|
-
title: "",
|
|
1282
|
+
title: "result",
|
|
1213
1283
|
};
|
|
1214
|
-
const
|
|
1284
|
+
const inferredProperties = inferProperties(card);
|
|
1285
|
+
if (inferredProperties.title) {
|
|
1286
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1287
|
+
}
|
|
1288
|
+
if (inferredProperties.subtitle) {
|
|
1289
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1290
|
+
}
|
|
1291
|
+
if (inferredProperties.imageUrl) {
|
|
1292
|
+
result.image = {
|
|
1293
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1294
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1295
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
return result;
|
|
1299
|
+
}
|
|
1300
|
+
function inferProperties(card) {
|
|
1301
|
+
var _a;
|
|
1302
|
+
const result = {};
|
|
1303
|
+
const nameSet = new Set();
|
|
1215
1304
|
let rootObject;
|
|
1216
1305
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1217
1306
|
rootObject = card.body[0].items;
|
|
@@ -1224,56 +1313,372 @@ function inferPreviewCardTemplate(card) {
|
|
|
1224
1313
|
const textElement = element;
|
|
1225
1314
|
const index = textElement.text.indexOf("${if(");
|
|
1226
1315
|
if (index > 0) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1316
|
+
const text = textElement.text.substring(index);
|
|
1317
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1318
|
+
const property = match ? match[1] : "";
|
|
1319
|
+
if (property) {
|
|
1320
|
+
nameSet.add(property);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1325
|
+
const imageElement = element;
|
|
1326
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1327
|
+
const property = match ? match[1] : "";
|
|
1328
|
+
if (property) {
|
|
1329
|
+
nameSet.add(property);
|
|
1229
1330
|
}
|
|
1230
1331
|
}
|
|
1231
1332
|
}
|
|
1232
|
-
for (const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
textBlockElements.delete(element);
|
|
1333
|
+
for (const name of nameSet) {
|
|
1334
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1335
|
+
result.title = name;
|
|
1336
|
+
nameSet.delete(name);
|
|
1237
1337
|
}
|
|
1238
1338
|
else if (!result.subtitle &&
|
|
1239
|
-
Utils.isWellKnownName(
|
|
1240
|
-
result.subtitle =
|
|
1241
|
-
|
|
1339
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1340
|
+
result.subtitle = name;
|
|
1341
|
+
nameSet.delete(name);
|
|
1242
1342
|
}
|
|
1243
|
-
else if (!result.
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (property) {
|
|
1247
|
-
result.image = {
|
|
1248
|
-
url: `\${${property}}`,
|
|
1249
|
-
alt: text,
|
|
1250
|
-
$when: `\${${property} != null}`,
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
textBlockElements.delete(element);
|
|
1343
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1344
|
+
result.imageUrl = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1254
1346
|
}
|
|
1255
1347
|
}
|
|
1256
|
-
for (const
|
|
1257
|
-
const text = element.text;
|
|
1348
|
+
for (const name of nameSet) {
|
|
1258
1349
|
if (!result.title) {
|
|
1259
|
-
result.title =
|
|
1260
|
-
|
|
1350
|
+
result.title = name;
|
|
1351
|
+
nameSet.delete(name);
|
|
1261
1352
|
}
|
|
1262
1353
|
else if (!result.subtitle) {
|
|
1263
|
-
result.subtitle =
|
|
1264
|
-
|
|
1354
|
+
result.subtitle = name;
|
|
1355
|
+
nameSet.delete(name);
|
|
1265
1356
|
}
|
|
1266
1357
|
}
|
|
1267
1358
|
if (!result.title && result.subtitle) {
|
|
1268
1359
|
result.title = result.subtitle;
|
|
1269
1360
|
delete result.subtitle;
|
|
1270
1361
|
}
|
|
1271
|
-
if (!result.title) {
|
|
1272
|
-
result.title = "result";
|
|
1273
|
-
}
|
|
1274
1362
|
return result;
|
|
1275
1363
|
}
|
|
1276
1364
|
|
|
1365
|
+
// Copyright (c) Microsoft Corporation.
|
|
1366
|
+
class ManifestUpdater {
|
|
1367
|
+
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
|
|
1368
|
+
const manifest = await fs.readJSON(manifestPath);
|
|
1369
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1370
|
+
// Insert plugins in manifest.json if it is plugin for Copilot.
|
|
1371
|
+
if (!options.isGptPlugin) {
|
|
1372
|
+
manifest.plugins = [
|
|
1373
|
+
{
|
|
1374
|
+
file: apiPluginRelativePath,
|
|
1375
|
+
id: ConstantString.DefaultPluginId,
|
|
1376
|
+
},
|
|
1377
|
+
];
|
|
1378
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1379
|
+
}
|
|
1380
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1381
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1382
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
|
|
1383
|
+
return [manifest, apiPlugin];
|
|
1384
|
+
}
|
|
1385
|
+
static updateManifestDescription(manifest, spec) {
|
|
1386
|
+
var _a, _b;
|
|
1387
|
+
manifest.description = {
|
|
1388
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1389
|
+
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),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
static checkSchema(schema, method, pathUrl) {
|
|
1393
|
+
if (schema.type === "array") {
|
|
1394
|
+
const items = schema.items;
|
|
1395
|
+
ManifestUpdater.checkSchema(items, method, pathUrl);
|
|
1396
|
+
}
|
|
1397
|
+
else if (schema.type !== "string" &&
|
|
1398
|
+
schema.type !== "boolean" &&
|
|
1399
|
+
schema.type !== "integer" &&
|
|
1400
|
+
schema.type !== "number") {
|
|
1401
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
|
|
1405
|
+
var _a, _b, _c, _d;
|
|
1406
|
+
const functions = [];
|
|
1407
|
+
const functionNames = [];
|
|
1408
|
+
const conversationStarters = [];
|
|
1409
|
+
const paths = spec.paths;
|
|
1410
|
+
const pluginAuthObj = {
|
|
1411
|
+
type: "None",
|
|
1412
|
+
};
|
|
1413
|
+
if (authInfo) {
|
|
1414
|
+
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
|
|
1415
|
+
pluginAuthObj.type = "OAuthPluginVault";
|
|
1416
|
+
}
|
|
1417
|
+
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
|
|
1418
|
+
pluginAuthObj.type = "ApiKeyPluginVault";
|
|
1419
|
+
}
|
|
1420
|
+
if (pluginAuthObj.type !== "None") {
|
|
1421
|
+
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
|
|
1422
|
+
pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
for (const pathUrl in paths) {
|
|
1426
|
+
const pathItem = paths[pathUrl];
|
|
1427
|
+
if (pathItem) {
|
|
1428
|
+
const operations = pathItem;
|
|
1429
|
+
for (const method in operations) {
|
|
1430
|
+
if (options.allowMethods.includes(method)) {
|
|
1431
|
+
const operationItem = operations[method];
|
|
1432
|
+
const confirmationBodies = [];
|
|
1433
|
+
if (operationItem) {
|
|
1434
|
+
const operationId = operationItem.operationId;
|
|
1435
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1436
|
+
const summary = operationItem.summary;
|
|
1437
|
+
const paramObject = operationItem.parameters;
|
|
1438
|
+
const requestBody = operationItem.requestBody;
|
|
1439
|
+
if (paramObject) {
|
|
1440
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1441
|
+
const param = paramObject[i];
|
|
1442
|
+
const schema = param.schema;
|
|
1443
|
+
ManifestUpdater.checkSchema(schema, method, pathUrl);
|
|
1444
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
if (requestBody) {
|
|
1448
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1449
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1450
|
+
if (requestBodySchema.type === "object") {
|
|
1451
|
+
for (const property in requestBodySchema.properties) {
|
|
1452
|
+
const schema = requestBodySchema.properties[property];
|
|
1453
|
+
ManifestUpdater.checkSchema(schema, method, pathUrl);
|
|
1454
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const funcObj = {
|
|
1462
|
+
name: operationId,
|
|
1463
|
+
description: description,
|
|
1464
|
+
};
|
|
1465
|
+
if (options.allowResponseSemantics) {
|
|
1466
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1467
|
+
if (json.schema) {
|
|
1468
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1469
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1470
|
+
funcObj.capabilities = {
|
|
1471
|
+
response_semantics: responseSemantic,
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
|
|
1476
|
+
if (!funcObj.capabilities) {
|
|
1477
|
+
funcObj.capabilities = {};
|
|
1478
|
+
}
|
|
1479
|
+
funcObj.capabilities.confirmation = {
|
|
1480
|
+
type: "AdaptiveCard",
|
|
1481
|
+
title: (_b = operationItem.summary) !== null && _b !== void 0 ? _b : description,
|
|
1482
|
+
};
|
|
1483
|
+
if (confirmationBodies.length > 0) {
|
|
1484
|
+
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
functions.push(funcObj);
|
|
1488
|
+
functionNames.push(operationId);
|
|
1489
|
+
const conversationStarterStr = (summary !== null && summary !== void 0 ? summary : description).slice(0, ConstantString.ConversationStarterMaxLens);
|
|
1490
|
+
if (conversationStarterStr) {
|
|
1491
|
+
conversationStarters.push(conversationStarterStr);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
let apiPlugin;
|
|
1499
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1500
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
apiPlugin = {
|
|
1504
|
+
schema_version: "v2.1",
|
|
1505
|
+
name_for_human: "",
|
|
1506
|
+
description_for_human: "",
|
|
1507
|
+
namespace: "",
|
|
1508
|
+
functions: [],
|
|
1509
|
+
runtimes: [],
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1513
|
+
for (const func of functions) {
|
|
1514
|
+
const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
|
|
1515
|
+
if (index === -1) {
|
|
1516
|
+
apiPlugin.functions.push(func);
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
apiPlugin.functions[index] = func;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1523
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => {
|
|
1524
|
+
var _a, _b;
|
|
1525
|
+
return runtime.spec.url === specRelativePath &&
|
|
1526
|
+
runtime.type === "OpenApi" &&
|
|
1527
|
+
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
|
|
1528
|
+
});
|
|
1529
|
+
if (index === -1) {
|
|
1530
|
+
apiPlugin.runtimes.push({
|
|
1531
|
+
type: "OpenApi",
|
|
1532
|
+
auth: pluginAuthObj,
|
|
1533
|
+
spec: {
|
|
1534
|
+
url: specRelativePath,
|
|
1535
|
+
},
|
|
1536
|
+
run_for_functions: functionNames,
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1541
|
+
}
|
|
1542
|
+
if (!apiPlugin.name_for_human) {
|
|
1543
|
+
apiPlugin.name_for_human = appName;
|
|
1544
|
+
}
|
|
1545
|
+
if (!apiPlugin.namespace) {
|
|
1546
|
+
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
|
|
1547
|
+
}
|
|
1548
|
+
if (!apiPlugin.description_for_human) {
|
|
1549
|
+
apiPlugin.description_for_human =
|
|
1550
|
+
(_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
|
|
1551
|
+
}
|
|
1552
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1553
|
+
if (!apiPlugin.capabilities) {
|
|
1554
|
+
apiPlugin.capabilities = {
|
|
1555
|
+
localization: {},
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1559
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1560
|
+
.slice(0, 5)
|
|
1561
|
+
.map((text) => ({ text }));
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return apiPlugin;
|
|
1565
|
+
}
|
|
1566
|
+
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
1567
|
+
try {
|
|
1568
|
+
const originalManifest = await fs.readJSON(manifestPath);
|
|
1569
|
+
const updatedPart = {};
|
|
1570
|
+
updatedPart.composeExtensions = [];
|
|
1571
|
+
let warnings = [];
|
|
1572
|
+
if (options.projectType === ProjectType.SME) {
|
|
1573
|
+
const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1574
|
+
const commands = updateResult[0];
|
|
1575
|
+
warnings = updateResult[1];
|
|
1576
|
+
const composeExtension = {
|
|
1577
|
+
composeExtensionType: "apiBased",
|
|
1578
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1579
|
+
commands: commands,
|
|
1580
|
+
};
|
|
1581
|
+
if (authInfo) {
|
|
1582
|
+
const auth = authInfo.authScheme;
|
|
1583
|
+
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
|
|
1584
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1585
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
|
|
1586
|
+
composeExtension.authorization = {
|
|
1587
|
+
authType: "apiSecretServiceAuth",
|
|
1588
|
+
apiSecretServiceAuthConfiguration: {
|
|
1589
|
+
apiSecretRegistrationId: `\${{${safeRegistrationIdName}}}`,
|
|
1590
|
+
},
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1594
|
+
composeExtension.authorization = {
|
|
1595
|
+
authType: "oAuth2.0",
|
|
1596
|
+
oAuthConfiguration: {
|
|
1597
|
+
oauthConfigurationId: `\${{${safeRegistrationIdName}}}`,
|
|
1598
|
+
},
|
|
1599
|
+
};
|
|
1600
|
+
updatedPart.webApplicationInfo = {
|
|
1601
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1602
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
1607
|
+
}
|
|
1608
|
+
updatedPart.description = originalManifest.description;
|
|
1609
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
1610
|
+
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
1611
|
+
return [updatedManifest, warnings];
|
|
1612
|
+
}
|
|
1613
|
+
catch (err) {
|
|
1614
|
+
throw new SpecParserError(err.toString(), ErrorType.UpdateManifestFailed);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
static async generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1618
|
+
var _a;
|
|
1619
|
+
const paths = spec.paths;
|
|
1620
|
+
const commands = [];
|
|
1621
|
+
const warnings = [];
|
|
1622
|
+
if (paths) {
|
|
1623
|
+
for (const pathUrl in paths) {
|
|
1624
|
+
const pathItem = paths[pathUrl];
|
|
1625
|
+
if (pathItem) {
|
|
1626
|
+
const operations = pathItem;
|
|
1627
|
+
// Currently only support GET and POST method
|
|
1628
|
+
for (const method in operations) {
|
|
1629
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1630
|
+
const operationItem = operations[method];
|
|
1631
|
+
if (operationItem) {
|
|
1632
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1633
|
+
if (command.parameters &&
|
|
1634
|
+
command.parameters.length >= 1 &&
|
|
1635
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1636
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1637
|
+
}
|
|
1638
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1639
|
+
command.parameters = [command.parameters[0]];
|
|
1640
|
+
warnings.push({
|
|
1641
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1642
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1643
|
+
data: command.id,
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
if (adaptiveCardFolder) {
|
|
1647
|
+
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1648
|
+
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1649
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1650
|
+
: "";
|
|
1651
|
+
}
|
|
1652
|
+
commands.push(command);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return [commands, warnings];
|
|
1660
|
+
}
|
|
1661
|
+
static getRelativePath(from, to) {
|
|
1662
|
+
const relativePath = path.relative(path.dirname(from), to);
|
|
1663
|
+
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1664
|
+
}
|
|
1665
|
+
static removeEnvs(str) {
|
|
1666
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1667
|
+
const matches = placeHolderReg.exec(str);
|
|
1668
|
+
let newStr = str;
|
|
1669
|
+
if (matches != null) {
|
|
1670
|
+
newStr = newStr.replace(matches[0], "");
|
|
1671
|
+
}
|
|
1672
|
+
return newStr;
|
|
1673
|
+
}
|
|
1674
|
+
static removeAllSpecialCharacters(str) {
|
|
1675
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1676
|
+
}
|
|
1677
|
+
static getConfirmationBodyItem(paramName) {
|
|
1678
|
+
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1277
1682
|
// Copyright (c) Microsoft Corporation.
|
|
1278
1683
|
/**
|
|
1279
1684
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1289,10 +1694,15 @@ class SpecParser {
|
|
|
1289
1694
|
allowMissingId: true,
|
|
1290
1695
|
allowSwagger: true,
|
|
1291
1696
|
allowAPIKeyAuth: false,
|
|
1697
|
+
allowBearerTokenAuth: false,
|
|
1292
1698
|
allowMultipleParameters: false,
|
|
1293
1699
|
allowOauth2: false,
|
|
1294
1700
|
allowMethods: ["get", "post"],
|
|
1701
|
+
allowConversationStarters: false,
|
|
1702
|
+
allowResponseSemantics: false,
|
|
1703
|
+
allowConfirmation: false,
|
|
1295
1704
|
projectType: ProjectType.SME,
|
|
1705
|
+
isGptPlugin: false,
|
|
1296
1706
|
};
|
|
1297
1707
|
this.pathOrSpec = pathOrDoc;
|
|
1298
1708
|
this.parser = new SwaggerParser();
|
|
@@ -1316,6 +1726,8 @@ class SpecParser {
|
|
|
1316
1726
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1317
1727
|
};
|
|
1318
1728
|
}
|
|
1729
|
+
const errors = [];
|
|
1730
|
+
const warnings = [];
|
|
1319
1731
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1320
1732
|
return {
|
|
1321
1733
|
status: ValidationStatus.Error,
|
|
@@ -1325,7 +1737,38 @@ class SpecParser {
|
|
|
1325
1737
|
],
|
|
1326
1738
|
};
|
|
1327
1739
|
}
|
|
1328
|
-
|
|
1740
|
+
// Remote reference not supported
|
|
1741
|
+
const refPaths = this.parser.$refs.paths();
|
|
1742
|
+
// refPaths [0] is the current spec file path
|
|
1743
|
+
if (refPaths.length > 1) {
|
|
1744
|
+
errors.push({
|
|
1745
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1746
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1747
|
+
data: refPaths,
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1751
|
+
warnings.push({
|
|
1752
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1753
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
const validator = this.getValidator(this.spec);
|
|
1757
|
+
const validationResult = validator.validateSpec();
|
|
1758
|
+
warnings.push(...validationResult.warnings);
|
|
1759
|
+
errors.push(...validationResult.errors);
|
|
1760
|
+
let status = ValidationStatus.Valid;
|
|
1761
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1762
|
+
status = ValidationStatus.Warning;
|
|
1763
|
+
}
|
|
1764
|
+
else if (errors.length > 0) {
|
|
1765
|
+
status = ValidationStatus.Error;
|
|
1766
|
+
}
|
|
1767
|
+
return {
|
|
1768
|
+
status: status,
|
|
1769
|
+
warnings: warnings,
|
|
1770
|
+
errors: errors,
|
|
1771
|
+
};
|
|
1329
1772
|
}
|
|
1330
1773
|
catch (err) {
|
|
1331
1774
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1345,39 +1788,40 @@ class SpecParser {
|
|
|
1345
1788
|
try {
|
|
1346
1789
|
await this.loadSpec();
|
|
1347
1790
|
const spec = this.spec;
|
|
1348
|
-
const apiMap = this.
|
|
1349
|
-
const result =
|
|
1791
|
+
const apiMap = this.getAPIs(spec);
|
|
1792
|
+
const result = {
|
|
1793
|
+
APIs: [],
|
|
1794
|
+
allAPICount: 0,
|
|
1795
|
+
validAPICount: 0,
|
|
1796
|
+
};
|
|
1350
1797
|
for (const apiKey in apiMap) {
|
|
1798
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1799
|
+
const [method, path] = apiKey.split(" ");
|
|
1800
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1351
1801
|
const apiResult = {
|
|
1352
|
-
api:
|
|
1802
|
+
api: apiKey,
|
|
1353
1803
|
server: "",
|
|
1354
|
-
operationId:
|
|
1804
|
+
operationId: operationId,
|
|
1805
|
+
isValid: isValid,
|
|
1806
|
+
reason: reason,
|
|
1355
1807
|
};
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
if (!operationId) {
|
|
1368
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1369
|
-
}
|
|
1370
|
-
apiResult.operationId = operationId;
|
|
1371
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1372
|
-
for (const auths of authArray) {
|
|
1373
|
-
if (auths.length === 1) {
|
|
1374
|
-
apiResult.auth = auths[0].authSchema;
|
|
1375
|
-
break;
|
|
1808
|
+
if (isValid) {
|
|
1809
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1810
|
+
if (serverObj) {
|
|
1811
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1812
|
+
}
|
|
1813
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1814
|
+
for (const auths of authArray) {
|
|
1815
|
+
if (auths.length === 1) {
|
|
1816
|
+
apiResult.auth = auths[0];
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1376
1819
|
}
|
|
1377
1820
|
}
|
|
1378
|
-
apiResult
|
|
1379
|
-
result.push(apiResult);
|
|
1821
|
+
result.APIs.push(apiResult);
|
|
1380
1822
|
}
|
|
1823
|
+
result.allAPICount = result.APIs.length;
|
|
1824
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1381
1825
|
return result;
|
|
1382
1826
|
}
|
|
1383
1827
|
catch (err) {
|
|
@@ -1430,18 +1874,12 @@ class SpecParser {
|
|
|
1430
1874
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1431
1875
|
const newUnResolvedSpec = newSpecs[0];
|
|
1432
1876
|
const newSpec = newSpecs[1];
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1436
|
-
}
|
|
1437
|
-
else {
|
|
1438
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1439
|
-
}
|
|
1440
|
-
await fs.outputFile(outputSpecPath, resultStr);
|
|
1877
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1878
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1441
1879
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1442
1880
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1443
1881
|
}
|
|
1444
|
-
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
|
|
1882
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1445
1883
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1446
1884
|
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1447
1885
|
}
|
|
@@ -1469,32 +1907,11 @@ class SpecParser {
|
|
|
1469
1907
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1470
1908
|
const newUnResolvedSpec = newSpecs[0];
|
|
1471
1909
|
const newSpec = newSpecs[1];
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
for (const method in newSpec.paths[url]) {
|
|
1476
|
-
const operation = newSpec.paths[url][method];
|
|
1477
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1478
|
-
if (authArray && authArray.length > 0) {
|
|
1479
|
-
authSet.add(authArray[0][0]);
|
|
1480
|
-
if (authSet.size > 1) {
|
|
1481
|
-
hasMultipleAuth = true;
|
|
1482
|
-
break;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
|
|
1488
|
-
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
1489
|
-
}
|
|
1490
|
-
let resultStr;
|
|
1491
|
-
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1492
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1493
|
-
}
|
|
1494
|
-
else {
|
|
1495
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1910
|
+
let authInfo = undefined;
|
|
1911
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1912
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1496
1913
|
}
|
|
1497
|
-
await
|
|
1914
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1498
1915
|
if (adaptiveCardFolder) {
|
|
1499
1916
|
for (const url in newSpec.paths) {
|
|
1500
1917
|
for (const method in newSpec.paths[url]) {
|
|
@@ -1524,7 +1941,6 @@ class SpecParser {
|
|
|
1524
1941
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1525
1942
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1526
1943
|
}
|
|
1527
|
-
const authInfo = Array.from(authSet)[0];
|
|
1528
1944
|
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1529
1945
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1530
1946
|
result.warnings.push(...warnings);
|
|
@@ -1550,13 +1966,28 @@ class SpecParser {
|
|
|
1550
1966
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1551
1967
|
}
|
|
1552
1968
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1969
|
+
getAPIs(spec) {
|
|
1970
|
+
const validator = this.getValidator(spec);
|
|
1971
|
+
const apiMap = validator.listAPIs();
|
|
1972
|
+
return apiMap;
|
|
1973
|
+
}
|
|
1974
|
+
getValidator(spec) {
|
|
1975
|
+
if (this.validator) {
|
|
1976
|
+
return this.validator;
|
|
1556
1977
|
}
|
|
1557
|
-
const
|
|
1558
|
-
this.
|
|
1559
|
-
return
|
|
1978
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1979
|
+
this.validator = validator;
|
|
1980
|
+
return validator;
|
|
1981
|
+
}
|
|
1982
|
+
async saveFilterSpec(outputSpecPath, unResolvedSpec) {
|
|
1983
|
+
let resultStr;
|
|
1984
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1985
|
+
resultStr = jsyaml.dump(unResolvedSpec);
|
|
1986
|
+
}
|
|
1987
|
+
else {
|
|
1988
|
+
resultStr = JSON.stringify(unResolvedSpec, null, 2);
|
|
1989
|
+
}
|
|
1990
|
+
await fs.outputFile(outputSpecPath, resultStr);
|
|
1560
1991
|
}
|
|
1561
1992
|
}
|
|
1562
1993
|
|