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