@microsoft/m365-spec-parser 0.1.1-alpha.cf377d39f.0 → 0.1.1-alpha.ded43fb2d.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm2017.js +656 -302
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1173 -496
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +658 -302
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1187 -502
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +7 -1
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +106 -18
- package/dist/src/manifestUpdater.d.ts +11 -4
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +12 -3
- package/dist/src/specParser.d.ts +14 -5
- package/dist/src/utils.d.ts +19 -34
- package/package.json +4 -4
package/dist/index.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
|
}
|
|
@@ -371,24 +228,47 @@ class Utils {
|
|
|
371
228
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
372
229
|
return result;
|
|
373
230
|
}
|
|
231
|
+
static getAuthInfo(spec) {
|
|
232
|
+
let authInfo = undefined;
|
|
233
|
+
for (const url in spec.paths) {
|
|
234
|
+
for (const method in spec.paths[url]) {
|
|
235
|
+
const operation = spec.paths[url][method];
|
|
236
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
237
|
+
if (authArray && authArray.length > 0) {
|
|
238
|
+
const currentAuth = authArray[0][0];
|
|
239
|
+
if (!authInfo) {
|
|
240
|
+
authInfo = authArray[0][0];
|
|
241
|
+
}
|
|
242
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
243
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return authInfo;
|
|
249
|
+
}
|
|
374
250
|
static updateFirstLetter(str) {
|
|
375
251
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
376
252
|
}
|
|
377
253
|
static getResponseJson(operationObject) {
|
|
378
254
|
var _a, _b;
|
|
379
255
|
let json = {};
|
|
256
|
+
let multipleMediaType = false;
|
|
380
257
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
381
258
|
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
259
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
260
|
+
multipleMediaType = false;
|
|
387
261
|
json = responseObject.content["application/json"];
|
|
388
|
-
|
|
262
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
263
|
+
multipleMediaType = true;
|
|
264
|
+
json = {};
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
389
269
|
}
|
|
390
270
|
}
|
|
391
|
-
return json;
|
|
271
|
+
return { json, multipleMediaType };
|
|
392
272
|
}
|
|
393
273
|
static convertPathToCamelCase(path) {
|
|
394
274
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -408,10 +288,10 @@ class Utils {
|
|
|
408
288
|
return undefined;
|
|
409
289
|
}
|
|
410
290
|
}
|
|
411
|
-
static
|
|
291
|
+
static resolveEnv(str) {
|
|
412
292
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
413
|
-
let matches = placeHolderReg.exec(
|
|
414
|
-
let
|
|
293
|
+
let matches = placeHolderReg.exec(str);
|
|
294
|
+
let newStr = str;
|
|
415
295
|
while (matches != null) {
|
|
416
296
|
const envVar = matches[1];
|
|
417
297
|
const envVal = process.env[envVar];
|
|
@@ -419,17 +299,17 @@ class Utils {
|
|
|
419
299
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
420
300
|
}
|
|
421
301
|
else {
|
|
422
|
-
|
|
302
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
423
303
|
}
|
|
424
|
-
matches = placeHolderReg.exec(
|
|
304
|
+
matches = placeHolderReg.exec(str);
|
|
425
305
|
}
|
|
426
|
-
return
|
|
306
|
+
return newStr;
|
|
427
307
|
}
|
|
428
308
|
static checkServerUrl(servers) {
|
|
429
309
|
const errors = [];
|
|
430
310
|
let serverUrl;
|
|
431
311
|
try {
|
|
432
|
-
serverUrl = Utils.
|
|
312
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
433
313
|
}
|
|
434
314
|
catch (err) {
|
|
435
315
|
errors.push({
|
|
@@ -459,7 +339,8 @@ class Utils {
|
|
|
459
339
|
}
|
|
460
340
|
return errors;
|
|
461
341
|
}
|
|
462
|
-
static validateServer(spec,
|
|
342
|
+
static validateServer(spec, options) {
|
|
343
|
+
var _a;
|
|
463
344
|
const errors = [];
|
|
464
345
|
let hasTopLevelServers = false;
|
|
465
346
|
let hasPathLevelServers = false;
|
|
@@ -480,7 +361,7 @@ class Utils {
|
|
|
480
361
|
}
|
|
481
362
|
for (const method in methods) {
|
|
482
363
|
const operationObject = methods[method];
|
|
483
|
-
if (
|
|
364
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
484
365
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
485
366
|
hasOperationLevelServers = true;
|
|
486
367
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -523,6 +404,7 @@ class Utils {
|
|
|
523
404
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
524
405
|
}
|
|
525
406
|
if (isRequired && schema.default === undefined) {
|
|
407
|
+
parameter.isRequired = true;
|
|
526
408
|
requiredParams.push(parameter);
|
|
527
409
|
}
|
|
528
410
|
else {
|
|
@@ -567,7 +449,7 @@ class Utils {
|
|
|
567
449
|
param.value = schema.default;
|
|
568
450
|
}
|
|
569
451
|
}
|
|
570
|
-
static parseApiInfo(operationItem,
|
|
452
|
+
static parseApiInfo(operationItem, options) {
|
|
571
453
|
var _a, _b;
|
|
572
454
|
const requiredParams = [];
|
|
573
455
|
const optionalParams = [];
|
|
@@ -581,11 +463,12 @@ class Utils {
|
|
|
581
463
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
582
464
|
};
|
|
583
465
|
const schema = param.schema;
|
|
584
|
-
if (allowMultipleParameters && schema) {
|
|
466
|
+
if (options.allowMultipleParameters && schema) {
|
|
585
467
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
586
468
|
}
|
|
587
469
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
588
470
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
471
|
+
parameter.isRequired = true;
|
|
589
472
|
requiredParams.push(parameter);
|
|
590
473
|
}
|
|
591
474
|
else {
|
|
@@ -599,19 +482,13 @@ class Utils {
|
|
|
599
482
|
const requestJson = requestBody.content["application/json"];
|
|
600
483
|
if (Object.keys(requestJson).length !== 0) {
|
|
601
484
|
const schema = requestJson.schema;
|
|
602
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
485
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
603
486
|
requiredParams.push(...requiredP);
|
|
604
487
|
optionalParams.push(...optionalP);
|
|
605
488
|
}
|
|
606
489
|
}
|
|
607
490
|
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
|
-
}
|
|
491
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
615
492
|
const command = {
|
|
616
493
|
context: ["compose"],
|
|
617
494
|
type: "query",
|
|
@@ -620,130 +497,567 @@ class Utils {
|
|
|
620
497
|
parameters: parameters,
|
|
621
498
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
622
499
|
};
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
500
|
+
return command;
|
|
501
|
+
}
|
|
502
|
+
static format(str, ...args) {
|
|
503
|
+
let index = 0;
|
|
504
|
+
return str.replace(/%s/g, () => {
|
|
505
|
+
const arg = args[index++];
|
|
506
|
+
return arg !== undefined ? arg : "";
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
510
|
+
if (!authName) {
|
|
511
|
+
return "";
|
|
630
512
|
}
|
|
631
|
-
|
|
513
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
514
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
515
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
516
|
+
}
|
|
517
|
+
return safeRegistrationIdEnvName;
|
|
632
518
|
}
|
|
633
|
-
static
|
|
634
|
-
const
|
|
519
|
+
static getServerObject(spec, method, path) {
|
|
520
|
+
const pathObj = spec.paths[path];
|
|
521
|
+
const operationObject = pathObj[method];
|
|
522
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
523
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
524
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
525
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
526
|
+
return serverUrl;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Copyright (c) Microsoft Corporation.
|
|
531
|
+
class Validator {
|
|
532
|
+
listAPIs() {
|
|
533
|
+
var _a;
|
|
534
|
+
if (this.apiMap) {
|
|
535
|
+
return this.apiMap;
|
|
536
|
+
}
|
|
537
|
+
const paths = this.spec.paths;
|
|
635
538
|
const result = {};
|
|
636
539
|
for (const path in paths) {
|
|
637
540
|
const methods = paths[path];
|
|
638
541
|
for (const method in methods) {
|
|
639
|
-
|
|
640
|
-
if (
|
|
641
|
-
const
|
|
642
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
542
|
+
const operationObject = methods[method];
|
|
543
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
544
|
+
const validateResult = this.validateAPI(method, path);
|
|
545
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
546
|
+
operation: operationObject,
|
|
547
|
+
isValid: validateResult.isValid,
|
|
548
|
+
reason: validateResult.reason,
|
|
549
|
+
};
|
|
643
550
|
}
|
|
644
551
|
}
|
|
645
552
|
}
|
|
553
|
+
this.apiMap = result;
|
|
646
554
|
return result;
|
|
647
555
|
}
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
// Server validation
|
|
658
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
|
|
659
|
-
errors.push(...serverErrors);
|
|
660
|
-
// Remote reference not supported
|
|
661
|
-
const refPaths = parser.$refs.paths();
|
|
662
|
-
// refPaths [0] is the current spec file path
|
|
663
|
-
if (refPaths.length > 1) {
|
|
664
|
-
errors.push({
|
|
665
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
666
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
667
|
-
data: refPaths,
|
|
556
|
+
validateSpecVersion() {
|
|
557
|
+
const result = { errors: [], warnings: [] };
|
|
558
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
559
|
+
result.errors.push({
|
|
560
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
561
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
562
|
+
data: this.spec.openapi,
|
|
668
563
|
});
|
|
669
564
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
validateSpecServer() {
|
|
568
|
+
const result = { errors: [], warnings: [] };
|
|
569
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
570
|
+
result.errors.push(...serverErrors);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
validateSpecNoSupportAPI() {
|
|
574
|
+
const result = { errors: [], warnings: [] };
|
|
575
|
+
const apiMap = this.listAPIs();
|
|
576
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
577
|
+
if (validAPIs.length === 0) {
|
|
578
|
+
const data = [];
|
|
579
|
+
for (const key in apiMap) {
|
|
580
|
+
const { reason } = apiMap[key];
|
|
581
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
582
|
+
data.push(apiInvalidReason);
|
|
583
|
+
}
|
|
584
|
+
result.errors.push({
|
|
674
585
|
type: ErrorType.NoSupportedApi,
|
|
675
586
|
content: ConstantString.NoSupportedApi,
|
|
587
|
+
data,
|
|
676
588
|
});
|
|
677
589
|
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
validateSpecOperationId() {
|
|
593
|
+
const result = { errors: [], warnings: [] };
|
|
594
|
+
const apiMap = this.listAPIs();
|
|
678
595
|
// OperationId missing
|
|
679
596
|
const apisMissingOperationId = [];
|
|
680
597
|
for (const key in apiMap) {
|
|
681
|
-
const
|
|
682
|
-
if (!
|
|
598
|
+
const { operation } = apiMap[key];
|
|
599
|
+
if (!operation.operationId) {
|
|
683
600
|
apisMissingOperationId.push(key);
|
|
684
601
|
}
|
|
685
602
|
}
|
|
686
603
|
if (apisMissingOperationId.length > 0) {
|
|
687
|
-
warnings.push({
|
|
604
|
+
result.warnings.push({
|
|
688
605
|
type: WarningType.OperationIdMissing,
|
|
689
606
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
690
607
|
data: apisMissingOperationId,
|
|
691
608
|
});
|
|
692
609
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
610
|
+
return result;
|
|
611
|
+
}
|
|
612
|
+
validateMethodAndPath(method, path) {
|
|
613
|
+
const result = { isValid: true, reason: [] };
|
|
614
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
615
|
+
result.isValid = false;
|
|
616
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
const pathObj = this.spec.paths[path];
|
|
620
|
+
if (!pathObj || !pathObj[method]) {
|
|
621
|
+
result.isValid = false;
|
|
622
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
validateResponse(method, path) {
|
|
628
|
+
const result = { isValid: true, reason: [] };
|
|
629
|
+
const operationObject = this.spec.paths[path][method];
|
|
630
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
631
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
632
|
+
// only support response body only contains “application/json” content type
|
|
633
|
+
if (multipleMediaType) {
|
|
634
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
635
|
+
}
|
|
636
|
+
else if (Object.keys(json).length === 0) {
|
|
637
|
+
// response body should not be empty
|
|
638
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
validateServer(method, path) {
|
|
644
|
+
const result = { isValid: true, reason: [] };
|
|
645
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
646
|
+
if (!serverObj) {
|
|
647
|
+
// should contain server URL
|
|
648
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
696
649
|
}
|
|
697
|
-
else
|
|
698
|
-
|
|
650
|
+
else {
|
|
651
|
+
// server url should be absolute url with https protocol
|
|
652
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
653
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
validateAuth(method, path) {
|
|
658
|
+
const pathObj = this.spec.paths[path];
|
|
659
|
+
const operationObject = pathObj[method];
|
|
660
|
+
const securities = operationObject.security;
|
|
661
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
662
|
+
if (authSchemeArray.length === 0) {
|
|
663
|
+
return { isValid: true, reason: [] };
|
|
664
|
+
}
|
|
665
|
+
if (this.options.allowAPIKeyAuth ||
|
|
666
|
+
this.options.allowOauth2 ||
|
|
667
|
+
this.options.allowBearerTokenAuth) {
|
|
668
|
+
// Currently we don't support multiple auth in one operation
|
|
669
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
670
|
+
return {
|
|
671
|
+
isValid: false,
|
|
672
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
for (const auths of authSchemeArray) {
|
|
676
|
+
if (auths.length === 1) {
|
|
677
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
678
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
679
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
680
|
+
return { isValid: true, reason: [] };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
699
684
|
}
|
|
700
|
-
return {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
685
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
686
|
+
}
|
|
687
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
688
|
+
var _a;
|
|
689
|
+
const paramResult = {
|
|
690
|
+
requiredNum: 0,
|
|
691
|
+
optionalNum: 0,
|
|
692
|
+
isValid: true,
|
|
693
|
+
reason: [],
|
|
704
694
|
};
|
|
695
|
+
if (Object.keys(schema).length === 0) {
|
|
696
|
+
return paramResult;
|
|
697
|
+
}
|
|
698
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
699
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
700
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
701
|
+
paramResult.isValid = false;
|
|
702
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
703
|
+
return paramResult;
|
|
704
|
+
}
|
|
705
|
+
if (schema.type === "string" ||
|
|
706
|
+
schema.type === "integer" ||
|
|
707
|
+
schema.type === "boolean" ||
|
|
708
|
+
schema.type === "number") {
|
|
709
|
+
if (isRequiredWithoutDefault) {
|
|
710
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (schema.type === "object") {
|
|
717
|
+
const { properties } = schema;
|
|
718
|
+
for (const property in properties) {
|
|
719
|
+
let isRequired = false;
|
|
720
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
721
|
+
isRequired = true;
|
|
722
|
+
}
|
|
723
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
724
|
+
paramResult.requiredNum += result.requiredNum;
|
|
725
|
+
paramResult.optionalNum += result.optionalNum;
|
|
726
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
727
|
+
paramResult.reason.push(...result.reason);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
732
|
+
paramResult.isValid = false;
|
|
733
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return paramResult;
|
|
705
737
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
738
|
+
checkParamSchema(paramObject) {
|
|
739
|
+
const paramResult = {
|
|
740
|
+
requiredNum: 0,
|
|
741
|
+
optionalNum: 0,
|
|
742
|
+
isValid: true,
|
|
743
|
+
reason: [],
|
|
744
|
+
};
|
|
745
|
+
if (!paramObject) {
|
|
746
|
+
return paramResult;
|
|
747
|
+
}
|
|
748
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
749
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
750
|
+
const param = paramObject[i];
|
|
751
|
+
const schema = param.schema;
|
|
752
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
753
|
+
paramResult.isValid = false;
|
|
754
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
758
|
+
if (isCopilot) {
|
|
759
|
+
if (isRequiredWithoutDefault) {
|
|
760
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
768
|
+
if (isRequiredWithoutDefault) {
|
|
769
|
+
paramResult.isValid = false;
|
|
770
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (schema.type !== "boolean" &&
|
|
775
|
+
schema.type !== "string" &&
|
|
776
|
+
schema.type !== "number" &&
|
|
777
|
+
schema.type !== "integer") {
|
|
778
|
+
if (isRequiredWithoutDefault) {
|
|
779
|
+
paramResult.isValid = false;
|
|
780
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
781
|
+
}
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (param.in === "query" || param.in === "path") {
|
|
785
|
+
if (isRequiredWithoutDefault) {
|
|
786
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return paramResult;
|
|
712
794
|
}
|
|
713
|
-
|
|
714
|
-
if (
|
|
715
|
-
|
|
795
|
+
hasNestedObjectInSchema(schema) {
|
|
796
|
+
if (schema.type === "object") {
|
|
797
|
+
for (const property in schema.properties) {
|
|
798
|
+
const nestedSchema = schema.properties[property];
|
|
799
|
+
if (nestedSchema.type === "object") {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
716
803
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Copyright (c) Microsoft Corporation.
|
|
809
|
+
class CopilotValidator extends Validator {
|
|
810
|
+
constructor(spec, options) {
|
|
811
|
+
super();
|
|
812
|
+
this.projectType = ProjectType.Copilot;
|
|
813
|
+
this.options = options;
|
|
814
|
+
this.spec = spec;
|
|
815
|
+
}
|
|
816
|
+
validateSpec() {
|
|
817
|
+
const result = { errors: [], warnings: [] };
|
|
818
|
+
// validate spec version
|
|
819
|
+
let validationResult = this.validateSpecVersion();
|
|
820
|
+
result.errors.push(...validationResult.errors);
|
|
821
|
+
// validate spec server
|
|
822
|
+
validationResult = this.validateSpecServer();
|
|
823
|
+
result.errors.push(...validationResult.errors);
|
|
824
|
+
// validate no supported API
|
|
825
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
826
|
+
result.errors.push(...validationResult.errors);
|
|
827
|
+
// validate operationId missing
|
|
828
|
+
validationResult = this.validateSpecOperationId();
|
|
829
|
+
result.warnings.push(...validationResult.warnings);
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
validateAPI(method, path) {
|
|
833
|
+
const result = { isValid: true, reason: [] };
|
|
834
|
+
method = method.toLocaleLowerCase();
|
|
835
|
+
// validate method and path
|
|
836
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
837
|
+
if (!methodAndPathResult.isValid) {
|
|
838
|
+
return methodAndPathResult;
|
|
839
|
+
}
|
|
840
|
+
const operationObject = this.spec.paths[path][method];
|
|
841
|
+
// validate auth
|
|
842
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
843
|
+
result.reason.push(...authCheckResult.reason);
|
|
844
|
+
// validate operationId
|
|
845
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
846
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
847
|
+
}
|
|
848
|
+
// validate server
|
|
849
|
+
const validateServerResult = this.validateServer(method, path);
|
|
850
|
+
result.reason.push(...validateServerResult.reason);
|
|
851
|
+
// validate response
|
|
852
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
853
|
+
result.reason.push(...validateResponseResult.reason);
|
|
854
|
+
// validate requestBody
|
|
855
|
+
const requestBody = operationObject.requestBody;
|
|
856
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
857
|
+
if (requestJsonBody) {
|
|
858
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
859
|
+
if (requestBodySchema.type !== "object") {
|
|
860
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
861
|
+
}
|
|
862
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
863
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
864
|
+
}
|
|
865
|
+
// validate parameters
|
|
866
|
+
const paramObject = operationObject.parameters;
|
|
867
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
868
|
+
result.reason.push(...paramResult.reason);
|
|
869
|
+
if (result.reason.length > 0) {
|
|
870
|
+
result.isValid = false;
|
|
871
|
+
}
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Copyright (c) Microsoft Corporation.
|
|
877
|
+
class SMEValidator extends Validator {
|
|
878
|
+
constructor(spec, options) {
|
|
879
|
+
super();
|
|
880
|
+
this.projectType = ProjectType.SME;
|
|
881
|
+
this.options = options;
|
|
882
|
+
this.spec = spec;
|
|
883
|
+
}
|
|
884
|
+
validateSpec() {
|
|
885
|
+
const result = { errors: [], warnings: [] };
|
|
886
|
+
// validate spec version
|
|
887
|
+
let validationResult = this.validateSpecVersion();
|
|
888
|
+
result.errors.push(...validationResult.errors);
|
|
889
|
+
// validate spec server
|
|
890
|
+
validationResult = this.validateSpecServer();
|
|
891
|
+
result.errors.push(...validationResult.errors);
|
|
892
|
+
// validate no supported API
|
|
893
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
894
|
+
result.errors.push(...validationResult.errors);
|
|
895
|
+
// validate operationId missing
|
|
896
|
+
if (this.options.allowMissingId) {
|
|
897
|
+
validationResult = this.validateSpecOperationId();
|
|
898
|
+
result.warnings.push(...validationResult.warnings);
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
validateAPI(method, path) {
|
|
903
|
+
const result = { isValid: true, reason: [] };
|
|
904
|
+
method = method.toLocaleLowerCase();
|
|
905
|
+
// validate method and path
|
|
906
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
907
|
+
if (!methodAndPathResult.isValid) {
|
|
908
|
+
return methodAndPathResult;
|
|
909
|
+
}
|
|
910
|
+
const operationObject = this.spec.paths[path][method];
|
|
911
|
+
// validate auth
|
|
912
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
913
|
+
result.reason.push(...authCheckResult.reason);
|
|
914
|
+
// validate operationId
|
|
915
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
916
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
917
|
+
}
|
|
918
|
+
// validate server
|
|
919
|
+
const validateServerResult = this.validateServer(method, path);
|
|
920
|
+
result.reason.push(...validateServerResult.reason);
|
|
921
|
+
// validate response
|
|
922
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
923
|
+
result.reason.push(...validateResponseResult.reason);
|
|
924
|
+
let postBodyResult = {
|
|
925
|
+
requiredNum: 0,
|
|
926
|
+
optionalNum: 0,
|
|
927
|
+
isValid: true,
|
|
928
|
+
reason: [],
|
|
929
|
+
};
|
|
930
|
+
// validate requestBody
|
|
931
|
+
const requestBody = operationObject.requestBody;
|
|
932
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
933
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
934
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
935
|
+
}
|
|
936
|
+
if (requestJsonBody) {
|
|
937
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
938
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
939
|
+
result.reason.push(...postBodyResult.reason);
|
|
940
|
+
}
|
|
941
|
+
// validate parameters
|
|
942
|
+
const paramObject = operationObject.parameters;
|
|
943
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
944
|
+
result.reason.push(...paramResult.reason);
|
|
945
|
+
// validate total parameters count
|
|
946
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
947
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
948
|
+
result.reason.push(...paramCountResult.reason);
|
|
949
|
+
}
|
|
950
|
+
if (result.reason.length > 0) {
|
|
951
|
+
result.isValid = false;
|
|
952
|
+
}
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
956
|
+
const result = { isValid: true, reason: [] };
|
|
957
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
958
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
959
|
+
if (totalRequiredParams > 1) {
|
|
960
|
+
if (!this.options.allowMultipleParameters ||
|
|
961
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
962
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
else if (totalParams === 0) {
|
|
966
|
+
result.reason.push(ErrorType.NoParameter);
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
972
|
+
|
|
973
|
+
// Copyright (c) Microsoft Corporation.
|
|
974
|
+
class TeamsAIValidator extends Validator {
|
|
975
|
+
constructor(spec, options) {
|
|
976
|
+
super();
|
|
977
|
+
this.projectType = ProjectType.TeamsAi;
|
|
978
|
+
this.options = options;
|
|
979
|
+
this.spec = spec;
|
|
980
|
+
}
|
|
981
|
+
validateSpec() {
|
|
982
|
+
const result = { errors: [], warnings: [] };
|
|
983
|
+
// validate spec server
|
|
984
|
+
let validationResult = this.validateSpecServer();
|
|
985
|
+
result.errors.push(...validationResult.errors);
|
|
986
|
+
// validate no supported API
|
|
987
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
988
|
+
result.errors.push(...validationResult.errors);
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
validateAPI(method, path) {
|
|
992
|
+
const result = { isValid: true, reason: [] };
|
|
993
|
+
method = method.toLocaleLowerCase();
|
|
994
|
+
// validate method and path
|
|
995
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
996
|
+
if (!methodAndPathResult.isValid) {
|
|
997
|
+
return methodAndPathResult;
|
|
998
|
+
}
|
|
999
|
+
const operationObject = this.spec.paths[path][method];
|
|
1000
|
+
// validate operationId
|
|
1001
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1002
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1003
|
+
}
|
|
1004
|
+
// validate server
|
|
1005
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1006
|
+
result.reason.push(...validateServerResult.reason);
|
|
1007
|
+
if (result.reason.length > 0) {
|
|
1008
|
+
result.isValid = false;
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
class ValidatorFactory {
|
|
1015
|
+
static create(spec, options) {
|
|
1016
|
+
var _a;
|
|
1017
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1018
|
+
switch (type) {
|
|
1019
|
+
case ProjectType.SME:
|
|
1020
|
+
return new SMEValidator(spec, options);
|
|
1021
|
+
case ProjectType.Copilot:
|
|
1022
|
+
return new CopilotValidator(spec, options);
|
|
1023
|
+
case ProjectType.TeamsAi:
|
|
1024
|
+
return new TeamsAIValidator(spec, options);
|
|
1025
|
+
default:
|
|
1026
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
720
1027
|
}
|
|
721
|
-
return safeRegistrationIdEnvName;
|
|
722
1028
|
}
|
|
723
1029
|
}
|
|
724
1030
|
|
|
725
1031
|
// Copyright (c) Microsoft Corporation.
|
|
726
1032
|
class SpecFilter {
|
|
727
|
-
static specFilter(filter, unResolveSpec, resolvedSpec,
|
|
1033
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1034
|
+
var _a;
|
|
728
1035
|
try {
|
|
729
1036
|
const newSpec = Object.assign({}, unResolveSpec);
|
|
730
1037
|
const newPaths = {};
|
|
731
1038
|
for (const filterItem of filter) {
|
|
732
1039
|
const [method, path] = filterItem.split(" ");
|
|
733
1040
|
const methodName = method.toLowerCase();
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1041
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1042
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1043
|
+
pathObj &&
|
|
1044
|
+
pathObj[methodName]) {
|
|
1045
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1046
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1047
|
+
if (!validateResult.isValid) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (!newPaths[path]) {
|
|
1051
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1052
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1053
|
+
delete newPaths[path][m];
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1057
|
+
// Add the operationId if missing
|
|
1058
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1059
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
741
1060
|
}
|
|
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
1061
|
}
|
|
748
1062
|
}
|
|
749
1063
|
newSpec.paths = newPaths;
|
|
@@ -755,97 +1069,11 @@ class SpecFilter {
|
|
|
755
1069
|
}
|
|
756
1070
|
}
|
|
757
1071
|
|
|
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
1072
|
// Copyright (c) Microsoft Corporation.
|
|
845
1073
|
class AdaptiveCardGenerator {
|
|
846
1074
|
static generateAdaptiveCard(operationItem) {
|
|
847
1075
|
try {
|
|
848
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1076
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
849
1077
|
let cardBody = [];
|
|
850
1078
|
let schema = json.schema;
|
|
851
1079
|
let jsonPath = "$";
|
|
@@ -1011,6 +1239,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1011
1239
|
};
|
|
1012
1240
|
return result;
|
|
1013
1241
|
}
|
|
1242
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1243
|
+
const props = inferProperties(card);
|
|
1244
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1245
|
+
const result = {
|
|
1246
|
+
data_path: dataPath,
|
|
1247
|
+
};
|
|
1248
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1249
|
+
result.properties = {};
|
|
1250
|
+
if (props.title) {
|
|
1251
|
+
result.properties.title = "$." + props.title;
|
|
1252
|
+
}
|
|
1253
|
+
if (props.subtitle) {
|
|
1254
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1255
|
+
}
|
|
1256
|
+
if (props.imageUrl) {
|
|
1257
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
result.static_template = card;
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1014
1263
|
/**
|
|
1015
1264
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1016
1265
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1023,11 +1272,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1023
1272
|
* @returns The inferred preview card template.
|
|
1024
1273
|
*/
|
|
1025
1274
|
function inferPreviewCardTemplate(card) {
|
|
1026
|
-
var _a;
|
|
1027
1275
|
const result = {
|
|
1028
|
-
title: "",
|
|
1276
|
+
title: "result",
|
|
1029
1277
|
};
|
|
1030
|
-
const
|
|
1278
|
+
const inferredProperties = inferProperties(card);
|
|
1279
|
+
if (inferredProperties.title) {
|
|
1280
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1281
|
+
}
|
|
1282
|
+
if (inferredProperties.subtitle) {
|
|
1283
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1284
|
+
}
|
|
1285
|
+
if (inferredProperties.imageUrl) {
|
|
1286
|
+
result.image = {
|
|
1287
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1288
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1289
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return result;
|
|
1293
|
+
}
|
|
1294
|
+
function inferProperties(card) {
|
|
1295
|
+
var _a;
|
|
1296
|
+
const result = {};
|
|
1297
|
+
const nameSet = new Set();
|
|
1031
1298
|
let rootObject;
|
|
1032
1299
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1033
1300
|
rootObject = card.body[0].items;
|
|
@@ -1040,56 +1307,399 @@ function inferPreviewCardTemplate(card) {
|
|
|
1040
1307
|
const textElement = element;
|
|
1041
1308
|
const index = textElement.text.indexOf("${if(");
|
|
1042
1309
|
if (index > 0) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1310
|
+
const text = textElement.text.substring(index);
|
|
1311
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1312
|
+
const property = match ? match[1] : "";
|
|
1313
|
+
if (property) {
|
|
1314
|
+
nameSet.add(property);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1319
|
+
const imageElement = element;
|
|
1320
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1321
|
+
const property = match ? match[1] : "";
|
|
1322
|
+
if (property) {
|
|
1323
|
+
nameSet.add(property);
|
|
1045
1324
|
}
|
|
1046
1325
|
}
|
|
1047
1326
|
}
|
|
1048
|
-
for (const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
textBlockElements.delete(element);
|
|
1327
|
+
for (const name of nameSet) {
|
|
1328
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1329
|
+
result.title = name;
|
|
1330
|
+
nameSet.delete(name);
|
|
1053
1331
|
}
|
|
1054
1332
|
else if (!result.subtitle &&
|
|
1055
|
-
Utils.isWellKnownName(
|
|
1056
|
-
result.subtitle =
|
|
1057
|
-
|
|
1333
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1334
|
+
result.subtitle = name;
|
|
1335
|
+
nameSet.delete(name);
|
|
1058
1336
|
}
|
|
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);
|
|
1337
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1338
|
+
result.imageUrl = name;
|
|
1339
|
+
nameSet.delete(name);
|
|
1070
1340
|
}
|
|
1071
1341
|
}
|
|
1072
|
-
for (const
|
|
1073
|
-
const text = element.text;
|
|
1342
|
+
for (const name of nameSet) {
|
|
1074
1343
|
if (!result.title) {
|
|
1075
|
-
result.title =
|
|
1076
|
-
|
|
1344
|
+
result.title = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1077
1346
|
}
|
|
1078
1347
|
else if (!result.subtitle) {
|
|
1079
|
-
result.subtitle =
|
|
1080
|
-
|
|
1348
|
+
result.subtitle = name;
|
|
1349
|
+
nameSet.delete(name);
|
|
1081
1350
|
}
|
|
1082
1351
|
}
|
|
1083
1352
|
if (!result.title && result.subtitle) {
|
|
1084
1353
|
result.title = result.subtitle;
|
|
1085
1354
|
delete result.subtitle;
|
|
1086
1355
|
}
|
|
1087
|
-
if (!result.title) {
|
|
1088
|
-
result.title = "result";
|
|
1089
|
-
}
|
|
1090
1356
|
return result;
|
|
1091
1357
|
}
|
|
1092
1358
|
|
|
1359
|
+
// Copyright (c) Microsoft Corporation.
|
|
1360
|
+
class ManifestUpdater {
|
|
1361
|
+
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
|
|
1362
|
+
const manifest = await fs.readJSON(manifestPath);
|
|
1363
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1364
|
+
manifest.plugins = [
|
|
1365
|
+
{
|
|
1366
|
+
file: apiPluginRelativePath,
|
|
1367
|
+
id: ConstantString.DefaultPluginId,
|
|
1368
|
+
},
|
|
1369
|
+
];
|
|
1370
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1371
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1372
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1373
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
|
|
1374
|
+
return [manifest, apiPlugin];
|
|
1375
|
+
}
|
|
1376
|
+
static updateManifestDescription(manifest, spec) {
|
|
1377
|
+
var _a, _b;
|
|
1378
|
+
manifest.description = {
|
|
1379
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1380
|
+
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),
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
1384
|
+
let parameter;
|
|
1385
|
+
if (schema.type === "array") {
|
|
1386
|
+
const items = schema.items;
|
|
1387
|
+
parameter = {
|
|
1388
|
+
type: "array",
|
|
1389
|
+
items: ManifestUpdater.mapOpenAPISchemaToFuncParam(items, method, pathUrl),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
else if (schema.type === "string" ||
|
|
1393
|
+
schema.type === "boolean" ||
|
|
1394
|
+
schema.type === "integer" ||
|
|
1395
|
+
schema.type === "number") {
|
|
1396
|
+
parameter = {
|
|
1397
|
+
type: schema.type,
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
|
|
1402
|
+
}
|
|
1403
|
+
if (schema.enum) {
|
|
1404
|
+
parameter.enum = schema.enum;
|
|
1405
|
+
}
|
|
1406
|
+
if (schema.description) {
|
|
1407
|
+
parameter.description = schema.description;
|
|
1408
|
+
}
|
|
1409
|
+
if (schema.default) {
|
|
1410
|
+
parameter.default = schema.default;
|
|
1411
|
+
}
|
|
1412
|
+
return parameter;
|
|
1413
|
+
}
|
|
1414
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
|
|
1415
|
+
var _a, _b, _c, _d, _e;
|
|
1416
|
+
const functions = [];
|
|
1417
|
+
const functionNames = [];
|
|
1418
|
+
const conversationStarters = [];
|
|
1419
|
+
const paths = spec.paths;
|
|
1420
|
+
const pluginAuthObj = {
|
|
1421
|
+
type: "None",
|
|
1422
|
+
};
|
|
1423
|
+
if (authInfo) {
|
|
1424
|
+
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
|
|
1425
|
+
pluginAuthObj.type = "OAuthPluginVault";
|
|
1426
|
+
}
|
|
1427
|
+
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
|
|
1428
|
+
pluginAuthObj.type = "ApiKeyPluginVault";
|
|
1429
|
+
}
|
|
1430
|
+
if (pluginAuthObj.type !== "None") {
|
|
1431
|
+
pluginAuthObj.reference_id = `${Utils.getSafeRegistrationIdEnvName(authInfo.name)}_REGISTRATION_ID`;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
for (const pathUrl in paths) {
|
|
1435
|
+
const pathItem = paths[pathUrl];
|
|
1436
|
+
if (pathItem) {
|
|
1437
|
+
const operations = pathItem;
|
|
1438
|
+
for (const method in operations) {
|
|
1439
|
+
if (options.allowMethods.includes(method)) {
|
|
1440
|
+
const operationItem = operations[method];
|
|
1441
|
+
const confirmationBodies = [];
|
|
1442
|
+
if (operationItem) {
|
|
1443
|
+
const operationId = operationItem.operationId;
|
|
1444
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1445
|
+
const paramObject = operationItem.parameters;
|
|
1446
|
+
const requestBody = operationItem.requestBody;
|
|
1447
|
+
const parameters = {
|
|
1448
|
+
type: "object",
|
|
1449
|
+
properties: {},
|
|
1450
|
+
required: [],
|
|
1451
|
+
};
|
|
1452
|
+
if (paramObject) {
|
|
1453
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1454
|
+
const param = paramObject[i];
|
|
1455
|
+
const schema = param.schema;
|
|
1456
|
+
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1457
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
|
|
1458
|
+
if (param.required) {
|
|
1459
|
+
parameters.required.push(param.name);
|
|
1460
|
+
}
|
|
1461
|
+
if (!parameters.properties[param.name].description) {
|
|
1462
|
+
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (requestBody) {
|
|
1467
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1468
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1469
|
+
if (requestBodySchema.type === "object") {
|
|
1470
|
+
if (requestBodySchema.required) {
|
|
1471
|
+
parameters.required.push(...requestBodySchema.required);
|
|
1472
|
+
}
|
|
1473
|
+
for (const property in requestBodySchema.properties) {
|
|
1474
|
+
const schema = requestBodySchema.properties[property];
|
|
1475
|
+
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1476
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const funcObj = {
|
|
1484
|
+
name: operationId,
|
|
1485
|
+
description: description,
|
|
1486
|
+
};
|
|
1487
|
+
if (paramObject || requestBody) {
|
|
1488
|
+
funcObj.parameters = parameters;
|
|
1489
|
+
}
|
|
1490
|
+
if (options.allowResponseSemantics) {
|
|
1491
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1492
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1493
|
+
funcObj.capabilities = {
|
|
1494
|
+
response_semantics: responseSemantic,
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
|
|
1498
|
+
if (!funcObj.capabilities) {
|
|
1499
|
+
funcObj.capabilities = {};
|
|
1500
|
+
}
|
|
1501
|
+
funcObj.capabilities.confirmation = {
|
|
1502
|
+
type: "AdaptiveCard",
|
|
1503
|
+
title: (_c = operationItem.summary) !== null && _c !== void 0 ? _c : description,
|
|
1504
|
+
};
|
|
1505
|
+
if (confirmationBodies.length > 0) {
|
|
1506
|
+
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
functions.push(funcObj);
|
|
1510
|
+
functionNames.push(operationId);
|
|
1511
|
+
if (description) {
|
|
1512
|
+
conversationStarters.push(description);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
let apiPlugin;
|
|
1520
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1521
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
apiPlugin = {
|
|
1525
|
+
schema_version: "v2.1",
|
|
1526
|
+
name_for_human: "",
|
|
1527
|
+
description_for_human: "",
|
|
1528
|
+
namespace: "",
|
|
1529
|
+
functions: [],
|
|
1530
|
+
runtimes: [],
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1534
|
+
for (const func of functions) {
|
|
1535
|
+
const index = (_d = apiPlugin.functions) === null || _d === void 0 ? void 0 : _d.findIndex((f) => f.name === func.name);
|
|
1536
|
+
if (index === -1) {
|
|
1537
|
+
apiPlugin.functions.push(func);
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
apiPlugin.functions[index] = func;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1544
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => {
|
|
1545
|
+
var _a, _b;
|
|
1546
|
+
return runtime.spec.url === specRelativePath &&
|
|
1547
|
+
runtime.type === "OpenApi" &&
|
|
1548
|
+
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
|
|
1549
|
+
});
|
|
1550
|
+
if (index === -1) {
|
|
1551
|
+
apiPlugin.runtimes.push({
|
|
1552
|
+
type: "OpenApi",
|
|
1553
|
+
auth: pluginAuthObj,
|
|
1554
|
+
spec: {
|
|
1555
|
+
url: specRelativePath,
|
|
1556
|
+
},
|
|
1557
|
+
run_for_functions: functionNames,
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1562
|
+
}
|
|
1563
|
+
if (!apiPlugin.name_for_human) {
|
|
1564
|
+
apiPlugin.name_for_human = appName;
|
|
1565
|
+
}
|
|
1566
|
+
if (!apiPlugin.namespace) {
|
|
1567
|
+
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
|
|
1568
|
+
}
|
|
1569
|
+
if (!apiPlugin.description_for_human) {
|
|
1570
|
+
apiPlugin.description_for_human =
|
|
1571
|
+
(_e = spec.info.description) !== null && _e !== void 0 ? _e : "<Please add description of the plugin>";
|
|
1572
|
+
}
|
|
1573
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1574
|
+
if (!apiPlugin.capabilities) {
|
|
1575
|
+
apiPlugin.capabilities = {
|
|
1576
|
+
localization: {},
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1580
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1581
|
+
.slice(0, 5)
|
|
1582
|
+
.map((text) => ({ text }));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return apiPlugin;
|
|
1586
|
+
}
|
|
1587
|
+
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
1588
|
+
try {
|
|
1589
|
+
const originalManifest = await fs.readJSON(manifestPath);
|
|
1590
|
+
const updatedPart = {};
|
|
1591
|
+
updatedPart.composeExtensions = [];
|
|
1592
|
+
let warnings = [];
|
|
1593
|
+
if (options.projectType === ProjectType.SME) {
|
|
1594
|
+
const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1595
|
+
const commands = updateResult[0];
|
|
1596
|
+
warnings = updateResult[1];
|
|
1597
|
+
const composeExtension = {
|
|
1598
|
+
composeExtensionType: "apiBased",
|
|
1599
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1600
|
+
commands: commands,
|
|
1601
|
+
};
|
|
1602
|
+
if (authInfo) {
|
|
1603
|
+
const auth = authInfo.authScheme;
|
|
1604
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1605
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1606
|
+
composeExtension.authorization = {
|
|
1607
|
+
authType: "apiSecretServiceAuth",
|
|
1608
|
+
apiSecretServiceAuthConfiguration: {
|
|
1609
|
+
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1610
|
+
},
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1614
|
+
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1615
|
+
composeExtension.authorization = {
|
|
1616
|
+
authType: "oAuth2.0",
|
|
1617
|
+
oAuthConfiguration: {
|
|
1618
|
+
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1619
|
+
},
|
|
1620
|
+
};
|
|
1621
|
+
updatedPart.webApplicationInfo = {
|
|
1622
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1623
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
1628
|
+
}
|
|
1629
|
+
updatedPart.description = originalManifest.description;
|
|
1630
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
1631
|
+
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
1632
|
+
return [updatedManifest, warnings];
|
|
1633
|
+
}
|
|
1634
|
+
catch (err) {
|
|
1635
|
+
throw new SpecParserError(err.toString(), ErrorType.UpdateManifestFailed);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
static async generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1639
|
+
var _a;
|
|
1640
|
+
const paths = spec.paths;
|
|
1641
|
+
const commands = [];
|
|
1642
|
+
const warnings = [];
|
|
1643
|
+
if (paths) {
|
|
1644
|
+
for (const pathUrl in paths) {
|
|
1645
|
+
const pathItem = paths[pathUrl];
|
|
1646
|
+
if (pathItem) {
|
|
1647
|
+
const operations = pathItem;
|
|
1648
|
+
// Currently only support GET and POST method
|
|
1649
|
+
for (const method in operations) {
|
|
1650
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1651
|
+
const operationItem = operations[method];
|
|
1652
|
+
if (operationItem) {
|
|
1653
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1654
|
+
if (command.parameters &&
|
|
1655
|
+
command.parameters.length >= 1 &&
|
|
1656
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1657
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1658
|
+
}
|
|
1659
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1660
|
+
command.parameters = [command.parameters[0]];
|
|
1661
|
+
warnings.push({
|
|
1662
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1663
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1664
|
+
data: command.id,
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
if (adaptiveCardFolder) {
|
|
1668
|
+
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1669
|
+
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1670
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1671
|
+
: "";
|
|
1672
|
+
}
|
|
1673
|
+
commands.push(command);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return [commands, warnings];
|
|
1681
|
+
}
|
|
1682
|
+
static getRelativePath(from, to) {
|
|
1683
|
+
const relativePath = path.relative(path.dirname(from), to);
|
|
1684
|
+
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1685
|
+
}
|
|
1686
|
+
static removeEnvs(str) {
|
|
1687
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1688
|
+
const matches = placeHolderReg.exec(str);
|
|
1689
|
+
let newStr = str;
|
|
1690
|
+
if (matches != null) {
|
|
1691
|
+
newStr = newStr.replace(matches[0], "");
|
|
1692
|
+
}
|
|
1693
|
+
return newStr;
|
|
1694
|
+
}
|
|
1695
|
+
static removeAllSpecialCharacters(str) {
|
|
1696
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1697
|
+
}
|
|
1698
|
+
static getConfirmationBodyItem(paramName) {
|
|
1699
|
+
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1093
1703
|
// Copyright (c) Microsoft Corporation.
|
|
1094
1704
|
/**
|
|
1095
1705
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1105,8 +1715,14 @@ class SpecParser {
|
|
|
1105
1715
|
allowMissingId: true,
|
|
1106
1716
|
allowSwagger: true,
|
|
1107
1717
|
allowAPIKeyAuth: false,
|
|
1718
|
+
allowBearerTokenAuth: false,
|
|
1108
1719
|
allowMultipleParameters: false,
|
|
1109
1720
|
allowOauth2: false,
|
|
1721
|
+
allowMethods: ["get", "post"],
|
|
1722
|
+
allowConversationStarters: false,
|
|
1723
|
+
allowResponseSemantics: false,
|
|
1724
|
+
allowConfirmation: false,
|
|
1725
|
+
projectType: ProjectType.SME,
|
|
1110
1726
|
};
|
|
1111
1727
|
this.pathOrSpec = pathOrDoc;
|
|
1112
1728
|
this.parser = new SwaggerParser();
|
|
@@ -1130,6 +1746,8 @@ class SpecParser {
|
|
|
1130
1746
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1131
1747
|
};
|
|
1132
1748
|
}
|
|
1749
|
+
const errors = [];
|
|
1750
|
+
const warnings = [];
|
|
1133
1751
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1134
1752
|
return {
|
|
1135
1753
|
status: ValidationStatus.Error,
|
|
@@ -1139,7 +1757,38 @@ class SpecParser {
|
|
|
1139
1757
|
],
|
|
1140
1758
|
};
|
|
1141
1759
|
}
|
|
1142
|
-
|
|
1760
|
+
// Remote reference not supported
|
|
1761
|
+
const refPaths = this.parser.$refs.paths();
|
|
1762
|
+
// refPaths [0] is the current spec file path
|
|
1763
|
+
if (refPaths.length > 1) {
|
|
1764
|
+
errors.push({
|
|
1765
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1766
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1767
|
+
data: refPaths,
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1771
|
+
warnings.push({
|
|
1772
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1773
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
const validator = this.getValidator(this.spec);
|
|
1777
|
+
const validationResult = validator.validateSpec();
|
|
1778
|
+
warnings.push(...validationResult.warnings);
|
|
1779
|
+
errors.push(...validationResult.errors);
|
|
1780
|
+
let status = ValidationStatus.Valid;
|
|
1781
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1782
|
+
status = ValidationStatus.Warning;
|
|
1783
|
+
}
|
|
1784
|
+
else if (errors.length > 0) {
|
|
1785
|
+
status = ValidationStatus.Error;
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
status: status,
|
|
1789
|
+
warnings: warnings,
|
|
1790
|
+
errors: errors,
|
|
1791
|
+
};
|
|
1143
1792
|
}
|
|
1144
1793
|
catch (err) {
|
|
1145
1794
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1159,39 +1808,40 @@ class SpecParser {
|
|
|
1159
1808
|
try {
|
|
1160
1809
|
await this.loadSpec();
|
|
1161
1810
|
const spec = this.spec;
|
|
1162
|
-
const apiMap = this.
|
|
1163
|
-
const result =
|
|
1811
|
+
const apiMap = this.getAPIs(spec);
|
|
1812
|
+
const result = {
|
|
1813
|
+
APIs: [],
|
|
1814
|
+
allAPICount: 0,
|
|
1815
|
+
validAPICount: 0,
|
|
1816
|
+
};
|
|
1164
1817
|
for (const apiKey in apiMap) {
|
|
1818
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1819
|
+
const [method, path] = apiKey.split(" ");
|
|
1820
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1165
1821
|
const apiResult = {
|
|
1166
|
-
api:
|
|
1822
|
+
api: apiKey,
|
|
1167
1823
|
server: "",
|
|
1168
|
-
operationId:
|
|
1824
|
+
operationId: operationId,
|
|
1825
|
+
isValid: isValid,
|
|
1826
|
+
reason: reason,
|
|
1169
1827
|
};
|
|
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;
|
|
1828
|
+
if (isValid) {
|
|
1829
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1830
|
+
if (serverObj) {
|
|
1831
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1832
|
+
}
|
|
1833
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1834
|
+
for (const auths of authArray) {
|
|
1835
|
+
if (auths.length === 1) {
|
|
1836
|
+
apiResult.auth = auths[0];
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1190
1839
|
}
|
|
1191
1840
|
}
|
|
1192
|
-
apiResult
|
|
1193
|
-
result.push(apiResult);
|
|
1841
|
+
result.APIs.push(apiResult);
|
|
1194
1842
|
}
|
|
1843
|
+
result.allAPICount = result.APIs.length;
|
|
1844
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1195
1845
|
return result;
|
|
1196
1846
|
}
|
|
1197
1847
|
catch (err) {
|
|
@@ -1214,7 +1864,7 @@ class SpecParser {
|
|
|
1214
1864
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1215
1865
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1216
1866
|
}
|
|
1217
|
-
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options
|
|
1867
|
+
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
|
|
1218
1868
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1219
1869
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1220
1870
|
}
|
|
@@ -1233,10 +1883,9 @@ class SpecParser {
|
|
|
1233
1883
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1234
1884
|
* @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
1885
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1236
|
-
* @param
|
|
1237
|
-
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
1886
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1238
1887
|
*/
|
|
1239
|
-
async
|
|
1888
|
+
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1240
1889
|
const result = {
|
|
1241
1890
|
allSuccess: true,
|
|
1242
1891
|
warnings: [],
|
|
@@ -1245,52 +1894,66 @@ class SpecParser {
|
|
|
1245
1894
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1246
1895
|
const newUnResolvedSpec = newSpecs[0];
|
|
1247
1896
|
const newSpec = newSpecs[1];
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
const operation = newSpec.paths[url][method];
|
|
1253
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1254
|
-
if (authArray && authArray.length > 0) {
|
|
1255
|
-
AuthSet.add(authArray[0][0].authSchema);
|
|
1256
|
-
if (AuthSet.size > 1) {
|
|
1257
|
-
hasMultipleAPIKeyAuth = true;
|
|
1258
|
-
break;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1897
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1898
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1899
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1900
|
+
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1262
1901
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1902
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1903
|
+
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1904
|
+
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1905
|
+
}
|
|
1906
|
+
catch (err) {
|
|
1907
|
+
if (err instanceof SpecParserError) {
|
|
1908
|
+
throw err;
|
|
1265
1909
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1910
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateFailed);
|
|
1911
|
+
}
|
|
1912
|
+
return result;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1916
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1917
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1918
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1919
|
+
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1920
|
+
*/
|
|
1921
|
+
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
1922
|
+
const result = {
|
|
1923
|
+
allSuccess: true,
|
|
1924
|
+
warnings: [],
|
|
1925
|
+
};
|
|
1926
|
+
try {
|
|
1927
|
+
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1928
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1929
|
+
const newSpec = newSpecs[1];
|
|
1930
|
+
let authInfo = undefined;
|
|
1931
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1932
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1269
1933
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
});
|
|
1934
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1935
|
+
if (adaptiveCardFolder) {
|
|
1936
|
+
for (const url in newSpec.paths) {
|
|
1937
|
+
for (const method in newSpec.paths[url]) {
|
|
1938
|
+
// paths object may contain description/summary which is not a http method, so we need to check if it is a operation object
|
|
1939
|
+
if (this.options.allowMethods.includes(method)) {
|
|
1940
|
+
const operation = newSpec.paths[url][method];
|
|
1941
|
+
try {
|
|
1942
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
|
|
1943
|
+
const fileName = path.join(adaptiveCardFolder, `${operation.operationId}.json`);
|
|
1944
|
+
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
|
|
1945
|
+
await fs.outputJSON(fileName, wrappedCard, { spaces: 2 });
|
|
1946
|
+
const dataFileName = path.join(adaptiveCardFolder, `${operation.operationId}.data.json`);
|
|
1947
|
+
await fs.outputJSON(dataFileName, {}, { spaces: 2 });
|
|
1948
|
+
}
|
|
1949
|
+
catch (err) {
|
|
1950
|
+
result.allSuccess = false;
|
|
1951
|
+
result.warnings.push({
|
|
1952
|
+
type: WarningType.GenerateCardFailed,
|
|
1953
|
+
content: err.toString(),
|
|
1954
|
+
data: operation.operationId,
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1294
1957
|
}
|
|
1295
1958
|
}
|
|
1296
1959
|
}
|
|
@@ -1298,8 +1961,7 @@ class SpecParser {
|
|
|
1298
1961
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1299
1962
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1300
1963
|
}
|
|
1301
|
-
const
|
|
1302
|
-
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, newSpec, this.options.allowMultipleParameters, auth, isMe);
|
|
1964
|
+
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1303
1965
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1304
1966
|
result.warnings.push(...warnings);
|
|
1305
1967
|
}
|
|
@@ -1324,15 +1986,30 @@ class SpecParser {
|
|
|
1324
1986
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1325
1987
|
}
|
|
1326
1988
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1989
|
+
getAPIs(spec) {
|
|
1990
|
+
const validator = this.getValidator(spec);
|
|
1991
|
+
const apiMap = validator.listAPIs();
|
|
1992
|
+
return apiMap;
|
|
1993
|
+
}
|
|
1994
|
+
getValidator(spec) {
|
|
1995
|
+
if (this.validator) {
|
|
1996
|
+
return this.validator;
|
|
1330
1997
|
}
|
|
1331
|
-
const
|
|
1332
|
-
this.
|
|
1333
|
-
return
|
|
1998
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1999
|
+
this.validator = validator;
|
|
2000
|
+
return validator;
|
|
2001
|
+
}
|
|
2002
|
+
async saveFilterSpec(outputSpecPath, unResolvedSpec) {
|
|
2003
|
+
let resultStr;
|
|
2004
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
2005
|
+
resultStr = jsyaml.dump(unResolvedSpec);
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
resultStr = JSON.stringify(unResolvedSpec, null, 2);
|
|
2009
|
+
}
|
|
2010
|
+
await fs.outputFile(outputSpecPath, resultStr);
|
|
1334
2011
|
}
|
|
1335
2012
|
}
|
|
1336
2013
|
|
|
1337
|
-
export { AdaptiveCardGenerator, ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
2014
|
+
export { AdaptiveCardGenerator, ConstantString, ErrorType, ProjectType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1338
2015
|
//# sourceMappingURL=index.esm2017.mjs.map
|