@microsoft/m365-spec-parser 0.1.1-alpha.f04ac4ba4.0 → 0.1.1-alpha.f2bd7316b.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 +635 -334
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1076 -631
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +635 -334
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1089 -640
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +9 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +83 -20
- package/dist/src/manifestUpdater.d.ts +9 -6
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +17 -33
- package/package.json +3 -3
package/dist/index.esm2017.mjs
CHANGED
|
@@ -19,7 +19,8 @@ var ErrorType;
|
|
|
19
19
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
20
20
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
21
21
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
22
|
-
ErrorType["
|
|
22
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
23
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
23
24
|
ErrorType["ListFailed"] = "list-failed";
|
|
24
25
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
25
26
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -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,7 +91,8 @@ 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.";
|
|
78
|
-
ConstantString.
|
|
94
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
95
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
79
96
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
80
97
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
81
98
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
@@ -86,8 +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 =
|
|
108
|
+
ConstantString.RegistrationIdPostfix = {
|
|
109
|
+
apiKey: "REGISTRATION_ID",
|
|
110
|
+
oauth2: "CONFIGURATION_ID",
|
|
111
|
+
http: "REGISTRATION_ID",
|
|
112
|
+
openIdConnect: "REGISTRATION_ID",
|
|
113
|
+
};
|
|
91
114
|
ConstantString.ResponseCodeFor20X = [
|
|
92
115
|
"200",
|
|
93
116
|
"201",
|
|
@@ -146,9 +169,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
146
169
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
147
170
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
148
171
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
172
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
149
173
|
ConstantString.CommandTitleMaxLens = 32;
|
|
150
174
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
151
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
175
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
176
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
152
177
|
|
|
153
178
|
// Copyright (c) Microsoft Corporation.
|
|
154
179
|
class SpecParserError extends Error {
|
|
@@ -171,242 +196,33 @@ class Utils {
|
|
|
171
196
|
}
|
|
172
197
|
return false;
|
|
173
198
|
}
|
|
174
|
-
static
|
|
175
|
-
|
|
176
|
-
requiredNum: 0,
|
|
177
|
-
optionalNum: 0,
|
|
178
|
-
isValid: true,
|
|
179
|
-
};
|
|
180
|
-
if (!paramObject) {
|
|
181
|
-
return paramResult;
|
|
182
|
-
}
|
|
183
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
184
|
-
const param = paramObject[i];
|
|
185
|
-
const schema = param.schema;
|
|
186
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
187
|
-
paramResult.isValid = false;
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
191
|
-
if (isCopilot) {
|
|
192
|
-
if (isRequiredWithoutDefault) {
|
|
193
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
197
|
-
}
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
201
|
-
if (isRequiredWithoutDefault) {
|
|
202
|
-
paramResult.isValid = false;
|
|
203
|
-
}
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
if (schema.type !== "boolean" &&
|
|
207
|
-
schema.type !== "string" &&
|
|
208
|
-
schema.type !== "number" &&
|
|
209
|
-
schema.type !== "integer") {
|
|
210
|
-
if (isRequiredWithoutDefault) {
|
|
211
|
-
paramResult.isValid = false;
|
|
212
|
-
}
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (param.in === "query" || param.in === "path") {
|
|
216
|
-
if (isRequiredWithoutDefault) {
|
|
217
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return paramResult;
|
|
225
|
-
}
|
|
226
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
227
|
-
var _a;
|
|
228
|
-
const paramResult = {
|
|
229
|
-
requiredNum: 0,
|
|
230
|
-
optionalNum: 0,
|
|
231
|
-
isValid: true,
|
|
232
|
-
};
|
|
233
|
-
if (Object.keys(schema).length === 0) {
|
|
234
|
-
return paramResult;
|
|
235
|
-
}
|
|
236
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
237
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
238
|
-
paramResult.isValid = false;
|
|
239
|
-
return paramResult;
|
|
240
|
-
}
|
|
241
|
-
if (schema.type === "string" ||
|
|
242
|
-
schema.type === "integer" ||
|
|
243
|
-
schema.type === "boolean" ||
|
|
244
|
-
schema.type === "number") {
|
|
245
|
-
if (isRequiredWithoutDefault) {
|
|
246
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else if (schema.type === "object") {
|
|
253
|
-
const { properties } = schema;
|
|
254
|
-
for (const property in properties) {
|
|
255
|
-
let isRequired = false;
|
|
256
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
257
|
-
isRequired = true;
|
|
258
|
-
}
|
|
259
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
260
|
-
paramResult.requiredNum += result.requiredNum;
|
|
261
|
-
paramResult.optionalNum += result.optionalNum;
|
|
262
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
267
|
-
paramResult.isValid = false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return paramResult;
|
|
199
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
200
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
271
201
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
* @param {string} method - The HTTP method of the API.
|
|
275
|
-
* @param {string} path - The path of the API.
|
|
276
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
277
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
278
|
-
* @description The following APIs are supported:
|
|
279
|
-
* 1. only support Get/Post operation without auth property
|
|
280
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
281
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
282
|
-
* 4. request body + required parameters <= 1
|
|
283
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
284
|
-
* 6. only support request body with “application/json” content type
|
|
285
|
-
*/
|
|
286
|
-
static isSupportedApi(method, path, spec, options) {
|
|
287
|
-
var _a;
|
|
288
|
-
const pathObj = spec.paths[path];
|
|
289
|
-
method = method.toLocaleLowerCase();
|
|
290
|
-
if (pathObj) {
|
|
291
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
292
|
-
const securities = pathObj[method].security;
|
|
293
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
294
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
const operationObject = pathObj[method];
|
|
298
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
const paramObject = operationObject.parameters;
|
|
302
|
-
const requestBody = operationObject.requestBody;
|
|
303
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
304
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
305
|
-
if (mediaTypesCount > 1) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
309
|
-
if (Object.keys(responseJson).length === 0) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
let requestBodyParamResult = {
|
|
313
|
-
requiredNum: 0,
|
|
314
|
-
optionalNum: 0,
|
|
315
|
-
isValid: true,
|
|
316
|
-
};
|
|
317
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
318
|
-
if (requestJsonBody) {
|
|
319
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
320
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
324
|
-
}
|
|
325
|
-
if (!requestBodyParamResult.isValid) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
329
|
-
if (!paramResult.isValid) {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
// Copilot support arbitrary parameters
|
|
333
|
-
if (isCopilot) {
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
337
|
-
if (options.allowMultipleParameters &&
|
|
338
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
339
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
else if (requestBodyParamResult.requiredNum +
|
|
345
|
-
requestBodyParamResult.optionalNum +
|
|
346
|
-
paramResult.requiredNum +
|
|
347
|
-
paramResult.optionalNum ===
|
|
348
|
-
0) {
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
static isSupportedAuth(authSchemaArray, options) {
|
|
359
|
-
if (authSchemaArray.length === 0) {
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
363
|
-
// Currently we don't support multiple auth in one operation
|
|
364
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
for (const auths of authSchemaArray) {
|
|
368
|
-
if (auths.length === 1) {
|
|
369
|
-
if (!options.allowOauth2 &&
|
|
370
|
-
options.allowAPIKeyAuth &&
|
|
371
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
else if (!options.allowAPIKeyAuth &&
|
|
375
|
-
options.allowOauth2 &&
|
|
376
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
else if (options.allowAPIKeyAuth &&
|
|
380
|
-
options.allowOauth2 &&
|
|
381
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
382
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return false;
|
|
202
|
+
static isBearerTokenAuth(authScheme) {
|
|
203
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
389
204
|
}
|
|
390
|
-
static isAPIKeyAuth(
|
|
391
|
-
return
|
|
205
|
+
static isAPIKeyAuth(authScheme) {
|
|
206
|
+
return authScheme.type === "apiKey";
|
|
392
207
|
}
|
|
393
|
-
static
|
|
394
|
-
return (
|
|
395
|
-
|
|
396
|
-
|
|
208
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
209
|
+
return !!(authScheme.type === "oauth2" &&
|
|
210
|
+
authScheme.flows &&
|
|
211
|
+
authScheme.flows.authorizationCode);
|
|
397
212
|
}
|
|
398
213
|
static getAuthArray(securities, spec) {
|
|
399
214
|
var _a;
|
|
400
215
|
const result = [];
|
|
401
216
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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];
|
|
405
221
|
const authArray = [];
|
|
406
222
|
for (const name in security) {
|
|
407
223
|
const auth = securitySchemas[name];
|
|
408
224
|
authArray.push({
|
|
409
|
-
|
|
225
|
+
authScheme: auth,
|
|
410
226
|
name: name,
|
|
411
227
|
});
|
|
412
228
|
}
|
|
@@ -418,24 +234,47 @@ class Utils {
|
|
|
418
234
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
419
235
|
return result;
|
|
420
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
|
+
}
|
|
421
256
|
static updateFirstLetter(str) {
|
|
422
257
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
423
258
|
}
|
|
424
259
|
static getResponseJson(operationObject) {
|
|
425
260
|
var _a, _b;
|
|
426
261
|
let json = {};
|
|
262
|
+
let multipleMediaType = false;
|
|
427
263
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
428
264
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
429
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
430
|
-
if (mediaTypesCount > 1) {
|
|
431
|
-
return {};
|
|
432
|
-
}
|
|
433
265
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
266
|
+
multipleMediaType = false;
|
|
434
267
|
json = responseObject.content["application/json"];
|
|
435
|
-
|
|
268
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
269
|
+
multipleMediaType = true;
|
|
270
|
+
json = {};
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
436
275
|
}
|
|
437
276
|
}
|
|
438
|
-
return json;
|
|
277
|
+
return { json, multipleMediaType };
|
|
439
278
|
}
|
|
440
279
|
static convertPathToCamelCase(path) {
|
|
441
280
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -455,10 +294,10 @@ class Utils {
|
|
|
455
294
|
return undefined;
|
|
456
295
|
}
|
|
457
296
|
}
|
|
458
|
-
static
|
|
297
|
+
static resolveEnv(str) {
|
|
459
298
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
460
|
-
let matches = placeHolderReg.exec(
|
|
461
|
-
let
|
|
299
|
+
let matches = placeHolderReg.exec(str);
|
|
300
|
+
let newStr = str;
|
|
462
301
|
while (matches != null) {
|
|
463
302
|
const envVar = matches[1];
|
|
464
303
|
const envVal = process.env[envVar];
|
|
@@ -466,17 +305,17 @@ class Utils {
|
|
|
466
305
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
467
306
|
}
|
|
468
307
|
else {
|
|
469
|
-
|
|
308
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
470
309
|
}
|
|
471
|
-
matches = placeHolderReg.exec(
|
|
310
|
+
matches = placeHolderReg.exec(str);
|
|
472
311
|
}
|
|
473
|
-
return
|
|
312
|
+
return newStr;
|
|
474
313
|
}
|
|
475
314
|
static checkServerUrl(servers) {
|
|
476
315
|
const errors = [];
|
|
477
316
|
let serverUrl;
|
|
478
317
|
try {
|
|
479
|
-
serverUrl = Utils.
|
|
318
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
480
319
|
}
|
|
481
320
|
catch (err) {
|
|
482
321
|
errors.push({
|
|
@@ -507,6 +346,7 @@ class Utils {
|
|
|
507
346
|
return errors;
|
|
508
347
|
}
|
|
509
348
|
static validateServer(spec, options) {
|
|
349
|
+
var _a;
|
|
510
350
|
const errors = [];
|
|
511
351
|
let hasTopLevelServers = false;
|
|
512
352
|
let hasPathLevelServers = false;
|
|
@@ -527,7 +367,7 @@ class Utils {
|
|
|
527
367
|
}
|
|
528
368
|
for (const method in methods) {
|
|
529
369
|
const operationObject = methods[method];
|
|
530
|
-
if (
|
|
370
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
531
371
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
532
372
|
hasOperationLevelServers = true;
|
|
533
373
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -570,6 +410,7 @@ class Utils {
|
|
|
570
410
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
571
411
|
}
|
|
572
412
|
if (isRequired && schema.default === undefined) {
|
|
413
|
+
parameter.isRequired = true;
|
|
573
414
|
requiredParams.push(parameter);
|
|
574
415
|
}
|
|
575
416
|
else {
|
|
@@ -633,6 +474,7 @@ class Utils {
|
|
|
633
474
|
}
|
|
634
475
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
635
476
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
477
|
+
parameter.isRequired = true;
|
|
636
478
|
requiredParams.push(parameter);
|
|
637
479
|
}
|
|
638
480
|
else {
|
|
@@ -652,13 +494,7 @@ class Utils {
|
|
|
652
494
|
}
|
|
653
495
|
}
|
|
654
496
|
const operationId = operationItem.operationId;
|
|
655
|
-
const parameters = [];
|
|
656
|
-
if (requiredParams.length !== 0) {
|
|
657
|
-
parameters.push(...requiredParams);
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
parameters.push(optionalParams[0]);
|
|
661
|
-
}
|
|
497
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
662
498
|
const command = {
|
|
663
499
|
context: ["compose"],
|
|
664
500
|
type: "query",
|
|
@@ -667,347 +503,575 @@ class Utils {
|
|
|
667
503
|
parameters: parameters,
|
|
668
504
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
669
505
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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 "";
|
|
677
518
|
}
|
|
678
|
-
|
|
519
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
520
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
521
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
522
|
+
}
|
|
523
|
+
return safeRegistrationIdEnvName;
|
|
679
524
|
}
|
|
680
|
-
static
|
|
681
|
-
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;
|
|
682
544
|
const result = {};
|
|
683
545
|
for (const path in paths) {
|
|
684
546
|
const methods = paths[path];
|
|
685
547
|
for (const method in methods) {
|
|
686
|
-
|
|
687
|
-
if (
|
|
688
|
-
const
|
|
689
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
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
|
+
};
|
|
690
556
|
}
|
|
691
557
|
}
|
|
692
558
|
}
|
|
559
|
+
this.apiMap = result;
|
|
693
560
|
return result;
|
|
694
561
|
}
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
// Server validation
|
|
705
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
706
|
-
errors.push(...serverErrors);
|
|
707
|
-
// Remote reference not supported
|
|
708
|
-
const refPaths = parser.$refs.paths();
|
|
709
|
-
// refPaths [0] is the current spec file path
|
|
710
|
-
if (refPaths.length > 1) {
|
|
711
|
-
errors.push({
|
|
712
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
713
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
714
|
-
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,
|
|
715
569
|
});
|
|
716
570
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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({
|
|
721
591
|
type: ErrorType.NoSupportedApi,
|
|
722
592
|
content: ConstantString.NoSupportedApi,
|
|
593
|
+
data,
|
|
723
594
|
});
|
|
724
595
|
}
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
validateSpecOperationId() {
|
|
599
|
+
const result = { errors: [], warnings: [] };
|
|
600
|
+
const apiMap = this.listAPIs();
|
|
725
601
|
// OperationId missing
|
|
726
602
|
const apisMissingOperationId = [];
|
|
727
603
|
for (const key in apiMap) {
|
|
728
|
-
const
|
|
729
|
-
if (!
|
|
604
|
+
const { operation } = apiMap[key];
|
|
605
|
+
if (!operation.operationId) {
|
|
730
606
|
apisMissingOperationId.push(key);
|
|
731
607
|
}
|
|
732
608
|
}
|
|
733
609
|
if (apisMissingOperationId.length > 0) {
|
|
734
|
-
warnings.push({
|
|
610
|
+
result.warnings.push({
|
|
735
611
|
type: WarningType.OperationIdMissing,
|
|
736
612
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
737
613
|
data: apisMissingOperationId,
|
|
738
614
|
});
|
|
739
615
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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;
|
|
743
624
|
}
|
|
744
|
-
|
|
745
|
-
|
|
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;
|
|
746
630
|
}
|
|
747
|
-
return
|
|
748
|
-
status,
|
|
749
|
-
warnings,
|
|
750
|
-
errors,
|
|
751
|
-
};
|
|
631
|
+
return result;
|
|
752
632
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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;
|
|
759
648
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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);
|
|
763
655
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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));
|
|
767
660
|
}
|
|
768
|
-
return
|
|
661
|
+
return result;
|
|
769
662
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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: [] };
|
|
788
687
|
}
|
|
789
688
|
}
|
|
790
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
791
|
-
// Add the operationId if missing
|
|
792
|
-
if (!newPaths[path][methodName].operationId) {
|
|
793
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
794
|
-
}
|
|
795
689
|
}
|
|
796
|
-
newSpec.paths = newPaths;
|
|
797
|
-
return newSpec;
|
|
798
|
-
}
|
|
799
|
-
catch (err) {
|
|
800
|
-
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
801
690
|
}
|
|
691
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
802
692
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
manifest.plugins = [
|
|
811
|
-
{
|
|
812
|
-
pluginFile: apiPluginRelativePath,
|
|
813
|
-
},
|
|
814
|
-
];
|
|
815
|
-
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
816
|
-
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
817
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
|
|
818
|
-
return [manifest, apiPlugin];
|
|
819
|
-
}
|
|
820
|
-
static updateManifestDescription(manifest, spec) {
|
|
821
|
-
var _a, _b;
|
|
822
|
-
manifest.description = {
|
|
823
|
-
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
824
|
-
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: [],
|
|
825
700
|
};
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
+
}
|
|
829
711
|
if (schema.type === "string" ||
|
|
830
|
-
schema.type === "boolean" ||
|
|
831
712
|
schema.type === "integer" ||
|
|
832
|
-
schema.type === "
|
|
833
|
-
schema.type === "
|
|
834
|
-
|
|
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
|
+
}
|
|
835
735
|
}
|
|
836
736
|
else {
|
|
837
|
-
|
|
737
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
738
|
+
paramResult.isValid = false;
|
|
739
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
740
|
+
}
|
|
838
741
|
}
|
|
839
|
-
return
|
|
742
|
+
return paramResult;
|
|
840
743
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
name: operationId,
|
|
894
|
-
description: description,
|
|
895
|
-
parameters: parameters,
|
|
896
|
-
};
|
|
897
|
-
functions.push(funcObj);
|
|
898
|
-
functionNames.push(operationId);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
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;
|
|
901
796
|
}
|
|
902
797
|
}
|
|
903
798
|
}
|
|
904
|
-
|
|
905
|
-
schema_version: "v2",
|
|
906
|
-
name_for_human: spec.info.title,
|
|
907
|
-
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
908
|
-
functions: functions,
|
|
909
|
-
runtimes: [
|
|
910
|
-
{
|
|
911
|
-
type: "OpenApi",
|
|
912
|
-
auth: {
|
|
913
|
-
type: "none", // TODO, support auth in the future
|
|
914
|
-
},
|
|
915
|
-
spec: {
|
|
916
|
-
url: specRelativePath,
|
|
917
|
-
},
|
|
918
|
-
run_for_functions: functionNames,
|
|
919
|
-
},
|
|
920
|
-
],
|
|
921
|
-
};
|
|
922
|
-
return apiPlugin;
|
|
799
|
+
return paramResult;
|
|
923
800
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (options.projectType === ProjectType.SME) {
|
|
931
|
-
const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
932
|
-
const commands = updateResult[0];
|
|
933
|
-
warnings = updateResult[1];
|
|
934
|
-
const composeExtension = {
|
|
935
|
-
composeExtensionType: "apiBased",
|
|
936
|
-
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
937
|
-
commands: commands,
|
|
938
|
-
};
|
|
939
|
-
if (auth) {
|
|
940
|
-
if (Utils.isAPIKeyAuth(auth)) {
|
|
941
|
-
auth = auth;
|
|
942
|
-
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${auth.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
943
|
-
composeExtension.authorization = {
|
|
944
|
-
authType: "apiSecretServiceAuth",
|
|
945
|
-
apiSecretServiceAuthConfiguration: {
|
|
946
|
-
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
947
|
-
},
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
else if (Utils.isBearerTokenAuth(auth)) {
|
|
951
|
-
composeExtension.authorization = {
|
|
952
|
-
authType: "microsoftEntra",
|
|
953
|
-
microsoftEntraConfiguration: {
|
|
954
|
-
supportsSingleSignOn: true,
|
|
955
|
-
},
|
|
956
|
-
};
|
|
957
|
-
updatedPart.webApplicationInfo = {
|
|
958
|
-
id: "${{AAD_APP_CLIENT_ID}}",
|
|
959
|
-
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
960
|
-
};
|
|
961
|
-
}
|
|
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;
|
|
962
807
|
}
|
|
963
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
964
808
|
}
|
|
965
|
-
updatedPart.description = originalManifest.description;
|
|
966
|
-
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
967
|
-
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
968
|
-
return [updatedManifest, warnings];
|
|
969
809
|
}
|
|
970
|
-
|
|
971
|
-
|
|
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;
|
|
972
877
|
}
|
|
878
|
+
return result;
|
|
973
879
|
}
|
|
974
|
-
|
|
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);
|
|
905
|
+
}
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
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) {
|
|
975
1022
|
var _a;
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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];
|
|
1001
1060
|
}
|
|
1002
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
|
+
}
|
|
1003
1067
|
}
|
|
1004
1068
|
}
|
|
1069
|
+
newSpec.paths = newPaths;
|
|
1070
|
+
return newSpec;
|
|
1071
|
+
}
|
|
1072
|
+
catch (err) {
|
|
1073
|
+
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
1005
1074
|
}
|
|
1006
|
-
return [commands, warnings];
|
|
1007
|
-
}
|
|
1008
|
-
static getRelativePath(from, to) {
|
|
1009
|
-
const relativePath = path.relative(path.dirname(from), to);
|
|
1010
|
-
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1011
1075
|
}
|
|
1012
1076
|
}
|
|
1013
1077
|
|
|
@@ -1015,7 +1079,7 @@ class ManifestUpdater {
|
|
|
1015
1079
|
class AdaptiveCardGenerator {
|
|
1016
1080
|
static generateAdaptiveCard(operationItem) {
|
|
1017
1081
|
try {
|
|
1018
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1082
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1019
1083
|
let cardBody = [];
|
|
1020
1084
|
let schema = json.schema;
|
|
1021
1085
|
let jsonPath = "$";
|
|
@@ -1181,6 +1245,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1181
1245
|
};
|
|
1182
1246
|
return result;
|
|
1183
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
|
+
}
|
|
1184
1269
|
/**
|
|
1185
1270
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1186
1271
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1193,11 +1278,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1193
1278
|
* @returns The inferred preview card template.
|
|
1194
1279
|
*/
|
|
1195
1280
|
function inferPreviewCardTemplate(card) {
|
|
1196
|
-
var _a;
|
|
1197
1281
|
const result = {
|
|
1198
|
-
title: "",
|
|
1282
|
+
title: "result",
|
|
1199
1283
|
};
|
|
1200
|
-
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();
|
|
1201
1304
|
let rootObject;
|
|
1202
1305
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1203
1306
|
rootObject = card.body[0].items;
|
|
@@ -1210,56 +1313,372 @@ function inferPreviewCardTemplate(card) {
|
|
|
1210
1313
|
const textElement = element;
|
|
1211
1314
|
const index = textElement.text.indexOf("${if(");
|
|
1212
1315
|
if (index > 0) {
|
|
1213
|
-
|
|
1214
|
-
|
|
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);
|
|
1215
1330
|
}
|
|
1216
1331
|
}
|
|
1217
1332
|
}
|
|
1218
|
-
for (const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
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);
|
|
1223
1337
|
}
|
|
1224
1338
|
else if (!result.subtitle &&
|
|
1225
|
-
Utils.isWellKnownName(
|
|
1226
|
-
result.subtitle =
|
|
1227
|
-
|
|
1339
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1340
|
+
result.subtitle = name;
|
|
1341
|
+
nameSet.delete(name);
|
|
1228
1342
|
}
|
|
1229
|
-
else if (!result.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
if (property) {
|
|
1233
|
-
result.image = {
|
|
1234
|
-
url: `\${${property}}`,
|
|
1235
|
-
alt: text,
|
|
1236
|
-
$when: `\${${property} != null}`,
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
textBlockElements.delete(element);
|
|
1343
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1344
|
+
result.imageUrl = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1240
1346
|
}
|
|
1241
1347
|
}
|
|
1242
|
-
for (const
|
|
1243
|
-
const text = element.text;
|
|
1348
|
+
for (const name of nameSet) {
|
|
1244
1349
|
if (!result.title) {
|
|
1245
|
-
result.title =
|
|
1246
|
-
|
|
1350
|
+
result.title = name;
|
|
1351
|
+
nameSet.delete(name);
|
|
1247
1352
|
}
|
|
1248
1353
|
else if (!result.subtitle) {
|
|
1249
|
-
result.subtitle =
|
|
1250
|
-
|
|
1354
|
+
result.subtitle = name;
|
|
1355
|
+
nameSet.delete(name);
|
|
1251
1356
|
}
|
|
1252
1357
|
}
|
|
1253
1358
|
if (!result.title && result.subtitle) {
|
|
1254
1359
|
result.title = result.subtitle;
|
|
1255
1360
|
delete result.subtitle;
|
|
1256
1361
|
}
|
|
1257
|
-
if (!result.title) {
|
|
1258
|
-
result.title = "result";
|
|
1259
|
-
}
|
|
1260
1362
|
return result;
|
|
1261
1363
|
}
|
|
1262
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
|
+
|
|
1263
1682
|
// Copyright (c) Microsoft Corporation.
|
|
1264
1683
|
/**
|
|
1265
1684
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1275,10 +1694,15 @@ class SpecParser {
|
|
|
1275
1694
|
allowMissingId: true,
|
|
1276
1695
|
allowSwagger: true,
|
|
1277
1696
|
allowAPIKeyAuth: false,
|
|
1697
|
+
allowBearerTokenAuth: false,
|
|
1278
1698
|
allowMultipleParameters: false,
|
|
1279
1699
|
allowOauth2: false,
|
|
1280
1700
|
allowMethods: ["get", "post"],
|
|
1701
|
+
allowConversationStarters: false,
|
|
1702
|
+
allowResponseSemantics: false,
|
|
1703
|
+
allowConfirmation: false,
|
|
1281
1704
|
projectType: ProjectType.SME,
|
|
1705
|
+
isGptPlugin: false,
|
|
1282
1706
|
};
|
|
1283
1707
|
this.pathOrSpec = pathOrDoc;
|
|
1284
1708
|
this.parser = new SwaggerParser();
|
|
@@ -1302,6 +1726,8 @@ class SpecParser {
|
|
|
1302
1726
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1303
1727
|
};
|
|
1304
1728
|
}
|
|
1729
|
+
const errors = [];
|
|
1730
|
+
const warnings = [];
|
|
1305
1731
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1306
1732
|
return {
|
|
1307
1733
|
status: ValidationStatus.Error,
|
|
@@ -1311,7 +1737,38 @@ class SpecParser {
|
|
|
1311
1737
|
],
|
|
1312
1738
|
};
|
|
1313
1739
|
}
|
|
1314
|
-
|
|
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
|
+
};
|
|
1315
1772
|
}
|
|
1316
1773
|
catch (err) {
|
|
1317
1774
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1331,39 +1788,40 @@ class SpecParser {
|
|
|
1331
1788
|
try {
|
|
1332
1789
|
await this.loadSpec();
|
|
1333
1790
|
const spec = this.spec;
|
|
1334
|
-
const apiMap = this.
|
|
1335
|
-
const result =
|
|
1791
|
+
const apiMap = this.getAPIs(spec);
|
|
1792
|
+
const result = {
|
|
1793
|
+
APIs: [],
|
|
1794
|
+
allAPICount: 0,
|
|
1795
|
+
validAPICount: 0,
|
|
1796
|
+
};
|
|
1336
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)}`;
|
|
1337
1801
|
const apiResult = {
|
|
1338
|
-
api:
|
|
1802
|
+
api: apiKey,
|
|
1339
1803
|
server: "",
|
|
1340
|
-
operationId:
|
|
1804
|
+
operationId: operationId,
|
|
1805
|
+
isValid: isValid,
|
|
1806
|
+
reason: reason,
|
|
1341
1807
|
};
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
if (!operationId) {
|
|
1354
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1355
|
-
}
|
|
1356
|
-
apiResult.operationId = operationId;
|
|
1357
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1358
|
-
for (const auths of authArray) {
|
|
1359
|
-
if (auths.length === 1) {
|
|
1360
|
-
apiResult.auth = auths[0].authSchema;
|
|
1361
|
-
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
|
+
}
|
|
1362
1819
|
}
|
|
1363
1820
|
}
|
|
1364
|
-
apiResult
|
|
1365
|
-
result.push(apiResult);
|
|
1821
|
+
result.APIs.push(apiResult);
|
|
1366
1822
|
}
|
|
1823
|
+
result.allAPICount = result.APIs.length;
|
|
1824
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1367
1825
|
return result;
|
|
1368
1826
|
}
|
|
1369
1827
|
catch (err) {
|
|
@@ -1416,18 +1874,12 @@ class SpecParser {
|
|
|
1416
1874
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1417
1875
|
const newUnResolvedSpec = newSpecs[0];
|
|
1418
1876
|
const newSpec = newSpecs[1];
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1422
|
-
}
|
|
1423
|
-
else {
|
|
1424
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1425
|
-
}
|
|
1426
|
-
await fs.outputFile(outputSpecPath, resultStr);
|
|
1877
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1878
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1427
1879
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1428
1880
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1429
1881
|
}
|
|
1430
|
-
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);
|
|
1431
1883
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1432
1884
|
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1433
1885
|
}
|
|
@@ -1455,32 +1907,11 @@ class SpecParser {
|
|
|
1455
1907
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1456
1908
|
const newUnResolvedSpec = newSpecs[0];
|
|
1457
1909
|
const newSpec = newSpecs[1];
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
for (const method in newSpec.paths[url]) {
|
|
1462
|
-
const operation = newSpec.paths[url][method];
|
|
1463
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1464
|
-
if (authArray && authArray.length > 0) {
|
|
1465
|
-
AuthSet.add(authArray[0][0].authSchema);
|
|
1466
|
-
if (AuthSet.size > 1) {
|
|
1467
|
-
hasMultipleAPIKeyAuth = true;
|
|
1468
|
-
break;
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
if (hasMultipleAPIKeyAuth) {
|
|
1474
|
-
throw new SpecParserError(ConstantString.MultipleAPIKeyNotSupported, ErrorType.MultipleAPIKeyNotSupported);
|
|
1475
|
-
}
|
|
1476
|
-
let resultStr;
|
|
1477
|
-
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1478
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1479
|
-
}
|
|
1480
|
-
else {
|
|
1481
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1910
|
+
let authInfo = undefined;
|
|
1911
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1912
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1482
1913
|
}
|
|
1483
|
-
await
|
|
1914
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1484
1915
|
if (adaptiveCardFolder) {
|
|
1485
1916
|
for (const url in newSpec.paths) {
|
|
1486
1917
|
for (const method in newSpec.paths[url]) {
|
|
@@ -1510,8 +1941,7 @@ class SpecParser {
|
|
|
1510
1941
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1511
1942
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1512
1943
|
}
|
|
1513
|
-
const
|
|
1514
|
-
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, auth);
|
|
1944
|
+
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1515
1945
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1516
1946
|
result.warnings.push(...warnings);
|
|
1517
1947
|
}
|
|
@@ -1536,13 +1966,28 @@ class SpecParser {
|
|
|
1536
1966
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1537
1967
|
}
|
|
1538
1968
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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;
|
|
1542
1977
|
}
|
|
1543
|
-
const
|
|
1544
|
-
this.
|
|
1545
|
-
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);
|
|
1546
1991
|
}
|
|
1547
1992
|
}
|
|
1548
1993
|
|