@microsoft/m365-spec-parser 0.0.2-alpha.2 → 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.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";
|
|
@@ -27,6 +28,22 @@ var ErrorType;
|
|
|
27
28
|
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
|
|
28
29
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
29
30
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
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";
|
|
30
47
|
ErrorType["Cancelled"] = "cancelled";
|
|
31
48
|
ErrorType["Unknown"] = "unknown";
|
|
32
49
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -49,7 +66,13 @@ var ValidationStatus;
|
|
|
49
66
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
50
67
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
51
68
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
52
|
-
})(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 = {}));
|
|
53
76
|
|
|
54
77
|
// Copyright (c) Microsoft Corporation.
|
|
55
78
|
class ConstantString {
|
|
@@ -60,7 +83,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
60
83
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
61
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.";
|
|
62
85
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
63
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
86
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
64
87
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
65
88
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
66
89
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -68,7 +91,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
68
91
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
69
92
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
70
93
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
71
|
-
ConstantString.
|
|
94
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
95
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
96
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
72
97
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
73
98
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
74
99
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -78,8 +103,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
78
103
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
79
104
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
80
105
|
ConstantString.TextBlockType = "TextBlock";
|
|
106
|
+
ConstantString.ImageType = "Image";
|
|
81
107
|
ConstantString.ContainerType = "Container";
|
|
82
|
-
ConstantString.RegistrationIdPostfix =
|
|
108
|
+
ConstantString.RegistrationIdPostfix = {
|
|
109
|
+
apiKey: "REGISTRATION_ID",
|
|
110
|
+
oauth2: "CONFIGURATION_ID",
|
|
111
|
+
http: "REGISTRATION_ID",
|
|
112
|
+
openIdConnect: "REGISTRATION_ID",
|
|
113
|
+
};
|
|
83
114
|
ConstantString.ResponseCodeFor20X = [
|
|
84
115
|
"200",
|
|
85
116
|
"201",
|
|
@@ -138,8 +169,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
138
169
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
139
170
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
140
171
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
172
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
141
173
|
ConstantString.CommandTitleMaxLens = 32;
|
|
142
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
174
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
175
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
176
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
143
177
|
|
|
144
178
|
// Copyright (c) Microsoft Corporation.
|
|
145
179
|
class SpecParserError extends Error {
|
|
@@ -151,214 +185,44 @@ class SpecParserError extends Error {
|
|
|
151
185
|
|
|
152
186
|
// Copyright (c) Microsoft Corporation.
|
|
153
187
|
class Utils {
|
|
154
|
-
static
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
if (!paramObject) {
|
|
161
|
-
return paramResult;
|
|
162
|
-
}
|
|
163
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
164
|
-
const param = paramObject[i];
|
|
165
|
-
const schema = param.schema;
|
|
166
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
167
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
168
|
-
if (isRequiredWithoutDefault) {
|
|
169
|
-
paramResult.isValid = false;
|
|
170
|
-
}
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (schema.type !== "boolean" &&
|
|
174
|
-
schema.type !== "string" &&
|
|
175
|
-
schema.type !== "number" &&
|
|
176
|
-
schema.type !== "integer") {
|
|
177
|
-
if (isRequiredWithoutDefault) {
|
|
178
|
-
paramResult.isValid = false;
|
|
179
|
-
}
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (param.in === "query" || param.in === "path") {
|
|
183
|
-
if (isRequiredWithoutDefault) {
|
|
184
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return paramResult;
|
|
192
|
-
}
|
|
193
|
-
static checkPostBody(schema, isRequired = false) {
|
|
194
|
-
var _a;
|
|
195
|
-
const paramResult = {
|
|
196
|
-
requiredNum: 0,
|
|
197
|
-
optionalNum: 0,
|
|
198
|
-
isValid: true,
|
|
199
|
-
};
|
|
200
|
-
if (Object.keys(schema).length === 0) {
|
|
201
|
-
return paramResult;
|
|
202
|
-
}
|
|
203
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
204
|
-
if (schema.type === "string" ||
|
|
205
|
-
schema.type === "integer" ||
|
|
206
|
-
schema.type === "boolean" ||
|
|
207
|
-
schema.type === "number") {
|
|
208
|
-
if (isRequiredWithoutDefault) {
|
|
209
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
else if (schema.type === "object") {
|
|
216
|
-
const { properties } = schema;
|
|
217
|
-
for (const property in properties) {
|
|
218
|
-
let isRequired = false;
|
|
219
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
220
|
-
isRequired = true;
|
|
221
|
-
}
|
|
222
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
223
|
-
paramResult.requiredNum += result.requiredNum;
|
|
224
|
-
paramResult.optionalNum += result.optionalNum;
|
|
225
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
if (isRequiredWithoutDefault) {
|
|
230
|
-
paramResult.isValid = false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return paramResult;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Checks if the given API is supported.
|
|
237
|
-
* @param {string} method - The HTTP method of the API.
|
|
238
|
-
* @param {string} path - The path of the API.
|
|
239
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
240
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
241
|
-
* @description The following APIs are supported:
|
|
242
|
-
* 1. only support Get/Post operation without auth property
|
|
243
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
244
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
245
|
-
* 4. request body + required parameters <= 1
|
|
246
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
247
|
-
* 6. only support request body with “application/json” content type
|
|
248
|
-
*/
|
|
249
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
250
|
-
const pathObj = spec.paths[path];
|
|
251
|
-
method = method.toLocaleLowerCase();
|
|
252
|
-
if (pathObj) {
|
|
253
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
254
|
-
pathObj[method]) {
|
|
255
|
-
const securities = pathObj[method].security;
|
|
256
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
257
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
const operationObject = pathObj[method];
|
|
261
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
const paramObject = operationObject.parameters;
|
|
265
|
-
const requestBody = operationObject.requestBody;
|
|
266
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
267
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
268
|
-
if (mediaTypesCount > 1) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
272
|
-
if (Object.keys(responseJson).length === 0) {
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
let requestBodyParamResult = {
|
|
276
|
-
requiredNum: 0,
|
|
277
|
-
optionalNum: 0,
|
|
278
|
-
isValid: true,
|
|
279
|
-
};
|
|
280
|
-
if (requestJsonBody) {
|
|
281
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
282
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
283
|
-
}
|
|
284
|
-
if (!requestBodyParamResult.isValid) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
288
|
-
if (!paramResult.isValid) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
292
|
-
if (allowMultipleParameters &&
|
|
293
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
else if (requestBodyParamResult.requiredNum +
|
|
299
|
-
requestBodyParamResult.optionalNum +
|
|
300
|
-
paramResult.requiredNum +
|
|
301
|
-
paramResult.optionalNum ===
|
|
302
|
-
0) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
188
|
+
static hasNestedObjectInSchema(schema) {
|
|
189
|
+
if (schema.type === "object") {
|
|
190
|
+
for (const property in schema.properties) {
|
|
191
|
+
const nestedSchema = schema.properties[property];
|
|
192
|
+
if (nestedSchema.type === "object") {
|
|
306
193
|
return true;
|
|
307
194
|
}
|
|
308
195
|
}
|
|
309
196
|
}
|
|
310
197
|
return false;
|
|
311
198
|
}
|
|
312
|
-
static
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// Currently we don't support multiple auth in one operation
|
|
318
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
for (const auths of authSchemaArray) {
|
|
322
|
-
if (auths.length === 1) {
|
|
323
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
else if (!allowAPIKeyAuth &&
|
|
327
|
-
allowOauth2 &&
|
|
328
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
else if (allowAPIKeyAuth &&
|
|
332
|
-
allowOauth2 &&
|
|
333
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
334
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return false;
|
|
199
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
200
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
201
|
+
}
|
|
202
|
+
static isBearerTokenAuth(authScheme) {
|
|
203
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
341
204
|
}
|
|
342
|
-
static isAPIKeyAuth(
|
|
343
|
-
return
|
|
205
|
+
static isAPIKeyAuth(authScheme) {
|
|
206
|
+
return authScheme.type === "apiKey";
|
|
344
207
|
}
|
|
345
|
-
static
|
|
346
|
-
return (
|
|
347
|
-
|
|
348
|
-
|
|
208
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
209
|
+
return !!(authScheme.type === "oauth2" &&
|
|
210
|
+
authScheme.flows &&
|
|
211
|
+
authScheme.flows.authorizationCode);
|
|
349
212
|
}
|
|
350
213
|
static getAuthArray(securities, spec) {
|
|
351
214
|
var _a;
|
|
352
215
|
const result = [];
|
|
353
216
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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];
|
|
357
221
|
const authArray = [];
|
|
358
222
|
for (const name in security) {
|
|
359
223
|
const auth = securitySchemas[name];
|
|
360
224
|
authArray.push({
|
|
361
|
-
|
|
225
|
+
authScheme: auth,
|
|
362
226
|
name: name,
|
|
363
227
|
});
|
|
364
228
|
}
|
|
@@ -370,24 +234,47 @@ class Utils {
|
|
|
370
234
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
371
235
|
return result;
|
|
372
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
|
+
}
|
|
373
256
|
static updateFirstLetter(str) {
|
|
374
257
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
375
258
|
}
|
|
376
259
|
static getResponseJson(operationObject) {
|
|
377
260
|
var _a, _b;
|
|
378
261
|
let json = {};
|
|
262
|
+
let multipleMediaType = false;
|
|
379
263
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
380
264
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
381
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
382
|
-
if (mediaTypesCount > 1) {
|
|
383
|
-
return {};
|
|
384
|
-
}
|
|
385
265
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
266
|
+
multipleMediaType = false;
|
|
386
267
|
json = responseObject.content["application/json"];
|
|
387
|
-
|
|
268
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
269
|
+
multipleMediaType = true;
|
|
270
|
+
json = {};
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
388
275
|
}
|
|
389
276
|
}
|
|
390
|
-
return json;
|
|
277
|
+
return { json, multipleMediaType };
|
|
391
278
|
}
|
|
392
279
|
static convertPathToCamelCase(path) {
|
|
393
280
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -407,10 +294,10 @@ class Utils {
|
|
|
407
294
|
return undefined;
|
|
408
295
|
}
|
|
409
296
|
}
|
|
410
|
-
static
|
|
297
|
+
static resolveEnv(str) {
|
|
411
298
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
412
|
-
let matches = placeHolderReg.exec(
|
|
413
|
-
let
|
|
299
|
+
let matches = placeHolderReg.exec(str);
|
|
300
|
+
let newStr = str;
|
|
414
301
|
while (matches != null) {
|
|
415
302
|
const envVar = matches[1];
|
|
416
303
|
const envVal = process.env[envVar];
|
|
@@ -418,17 +305,17 @@ class Utils {
|
|
|
418
305
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
419
306
|
}
|
|
420
307
|
else {
|
|
421
|
-
|
|
308
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
422
309
|
}
|
|
423
|
-
matches = placeHolderReg.exec(
|
|
310
|
+
matches = placeHolderReg.exec(str);
|
|
424
311
|
}
|
|
425
|
-
return
|
|
312
|
+
return newStr;
|
|
426
313
|
}
|
|
427
314
|
static checkServerUrl(servers) {
|
|
428
315
|
const errors = [];
|
|
429
316
|
let serverUrl;
|
|
430
317
|
try {
|
|
431
|
-
serverUrl = Utils.
|
|
318
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
432
319
|
}
|
|
433
320
|
catch (err) {
|
|
434
321
|
errors.push({
|
|
@@ -458,7 +345,8 @@ class Utils {
|
|
|
458
345
|
}
|
|
459
346
|
return errors;
|
|
460
347
|
}
|
|
461
|
-
static validateServer(spec,
|
|
348
|
+
static validateServer(spec, options) {
|
|
349
|
+
var _a;
|
|
462
350
|
const errors = [];
|
|
463
351
|
let hasTopLevelServers = false;
|
|
464
352
|
let hasPathLevelServers = false;
|
|
@@ -479,7 +367,7 @@ class Utils {
|
|
|
479
367
|
}
|
|
480
368
|
for (const method in methods) {
|
|
481
369
|
const operationObject = methods[method];
|
|
482
|
-
if (
|
|
370
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
483
371
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
484
372
|
hasOperationLevelServers = true;
|
|
485
373
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -522,6 +410,7 @@ class Utils {
|
|
|
522
410
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
523
411
|
}
|
|
524
412
|
if (isRequired && schema.default === undefined) {
|
|
413
|
+
parameter.isRequired = true;
|
|
525
414
|
requiredParams.push(parameter);
|
|
526
415
|
}
|
|
527
416
|
else {
|
|
@@ -566,7 +455,7 @@ class Utils {
|
|
|
566
455
|
param.value = schema.default;
|
|
567
456
|
}
|
|
568
457
|
}
|
|
569
|
-
static parseApiInfo(operationItem,
|
|
458
|
+
static parseApiInfo(operationItem, options) {
|
|
570
459
|
var _a, _b;
|
|
571
460
|
const requiredParams = [];
|
|
572
461
|
const optionalParams = [];
|
|
@@ -580,11 +469,12 @@ class Utils {
|
|
|
580
469
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
581
470
|
};
|
|
582
471
|
const schema = param.schema;
|
|
583
|
-
if (allowMultipleParameters && schema) {
|
|
472
|
+
if (options.allowMultipleParameters && schema) {
|
|
584
473
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
585
474
|
}
|
|
586
475
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
587
476
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
477
|
+
parameter.isRequired = true;
|
|
588
478
|
requiredParams.push(parameter);
|
|
589
479
|
}
|
|
590
480
|
else {
|
|
@@ -598,19 +488,13 @@ class Utils {
|
|
|
598
488
|
const requestJson = requestBody.content["application/json"];
|
|
599
489
|
if (Object.keys(requestJson).length !== 0) {
|
|
600
490
|
const schema = requestJson.schema;
|
|
601
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
491
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
602
492
|
requiredParams.push(...requiredP);
|
|
603
493
|
optionalParams.push(...optionalP);
|
|
604
494
|
}
|
|
605
495
|
}
|
|
606
496
|
const operationId = operationItem.operationId;
|
|
607
|
-
const parameters = [];
|
|
608
|
-
if (requiredParams.length !== 0) {
|
|
609
|
-
parameters.push(...requiredParams);
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
parameters.push(optionalParams[0]);
|
|
613
|
-
}
|
|
497
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
614
498
|
const command = {
|
|
615
499
|
context: ["compose"],
|
|
616
500
|
type: "query",
|
|
@@ -619,224 +503,575 @@ class Utils {
|
|
|
619
503
|
parameters: parameters,
|
|
620
504
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
621
505
|
};
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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 "";
|
|
629
518
|
}
|
|
630
|
-
|
|
519
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
520
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
521
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
522
|
+
}
|
|
523
|
+
return safeRegistrationIdEnvName;
|
|
631
524
|
}
|
|
632
|
-
static
|
|
633
|
-
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;
|
|
634
544
|
const result = {};
|
|
635
545
|
for (const path in paths) {
|
|
636
546
|
const methods = paths[path];
|
|
637
547
|
for (const method in methods) {
|
|
638
|
-
|
|
639
|
-
if (
|
|
640
|
-
const
|
|
641
|
-
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
|
+
};
|
|
642
556
|
}
|
|
643
557
|
}
|
|
644
558
|
}
|
|
559
|
+
this.apiMap = result;
|
|
645
560
|
return result;
|
|
646
561
|
}
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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,
|
|
654
569
|
});
|
|
655
570
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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({
|
|
673
591
|
type: ErrorType.NoSupportedApi,
|
|
674
592
|
content: ConstantString.NoSupportedApi,
|
|
593
|
+
data,
|
|
675
594
|
});
|
|
676
595
|
}
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
validateSpecOperationId() {
|
|
599
|
+
const result = { errors: [], warnings: [] };
|
|
600
|
+
const apiMap = this.listAPIs();
|
|
677
601
|
// OperationId missing
|
|
678
602
|
const apisMissingOperationId = [];
|
|
679
603
|
for (const key in apiMap) {
|
|
680
|
-
const
|
|
681
|
-
if (!
|
|
604
|
+
const { operation } = apiMap[key];
|
|
605
|
+
if (!operation.operationId) {
|
|
682
606
|
apisMissingOperationId.push(key);
|
|
683
607
|
}
|
|
684
608
|
}
|
|
685
609
|
if (apisMissingOperationId.length > 0) {
|
|
686
|
-
warnings.push({
|
|
610
|
+
result.warnings.push({
|
|
687
611
|
type: WarningType.OperationIdMissing,
|
|
688
612
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
689
613
|
data: apisMissingOperationId,
|
|
690
614
|
});
|
|
691
615
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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;
|
|
695
624
|
}
|
|
696
|
-
|
|
697
|
-
|
|
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;
|
|
698
630
|
}
|
|
699
|
-
return
|
|
700
|
-
status,
|
|
701
|
-
warnings,
|
|
702
|
-
errors,
|
|
703
|
-
};
|
|
631
|
+
return result;
|
|
704
632
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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;
|
|
711
648
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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);
|
|
715
655
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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));
|
|
719
660
|
}
|
|
720
|
-
return
|
|
661
|
+
return result;
|
|
721
662
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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: [] };
|
|
740
687
|
}
|
|
741
688
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
692
|
+
}
|
|
693
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
694
|
+
var _a;
|
|
695
|
+
const paramResult = {
|
|
696
|
+
requiredNum: 0,
|
|
697
|
+
optionalNum: 0,
|
|
698
|
+
isValid: true,
|
|
699
|
+
reason: [],
|
|
700
|
+
};
|
|
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
|
+
}
|
|
711
|
+
if (schema.type === "string" ||
|
|
712
|
+
schema.type === "integer" ||
|
|
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;
|
|
746
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);
|
|
747
734
|
}
|
|
748
|
-
newSpec.paths = newPaths;
|
|
749
|
-
return newSpec;
|
|
750
735
|
}
|
|
751
|
-
|
|
752
|
-
|
|
736
|
+
else {
|
|
737
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
738
|
+
paramResult.isValid = false;
|
|
739
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return paramResult;
|
|
743
|
+
}
|
|
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;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return paramResult;
|
|
800
|
+
}
|
|
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;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
753
809
|
}
|
|
810
|
+
return false;
|
|
754
811
|
}
|
|
755
812
|
}
|
|
756
813
|
|
|
757
814
|
// Copyright (c) Microsoft Corporation.
|
|
758
|
-
class
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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);
|
|
793
867
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : originalManifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
|
|
797
|
-
};
|
|
798
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
799
|
-
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
800
|
-
return [updatedManifest, warnings];
|
|
868
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
869
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
801
870
|
}
|
|
802
|
-
|
|
803
|
-
|
|
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;
|
|
1015
|
+
}
|
|
1016
|
+
return result;
|
|
1017
|
+
}
|
|
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}`);
|
|
804
1033
|
}
|
|
805
1034
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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];
|
|
830
1060
|
}
|
|
831
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
|
+
}
|
|
832
1067
|
}
|
|
833
1068
|
}
|
|
1069
|
+
newSpec.paths = newPaths;
|
|
1070
|
+
return newSpec;
|
|
1071
|
+
}
|
|
1072
|
+
catch (err) {
|
|
1073
|
+
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
834
1074
|
}
|
|
835
|
-
return [commands, warnings];
|
|
836
|
-
}
|
|
837
|
-
static getRelativePath(from, to) {
|
|
838
|
-
const relativePath = path.relative(path.dirname(from), to);
|
|
839
|
-
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
840
1075
|
}
|
|
841
1076
|
}
|
|
842
1077
|
|
|
@@ -844,7 +1079,7 @@ class ManifestUpdater {
|
|
|
844
1079
|
class AdaptiveCardGenerator {
|
|
845
1080
|
static generateAdaptiveCard(operationItem) {
|
|
846
1081
|
try {
|
|
847
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1082
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
848
1083
|
let cardBody = [];
|
|
849
1084
|
let schema = json.schema;
|
|
850
1085
|
let jsonPath = "$";
|
|
@@ -1010,6 +1245,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1010
1245
|
};
|
|
1011
1246
|
return result;
|
|
1012
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
|
+
}
|
|
1013
1269
|
/**
|
|
1014
1270
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1015
1271
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1022,11 +1278,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1022
1278
|
* @returns The inferred preview card template.
|
|
1023
1279
|
*/
|
|
1024
1280
|
function inferPreviewCardTemplate(card) {
|
|
1025
|
-
var _a;
|
|
1026
1281
|
const result = {
|
|
1027
|
-
title: "",
|
|
1282
|
+
title: "result",
|
|
1028
1283
|
};
|
|
1029
|
-
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();
|
|
1030
1304
|
let rootObject;
|
|
1031
1305
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1032
1306
|
rootObject = card.body[0].items;
|
|
@@ -1039,56 +1313,372 @@ function inferPreviewCardTemplate(card) {
|
|
|
1039
1313
|
const textElement = element;
|
|
1040
1314
|
const index = textElement.text.indexOf("${if(");
|
|
1041
1315
|
if (index > 0) {
|
|
1042
|
-
|
|
1043
|
-
|
|
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);
|
|
1044
1330
|
}
|
|
1045
1331
|
}
|
|
1046
1332
|
}
|
|
1047
|
-
for (const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
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);
|
|
1052
1337
|
}
|
|
1053
1338
|
else if (!result.subtitle &&
|
|
1054
|
-
Utils.isWellKnownName(
|
|
1055
|
-
result.subtitle =
|
|
1056
|
-
|
|
1339
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1340
|
+
result.subtitle = name;
|
|
1341
|
+
nameSet.delete(name);
|
|
1057
1342
|
}
|
|
1058
|
-
else if (!result.
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if (property) {
|
|
1062
|
-
result.image = {
|
|
1063
|
-
url: `\${${property}}`,
|
|
1064
|
-
alt: text,
|
|
1065
|
-
$when: `\${${property} != null}`,
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
textBlockElements.delete(element);
|
|
1343
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1344
|
+
result.imageUrl = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1069
1346
|
}
|
|
1070
1347
|
}
|
|
1071
|
-
for (const
|
|
1072
|
-
const text = element.text;
|
|
1348
|
+
for (const name of nameSet) {
|
|
1073
1349
|
if (!result.title) {
|
|
1074
|
-
result.title =
|
|
1075
|
-
|
|
1350
|
+
result.title = name;
|
|
1351
|
+
nameSet.delete(name);
|
|
1076
1352
|
}
|
|
1077
1353
|
else if (!result.subtitle) {
|
|
1078
|
-
result.subtitle =
|
|
1079
|
-
|
|
1354
|
+
result.subtitle = name;
|
|
1355
|
+
nameSet.delete(name);
|
|
1080
1356
|
}
|
|
1081
1357
|
}
|
|
1082
1358
|
if (!result.title && result.subtitle) {
|
|
1083
1359
|
result.title = result.subtitle;
|
|
1084
1360
|
delete result.subtitle;
|
|
1085
1361
|
}
|
|
1086
|
-
if (!result.title) {
|
|
1087
|
-
result.title = "result";
|
|
1088
|
-
}
|
|
1089
1362
|
return result;
|
|
1090
1363
|
}
|
|
1091
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
|
+
|
|
1092
1682
|
// Copyright (c) Microsoft Corporation.
|
|
1093
1683
|
/**
|
|
1094
1684
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1104,8 +1694,15 @@ class SpecParser {
|
|
|
1104
1694
|
allowMissingId: true,
|
|
1105
1695
|
allowSwagger: true,
|
|
1106
1696
|
allowAPIKeyAuth: false,
|
|
1697
|
+
allowBearerTokenAuth: false,
|
|
1107
1698
|
allowMultipleParameters: false,
|
|
1108
1699
|
allowOauth2: false,
|
|
1700
|
+
allowMethods: ["get", "post"],
|
|
1701
|
+
allowConversationStarters: false,
|
|
1702
|
+
allowResponseSemantics: false,
|
|
1703
|
+
allowConfirmation: false,
|
|
1704
|
+
projectType: ProjectType.SME,
|
|
1705
|
+
isGptPlugin: false,
|
|
1109
1706
|
};
|
|
1110
1707
|
this.pathOrSpec = pathOrDoc;
|
|
1111
1708
|
this.parser = new SwaggerParser();
|
|
@@ -1129,6 +1726,8 @@ class SpecParser {
|
|
|
1129
1726
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1130
1727
|
};
|
|
1131
1728
|
}
|
|
1729
|
+
const errors = [];
|
|
1730
|
+
const warnings = [];
|
|
1132
1731
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1133
1732
|
return {
|
|
1134
1733
|
status: ValidationStatus.Error,
|
|
@@ -1138,7 +1737,38 @@ class SpecParser {
|
|
|
1138
1737
|
],
|
|
1139
1738
|
};
|
|
1140
1739
|
}
|
|
1141
|
-
|
|
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
|
+
};
|
|
1142
1772
|
}
|
|
1143
1773
|
catch (err) {
|
|
1144
1774
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1158,39 +1788,40 @@ class SpecParser {
|
|
|
1158
1788
|
try {
|
|
1159
1789
|
await this.loadSpec();
|
|
1160
1790
|
const spec = this.spec;
|
|
1161
|
-
const apiMap = this.
|
|
1162
|
-
const result =
|
|
1791
|
+
const apiMap = this.getAPIs(spec);
|
|
1792
|
+
const result = {
|
|
1793
|
+
APIs: [],
|
|
1794
|
+
allAPICount: 0,
|
|
1795
|
+
validAPICount: 0,
|
|
1796
|
+
};
|
|
1163
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)}`;
|
|
1164
1801
|
const apiResult = {
|
|
1165
|
-
api:
|
|
1802
|
+
api: apiKey,
|
|
1166
1803
|
server: "",
|
|
1167
|
-
operationId:
|
|
1804
|
+
operationId: operationId,
|
|
1805
|
+
isValid: isValid,
|
|
1806
|
+
reason: reason,
|
|
1168
1807
|
};
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
if (!operationId) {
|
|
1181
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1182
|
-
}
|
|
1183
|
-
apiResult.operationId = operationId;
|
|
1184
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1185
|
-
for (const auths of authArray) {
|
|
1186
|
-
if (auths.length === 1) {
|
|
1187
|
-
apiResult.auth = auths[0].authSchema;
|
|
1188
|
-
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
|
+
}
|
|
1189
1819
|
}
|
|
1190
1820
|
}
|
|
1191
|
-
apiResult
|
|
1192
|
-
result.push(apiResult);
|
|
1821
|
+
result.APIs.push(apiResult);
|
|
1193
1822
|
}
|
|
1823
|
+
result.allAPICount = result.APIs.length;
|
|
1824
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1194
1825
|
return result;
|
|
1195
1826
|
}
|
|
1196
1827
|
catch (err) {
|
|
@@ -1201,17 +1832,10 @@ class SpecParser {
|
|
|
1201
1832
|
}
|
|
1202
1833
|
}
|
|
1203
1834
|
/**
|
|
1204
|
-
*
|
|
1205
|
-
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1835
|
+
* Generate specs according to the filters.
|
|
1206
1836
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1207
|
-
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1208
|
-
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1209
1837
|
*/
|
|
1210
|
-
async
|
|
1211
|
-
const result = {
|
|
1212
|
-
allSuccess: true,
|
|
1213
|
-
warnings: [],
|
|
1214
|
-
};
|
|
1838
|
+
async getFilteredSpecs(filter, signal) {
|
|
1215
1839
|
try {
|
|
1216
1840
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1217
1841
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
@@ -1220,57 +1844,96 @@ class SpecParser {
|
|
|
1220
1844
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1221
1845
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1222
1846
|
}
|
|
1223
|
-
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options
|
|
1847
|
+
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
|
|
1224
1848
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1225
1849
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1226
1850
|
}
|
|
1227
1851
|
const newSpec = (await this.parser.dereference(newUnResolvedSpec));
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1852
|
+
return [newUnResolvedSpec, newSpec];
|
|
1853
|
+
}
|
|
1854
|
+
catch (err) {
|
|
1855
|
+
if (err instanceof SpecParserError) {
|
|
1856
|
+
throw err;
|
|
1857
|
+
}
|
|
1858
|
+
throw new SpecParserError(err.toString(), ErrorType.GetSpecFailed);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1863
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1864
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1865
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1866
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1867
|
+
*/
|
|
1868
|
+
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1869
|
+
const result = {
|
|
1870
|
+
allSuccess: true,
|
|
1871
|
+
warnings: [],
|
|
1872
|
+
};
|
|
1873
|
+
try {
|
|
1874
|
+
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1875
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1876
|
+
const newSpec = newSpecs[1];
|
|
1877
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1878
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1879
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1880
|
+
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1242
1881
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1882
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1883
|
+
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1884
|
+
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1885
|
+
}
|
|
1886
|
+
catch (err) {
|
|
1887
|
+
if (err instanceof SpecParserError) {
|
|
1888
|
+
throw err;
|
|
1245
1889
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1890
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateFailed);
|
|
1891
|
+
}
|
|
1892
|
+
return result;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1896
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
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.
|
|
1898
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
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.
|
|
1900
|
+
*/
|
|
1901
|
+
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
1902
|
+
const result = {
|
|
1903
|
+
allSuccess: true,
|
|
1904
|
+
warnings: [],
|
|
1905
|
+
};
|
|
1906
|
+
try {
|
|
1907
|
+
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1908
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1909
|
+
const newSpec = newSpecs[1];
|
|
1910
|
+
let authInfo = undefined;
|
|
1911
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1912
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1249
1913
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
});
|
|
1914
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1915
|
+
if (adaptiveCardFolder) {
|
|
1916
|
+
for (const url in newSpec.paths) {
|
|
1917
|
+
for (const method in newSpec.paths[url]) {
|
|
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)) {
|
|
1920
|
+
const operation = newSpec.paths[url][method];
|
|
1921
|
+
try {
|
|
1922
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
|
|
1923
|
+
const fileName = path.join(adaptiveCardFolder, `${operation.operationId}.json`);
|
|
1924
|
+
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
|
|
1925
|
+
await fs.outputJSON(fileName, wrappedCard, { spaces: 2 });
|
|
1926
|
+
const dataFileName = path.join(adaptiveCardFolder, `${operation.operationId}.data.json`);
|
|
1927
|
+
await fs.outputJSON(dataFileName, {}, { spaces: 2 });
|
|
1928
|
+
}
|
|
1929
|
+
catch (err) {
|
|
1930
|
+
result.allSuccess = false;
|
|
1931
|
+
result.warnings.push({
|
|
1932
|
+
type: WarningType.GenerateCardFailed,
|
|
1933
|
+
content: err.toString(),
|
|
1934
|
+
data: operation.operationId,
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1274
1937
|
}
|
|
1275
1938
|
}
|
|
1276
1939
|
}
|
|
@@ -1278,8 +1941,7 @@ class SpecParser {
|
|
|
1278
1941
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1279
1942
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1280
1943
|
}
|
|
1281
|
-
const
|
|
1282
|
-
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, adaptiveCardFolder, newSpec, this.options.allowMultipleParameters, auth);
|
|
1944
|
+
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1283
1945
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1284
1946
|
result.warnings.push(...warnings);
|
|
1285
1947
|
}
|
|
@@ -1304,15 +1966,30 @@ class SpecParser {
|
|
|
1304
1966
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1305
1967
|
}
|
|
1306
1968
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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;
|
|
1310
1977
|
}
|
|
1311
|
-
const
|
|
1312
|
-
this.
|
|
1313
|
-
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);
|
|
1314
1991
|
}
|
|
1315
1992
|
}
|
|
1316
1993
|
|
|
1317
|
-
export { ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1994
|
+
export { AdaptiveCardGenerator, ConstantString, ErrorType, ProjectType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
1318
1995
|
//# sourceMappingURL=index.esm2017.mjs.map
|