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