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