@microsoft/m365-spec-parser 0.1.0 → 0.1.1-alpha.0de595af8.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 +691 -259
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +845 -354
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +695 -259
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +859 -361
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +5 -1
- package/dist/src/index.browser.d.ts +2 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/interfaces.d.ts +76 -18
- package/dist/src/manifestUpdater.d.ts +9 -4
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +17 -2
- package/dist/src/specParser.d.ts +17 -3
- package/dist/src/utils.d.ts +19 -33
- package/package.json +62 -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 {
|
|
@@ -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";
|
|
@@ -122,6 +147,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
|
122
147
|
ConstantString.TextBlockType = "TextBlock";
|
|
123
148
|
ConstantString.ContainerType = "Container";
|
|
124
149
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
150
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
125
151
|
ConstantString.ResponseCodeFor20X = [
|
|
126
152
|
"200",
|
|
127
153
|
"201",
|
|
@@ -181,7 +207,8 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
181
207
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
182
208
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
183
209
|
ConstantString.CommandTitleMaxLens = 32;
|
|
184
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
210
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
211
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
185
212
|
|
|
186
213
|
// Copyright (c) Microsoft Corporation.
|
|
187
214
|
class SpecParserError extends Error {
|
|
@@ -193,201 +220,30 @@ class SpecParserError extends Error {
|
|
|
193
220
|
|
|
194
221
|
// Copyright (c) Microsoft Corporation.
|
|
195
222
|
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 {
|
|
223
|
+
static hasNestedObjectInSchema(schema) {
|
|
224
|
+
if (schema.type === "object") {
|
|
225
|
+
for (const property in schema.properties) {
|
|
226
|
+
const nestedSchema = schema.properties[property];
|
|
227
|
+
if (nestedSchema.type === "object") {
|
|
348
228
|
return true;
|
|
349
229
|
}
|
|
350
230
|
}
|
|
351
231
|
}
|
|
352
232
|
return false;
|
|
353
233
|
}
|
|
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;
|
|
234
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
235
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
236
|
+
}
|
|
237
|
+
static isBearerTokenAuth(authScheme) {
|
|
238
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
383
239
|
}
|
|
384
|
-
static isAPIKeyAuth(
|
|
385
|
-
return
|
|
240
|
+
static isAPIKeyAuth(authScheme) {
|
|
241
|
+
return authScheme.type === "apiKey";
|
|
386
242
|
}
|
|
387
|
-
static
|
|
388
|
-
return (
|
|
389
|
-
|
|
390
|
-
|
|
243
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
244
|
+
return !!(authScheme.type === "oauth2" &&
|
|
245
|
+
authScheme.flows &&
|
|
246
|
+
authScheme.flows.authorizationCode);
|
|
391
247
|
}
|
|
392
248
|
static getAuthArray(securities, spec) {
|
|
393
249
|
var _a;
|
|
@@ -400,7 +256,7 @@ class Utils {
|
|
|
400
256
|
for (const name in security) {
|
|
401
257
|
const auth = securitySchemas[name];
|
|
402
258
|
authArray.push({
|
|
403
|
-
|
|
259
|
+
authScheme: auth,
|
|
404
260
|
name: name,
|
|
405
261
|
});
|
|
406
262
|
}
|
|
@@ -418,18 +274,22 @@ class Utils {
|
|
|
418
274
|
static getResponseJson(operationObject) {
|
|
419
275
|
var _a, _b;
|
|
420
276
|
let json = {};
|
|
277
|
+
let multipleMediaType = false;
|
|
421
278
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
422
279
|
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
280
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
281
|
+
multipleMediaType = false;
|
|
428
282
|
json = responseObject.content["application/json"];
|
|
429
|
-
|
|
283
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
284
|
+
multipleMediaType = true;
|
|
285
|
+
json = {};
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
430
290
|
}
|
|
431
291
|
}
|
|
432
|
-
return json;
|
|
292
|
+
return { json, multipleMediaType };
|
|
433
293
|
}
|
|
434
294
|
static convertPathToCamelCase(path) {
|
|
435
295
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -449,10 +309,10 @@ class Utils {
|
|
|
449
309
|
return undefined;
|
|
450
310
|
}
|
|
451
311
|
}
|
|
452
|
-
static
|
|
312
|
+
static resolveEnv(str) {
|
|
453
313
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
454
|
-
let matches = placeHolderReg.exec(
|
|
455
|
-
let
|
|
314
|
+
let matches = placeHolderReg.exec(str);
|
|
315
|
+
let newStr = str;
|
|
456
316
|
while (matches != null) {
|
|
457
317
|
const envVar = matches[1];
|
|
458
318
|
const envVal = process.env[envVar];
|
|
@@ -460,17 +320,17 @@ class Utils {
|
|
|
460
320
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
461
321
|
}
|
|
462
322
|
else {
|
|
463
|
-
|
|
323
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
464
324
|
}
|
|
465
|
-
matches = placeHolderReg.exec(
|
|
325
|
+
matches = placeHolderReg.exec(str);
|
|
466
326
|
}
|
|
467
|
-
return
|
|
327
|
+
return newStr;
|
|
468
328
|
}
|
|
469
329
|
static checkServerUrl(servers) {
|
|
470
330
|
const errors = [];
|
|
471
331
|
let serverUrl;
|
|
472
332
|
try {
|
|
473
|
-
serverUrl = Utils.
|
|
333
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
474
334
|
}
|
|
475
335
|
catch (err) {
|
|
476
336
|
errors.push({
|
|
@@ -500,7 +360,8 @@ class Utils {
|
|
|
500
360
|
}
|
|
501
361
|
return errors;
|
|
502
362
|
}
|
|
503
|
-
static validateServer(spec,
|
|
363
|
+
static validateServer(spec, options) {
|
|
364
|
+
var _a;
|
|
504
365
|
const errors = [];
|
|
505
366
|
let hasTopLevelServers = false;
|
|
506
367
|
let hasPathLevelServers = false;
|
|
@@ -521,7 +382,7 @@ class Utils {
|
|
|
521
382
|
}
|
|
522
383
|
for (const method in methods) {
|
|
523
384
|
const operationObject = methods[method];
|
|
524
|
-
if (
|
|
385
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
525
386
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
526
387
|
hasOperationLevelServers = true;
|
|
527
388
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -564,6 +425,7 @@ class Utils {
|
|
|
564
425
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
565
426
|
}
|
|
566
427
|
if (isRequired && schema.default === undefined) {
|
|
428
|
+
parameter.isRequired = true;
|
|
567
429
|
requiredParams.push(parameter);
|
|
568
430
|
}
|
|
569
431
|
else {
|
|
@@ -608,7 +470,7 @@ class Utils {
|
|
|
608
470
|
param.value = schema.default;
|
|
609
471
|
}
|
|
610
472
|
}
|
|
611
|
-
static parseApiInfo(operationItem,
|
|
473
|
+
static parseApiInfo(operationItem, options) {
|
|
612
474
|
var _a, _b;
|
|
613
475
|
const requiredParams = [];
|
|
614
476
|
const optionalParams = [];
|
|
@@ -622,11 +484,12 @@ class Utils {
|
|
|
622
484
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
623
485
|
};
|
|
624
486
|
const schema = param.schema;
|
|
625
|
-
if (allowMultipleParameters && schema) {
|
|
487
|
+
if (options.allowMultipleParameters && schema) {
|
|
626
488
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
627
489
|
}
|
|
628
490
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
629
491
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
492
|
+
parameter.isRequired = true;
|
|
630
493
|
requiredParams.push(parameter);
|
|
631
494
|
}
|
|
632
495
|
else {
|
|
@@ -640,19 +503,13 @@ class Utils {
|
|
|
640
503
|
const requestJson = requestBody.content["application/json"];
|
|
641
504
|
if (Object.keys(requestJson).length !== 0) {
|
|
642
505
|
const schema = requestJson.schema;
|
|
643
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
506
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
644
507
|
requiredParams.push(...requiredP);
|
|
645
508
|
optionalParams.push(...optionalP);
|
|
646
509
|
}
|
|
647
510
|
}
|
|
648
511
|
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
|
-
}
|
|
512
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
656
513
|
const command = {
|
|
657
514
|
context: ["compose"],
|
|
658
515
|
type: "query",
|
|
@@ -661,32 +518,9 @@ class Utils {
|
|
|
661
518
|
parameters: parameters,
|
|
662
519
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
663
520
|
};
|
|
664
|
-
|
|
665
|
-
if (requiredParams.length === 0 && optionalParams.length > 1) {
|
|
666
|
-
warning = {
|
|
667
|
-
type: exports.WarningType.OperationOnlyContainsOptionalParam,
|
|
668
|
-
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
|
|
669
|
-
data: operationId,
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
return [command, warning];
|
|
673
|
-
}
|
|
674
|
-
static listSupportedAPIs(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
675
|
-
const paths = spec.paths;
|
|
676
|
-
const result = {};
|
|
677
|
-
for (const path in paths) {
|
|
678
|
-
const methods = paths[path];
|
|
679
|
-
for (const method in methods) {
|
|
680
|
-
// For developer preview, only support GET operation with only 1 parameter without auth
|
|
681
|
-
if (Utils.isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2)) {
|
|
682
|
-
const operationObject = methods[method];
|
|
683
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return result;
|
|
521
|
+
return command;
|
|
688
522
|
}
|
|
689
|
-
static validateSpec(spec, parser,
|
|
523
|
+
static validateSpec(spec, parser, apiMap, isSwaggerFile, options) {
|
|
690
524
|
const errors = [];
|
|
691
525
|
const warnings = [];
|
|
692
526
|
if (isSwaggerFile) {
|
|
@@ -695,8 +529,7 @@ class Utils {
|
|
|
695
529
|
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
696
530
|
});
|
|
697
531
|
}
|
|
698
|
-
|
|
699
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2);
|
|
532
|
+
const serverErrors = Utils.validateServer(spec, options);
|
|
700
533
|
errors.push(...serverErrors);
|
|
701
534
|
// Remote reference not supported
|
|
702
535
|
const refPaths = parser.$refs.paths();
|
|
@@ -709,8 +542,8 @@ class Utils {
|
|
|
709
542
|
});
|
|
710
543
|
}
|
|
711
544
|
// No supported API
|
|
712
|
-
const
|
|
713
|
-
if (
|
|
545
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
546
|
+
if (validAPIs.length === 0) {
|
|
714
547
|
errors.push({
|
|
715
548
|
type: exports.ErrorType.NoSupportedApi,
|
|
716
549
|
content: ConstantString.NoSupportedApi,
|
|
@@ -719,8 +552,8 @@ class Utils {
|
|
|
719
552
|
// OperationId missing
|
|
720
553
|
const apisMissingOperationId = [];
|
|
721
554
|
for (const key in apiMap) {
|
|
722
|
-
const
|
|
723
|
-
if (!
|
|
555
|
+
const { operation } = apiMap[key];
|
|
556
|
+
if (!operation.operationId) {
|
|
724
557
|
apisMissingOperationId.push(key);
|
|
725
558
|
}
|
|
726
559
|
}
|
|
@@ -761,30 +594,434 @@ class Utils {
|
|
|
761
594
|
}
|
|
762
595
|
return safeRegistrationIdEnvName;
|
|
763
596
|
}
|
|
597
|
+
static getAllAPICount(spec) {
|
|
598
|
+
let count = 0;
|
|
599
|
+
const paths = spec.paths;
|
|
600
|
+
for (const path in paths) {
|
|
601
|
+
const methods = paths[path];
|
|
602
|
+
for (const method in methods) {
|
|
603
|
+
if (ConstantString.AllOperationMethods.includes(method)) {
|
|
604
|
+
count++;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return count;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Copyright (c) Microsoft Corporation.
|
|
613
|
+
class Validator {
|
|
614
|
+
validateMethodAndPath(method, path) {
|
|
615
|
+
const result = { isValid: true, reason: [] };
|
|
616
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
617
|
+
result.isValid = false;
|
|
618
|
+
result.reason.push(exports.ErrorType.MethodNotAllowed);
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
const pathObj = this.spec.paths[path];
|
|
622
|
+
if (!pathObj || !pathObj[method]) {
|
|
623
|
+
result.isValid = false;
|
|
624
|
+
result.reason.push(exports.ErrorType.UrlPathNotExist);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
validateResponse(method, path) {
|
|
630
|
+
const result = { isValid: true, reason: [] };
|
|
631
|
+
const operationObject = this.spec.paths[path][method];
|
|
632
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
633
|
+
// only support response body only contains “application/json” content type
|
|
634
|
+
if (multipleMediaType) {
|
|
635
|
+
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
|
|
636
|
+
}
|
|
637
|
+
else if (Object.keys(json).length === 0) {
|
|
638
|
+
// response body should not be empty
|
|
639
|
+
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
validateServer(method, path) {
|
|
644
|
+
const pathObj = this.spec.paths[path];
|
|
645
|
+
const result = { isValid: true, reason: [] };
|
|
646
|
+
const operationObject = pathObj[method];
|
|
647
|
+
const rootServer = this.spec.servers && this.spec.servers[0];
|
|
648
|
+
const methodServer = this.spec.paths[path].servers && this.spec.paths[path].servers[0];
|
|
649
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
650
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
651
|
+
if (!serverUrl) {
|
|
652
|
+
// should contain server URL
|
|
653
|
+
result.reason.push(exports.ErrorType.NoServerInformation);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
// server url should be absolute url with https protocol
|
|
657
|
+
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
658
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
659
|
+
}
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
662
|
+
validateAuth(method, path) {
|
|
663
|
+
const pathObj = this.spec.paths[path];
|
|
664
|
+
const operationObject = pathObj[method];
|
|
665
|
+
const securities = operationObject.security;
|
|
666
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
667
|
+
if (authSchemeArray.length === 0) {
|
|
668
|
+
return { isValid: true, reason: [] };
|
|
669
|
+
}
|
|
670
|
+
if (this.options.allowAPIKeyAuth ||
|
|
671
|
+
this.options.allowOauth2 ||
|
|
672
|
+
this.options.allowBearerTokenAuth) {
|
|
673
|
+
// Currently we don't support multiple auth in one operation
|
|
674
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
675
|
+
return {
|
|
676
|
+
isValid: false,
|
|
677
|
+
reason: [exports.ErrorType.MultipleAuthNotSupported],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
for (const auths of authSchemeArray) {
|
|
681
|
+
if (auths.length === 1) {
|
|
682
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
683
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
684
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
685
|
+
return { isValid: true, reason: [] };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
|
|
691
|
+
}
|
|
692
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
693
|
+
var _a;
|
|
694
|
+
const paramResult = {
|
|
695
|
+
requiredNum: 0,
|
|
696
|
+
optionalNum: 0,
|
|
697
|
+
isValid: true,
|
|
698
|
+
reason: [],
|
|
699
|
+
};
|
|
700
|
+
if (Object.keys(schema).length === 0) {
|
|
701
|
+
return paramResult;
|
|
702
|
+
}
|
|
703
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
704
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
705
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
706
|
+
paramResult.isValid = false;
|
|
707
|
+
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
|
|
708
|
+
return paramResult;
|
|
709
|
+
}
|
|
710
|
+
if (schema.type === "string" ||
|
|
711
|
+
schema.type === "integer" ||
|
|
712
|
+
schema.type === "boolean" ||
|
|
713
|
+
schema.type === "number") {
|
|
714
|
+
if (isRequiredWithoutDefault) {
|
|
715
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else if (schema.type === "object") {
|
|
722
|
+
const { properties } = schema;
|
|
723
|
+
for (const property in properties) {
|
|
724
|
+
let isRequired = false;
|
|
725
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
726
|
+
isRequired = true;
|
|
727
|
+
}
|
|
728
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
729
|
+
paramResult.requiredNum += result.requiredNum;
|
|
730
|
+
paramResult.optionalNum += result.optionalNum;
|
|
731
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
732
|
+
paramResult.reason.push(...result.reason);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
737
|
+
paramResult.isValid = false;
|
|
738
|
+
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return paramResult;
|
|
742
|
+
}
|
|
743
|
+
checkParamSchema(paramObject) {
|
|
744
|
+
const paramResult = {
|
|
745
|
+
requiredNum: 0,
|
|
746
|
+
optionalNum: 0,
|
|
747
|
+
isValid: true,
|
|
748
|
+
reason: [],
|
|
749
|
+
};
|
|
750
|
+
if (!paramObject) {
|
|
751
|
+
return paramResult;
|
|
752
|
+
}
|
|
753
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
754
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
755
|
+
const param = paramObject[i];
|
|
756
|
+
const schema = param.schema;
|
|
757
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
763
|
+
if (isCopilot) {
|
|
764
|
+
if (isRequiredWithoutDefault) {
|
|
765
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
769
|
+
}
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
773
|
+
if (isRequiredWithoutDefault) {
|
|
774
|
+
paramResult.isValid = false;
|
|
775
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
776
|
+
}
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (schema.type !== "boolean" &&
|
|
780
|
+
schema.type !== "string" &&
|
|
781
|
+
schema.type !== "number" &&
|
|
782
|
+
schema.type !== "integer") {
|
|
783
|
+
if (isRequiredWithoutDefault) {
|
|
784
|
+
paramResult.isValid = false;
|
|
785
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (param.in === "query" || param.in === "path") {
|
|
790
|
+
if (isRequiredWithoutDefault) {
|
|
791
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return paramResult;
|
|
799
|
+
}
|
|
800
|
+
hasNestedObjectInSchema(schema) {
|
|
801
|
+
if (schema.type === "object") {
|
|
802
|
+
for (const property in schema.properties) {
|
|
803
|
+
const nestedSchema = schema.properties[property];
|
|
804
|
+
if (nestedSchema.type === "object") {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Copyright (c) Microsoft Corporation.
|
|
814
|
+
class CopilotValidator extends Validator {
|
|
815
|
+
constructor(spec, options) {
|
|
816
|
+
super();
|
|
817
|
+
this.projectType = exports.ProjectType.Copilot;
|
|
818
|
+
this.options = options;
|
|
819
|
+
this.spec = spec;
|
|
820
|
+
}
|
|
821
|
+
validateAPI(method, path) {
|
|
822
|
+
const result = { isValid: true, reason: [] };
|
|
823
|
+
method = method.toLocaleLowerCase();
|
|
824
|
+
// validate method and path
|
|
825
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
826
|
+
if (!methodAndPathResult.isValid) {
|
|
827
|
+
return methodAndPathResult;
|
|
828
|
+
}
|
|
829
|
+
const operationObject = this.spec.paths[path][method];
|
|
830
|
+
// validate auth
|
|
831
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
832
|
+
result.reason.push(...authCheckResult.reason);
|
|
833
|
+
// validate operationId
|
|
834
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
835
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
836
|
+
}
|
|
837
|
+
// validate server
|
|
838
|
+
const validateServerResult = this.validateServer(method, path);
|
|
839
|
+
result.reason.push(...validateServerResult.reason);
|
|
840
|
+
// validate response
|
|
841
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
842
|
+
result.reason.push(...validateResponseResult.reason);
|
|
843
|
+
// validate requestBody
|
|
844
|
+
const requestBody = operationObject.requestBody;
|
|
845
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
846
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
847
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
848
|
+
}
|
|
849
|
+
if (requestJsonBody) {
|
|
850
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
851
|
+
if (requestBodySchema.type !== "object") {
|
|
852
|
+
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
|
|
853
|
+
}
|
|
854
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
855
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
856
|
+
}
|
|
857
|
+
// validate parameters
|
|
858
|
+
const paramObject = operationObject.parameters;
|
|
859
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
860
|
+
result.reason.push(...paramResult.reason);
|
|
861
|
+
if (result.reason.length > 0) {
|
|
862
|
+
result.isValid = false;
|
|
863
|
+
}
|
|
864
|
+
return result;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Copyright (c) Microsoft Corporation.
|
|
869
|
+
class SMEValidator extends Validator {
|
|
870
|
+
constructor(spec, options) {
|
|
871
|
+
super();
|
|
872
|
+
this.projectType = exports.ProjectType.SME;
|
|
873
|
+
this.options = options;
|
|
874
|
+
this.spec = spec;
|
|
875
|
+
}
|
|
876
|
+
validateAPI(method, path) {
|
|
877
|
+
const result = { isValid: true, reason: [] };
|
|
878
|
+
method = method.toLocaleLowerCase();
|
|
879
|
+
// validate method and path
|
|
880
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
881
|
+
if (!methodAndPathResult.isValid) {
|
|
882
|
+
return methodAndPathResult;
|
|
883
|
+
}
|
|
884
|
+
const operationObject = this.spec.paths[path][method];
|
|
885
|
+
// validate auth
|
|
886
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
887
|
+
result.reason.push(...authCheckResult.reason);
|
|
888
|
+
// validate operationId
|
|
889
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
890
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
891
|
+
}
|
|
892
|
+
// validate server
|
|
893
|
+
const validateServerResult = this.validateServer(method, path);
|
|
894
|
+
result.reason.push(...validateServerResult.reason);
|
|
895
|
+
// validate response
|
|
896
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
897
|
+
result.reason.push(...validateResponseResult.reason);
|
|
898
|
+
let postBodyResult = {
|
|
899
|
+
requiredNum: 0,
|
|
900
|
+
optionalNum: 0,
|
|
901
|
+
isValid: true,
|
|
902
|
+
reason: [],
|
|
903
|
+
};
|
|
904
|
+
// validate requestBody
|
|
905
|
+
const requestBody = operationObject.requestBody;
|
|
906
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
907
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
908
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
909
|
+
}
|
|
910
|
+
if (requestJsonBody) {
|
|
911
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
912
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
913
|
+
result.reason.push(...postBodyResult.reason);
|
|
914
|
+
}
|
|
915
|
+
// validate parameters
|
|
916
|
+
const paramObject = operationObject.parameters;
|
|
917
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
918
|
+
result.reason.push(...paramResult.reason);
|
|
919
|
+
// validate total parameters count
|
|
920
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
921
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
922
|
+
result.reason.push(...paramCountResult.reason);
|
|
923
|
+
}
|
|
924
|
+
if (result.reason.length > 0) {
|
|
925
|
+
result.isValid = false;
|
|
926
|
+
}
|
|
927
|
+
return result;
|
|
928
|
+
}
|
|
929
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
930
|
+
const result = { isValid: true, reason: [] };
|
|
931
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
932
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
933
|
+
if (totalRequiredParams > 1) {
|
|
934
|
+
if (!this.options.allowMultipleParameters ||
|
|
935
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
936
|
+
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else if (totalParams === 0) {
|
|
940
|
+
result.reason.push(exports.ErrorType.NoParameter);
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
946
|
+
|
|
947
|
+
// Copyright (c) Microsoft Corporation.
|
|
948
|
+
class TeamsAIValidator extends Validator {
|
|
949
|
+
constructor(spec, options) {
|
|
950
|
+
super();
|
|
951
|
+
this.projectType = exports.ProjectType.TeamsAi;
|
|
952
|
+
this.options = options;
|
|
953
|
+
this.spec = spec;
|
|
954
|
+
}
|
|
955
|
+
validateAPI(method, path) {
|
|
956
|
+
const result = { isValid: true, reason: [] };
|
|
957
|
+
method = method.toLocaleLowerCase();
|
|
958
|
+
// validate method and path
|
|
959
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
960
|
+
if (!methodAndPathResult.isValid) {
|
|
961
|
+
return methodAndPathResult;
|
|
962
|
+
}
|
|
963
|
+
const operationObject = this.spec.paths[path][method];
|
|
964
|
+
// validate operationId
|
|
965
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
966
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
967
|
+
}
|
|
968
|
+
// validate server
|
|
969
|
+
const validateServerResult = this.validateServer(method, path);
|
|
970
|
+
result.reason.push(...validateServerResult.reason);
|
|
971
|
+
if (result.reason.length > 0) {
|
|
972
|
+
result.isValid = false;
|
|
973
|
+
}
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
class ValidatorFactory {
|
|
979
|
+
static create(spec, options) {
|
|
980
|
+
var _a;
|
|
981
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : exports.ProjectType.SME;
|
|
982
|
+
switch (type) {
|
|
983
|
+
case exports.ProjectType.SME:
|
|
984
|
+
return new SMEValidator(spec, options);
|
|
985
|
+
case exports.ProjectType.Copilot:
|
|
986
|
+
return new CopilotValidator(spec, options);
|
|
987
|
+
case exports.ProjectType.TeamsAi:
|
|
988
|
+
return new TeamsAIValidator(spec, options);
|
|
989
|
+
default:
|
|
990
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
764
993
|
}
|
|
765
994
|
|
|
766
995
|
// Copyright (c) Microsoft Corporation.
|
|
767
996
|
class SpecFilter {
|
|
768
|
-
static specFilter(filter, unResolveSpec, resolvedSpec,
|
|
997
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
998
|
+
var _a;
|
|
769
999
|
try {
|
|
770
1000
|
const newSpec = Object.assign({}, unResolveSpec);
|
|
771
1001
|
const newPaths = {};
|
|
772
1002
|
for (const filterItem of filter) {
|
|
773
1003
|
const [method, path] = filterItem.split(" ");
|
|
774
1004
|
const methodName = method.toLowerCase();
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1005
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1006
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1007
|
+
pathObj &&
|
|
1008
|
+
pathObj[methodName]) {
|
|
1009
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1010
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1011
|
+
if (!validateResult.isValid) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!newPaths[path]) {
|
|
1015
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1016
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1017
|
+
delete newPaths[path][m];
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1021
|
+
// Add the operationId if missing
|
|
1022
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1023
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
782
1024
|
}
|
|
783
|
-
}
|
|
784
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
785
|
-
// Add the operationId if missing
|
|
786
|
-
if (!newPaths[path][methodName].operationId) {
|
|
787
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
788
1025
|
}
|
|
789
1026
|
}
|
|
790
1027
|
newSpec.paths = newPaths;
|
|
@@ -798,47 +1035,171 @@ class SpecFilter {
|
|
|
798
1035
|
|
|
799
1036
|
// Copyright (c) Microsoft Corporation.
|
|
800
1037
|
class ManifestUpdater {
|
|
801
|
-
static
|
|
1038
|
+
static updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
|
|
1039
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1040
|
+
const manifest = yield fs__default['default'].readJSON(manifestPath);
|
|
1041
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1042
|
+
manifest.plugins = [
|
|
1043
|
+
{
|
|
1044
|
+
pluginFile: apiPluginRelativePath,
|
|
1045
|
+
},
|
|
1046
|
+
];
|
|
1047
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1048
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1049
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1050
|
+
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
1051
|
+
return [manifest, apiPlugin];
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
static updateManifestDescription(manifest, spec) {
|
|
802
1055
|
var _a, _b;
|
|
1056
|
+
manifest.description = {
|
|
1057
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1058
|
+
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),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
1062
|
+
let parameter;
|
|
1063
|
+
if (schema.type === "string" ||
|
|
1064
|
+
schema.type === "boolean" ||
|
|
1065
|
+
schema.type === "integer" ||
|
|
1066
|
+
schema.type === "number" ||
|
|
1067
|
+
schema.type === "array") {
|
|
1068
|
+
parameter = schema;
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), exports.ErrorType.UpdateManifestFailed);
|
|
1072
|
+
}
|
|
1073
|
+
return parameter;
|
|
1074
|
+
}
|
|
1075
|
+
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
1076
|
+
var _a, _b, _c;
|
|
1077
|
+
const functions = [];
|
|
1078
|
+
const functionNames = [];
|
|
1079
|
+
const paths = spec.paths;
|
|
1080
|
+
for (const pathUrl in paths) {
|
|
1081
|
+
const pathItem = paths[pathUrl];
|
|
1082
|
+
if (pathItem) {
|
|
1083
|
+
const operations = pathItem;
|
|
1084
|
+
for (const method in operations) {
|
|
1085
|
+
if (options.allowMethods.includes(method)) {
|
|
1086
|
+
const operationItem = operations[method];
|
|
1087
|
+
if (operationItem) {
|
|
1088
|
+
const operationId = operationItem.operationId;
|
|
1089
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1090
|
+
const paramObject = operationItem.parameters;
|
|
1091
|
+
const requestBody = operationItem.requestBody;
|
|
1092
|
+
const parameters = {
|
|
1093
|
+
type: "object",
|
|
1094
|
+
properties: {},
|
|
1095
|
+
required: [],
|
|
1096
|
+
};
|
|
1097
|
+
if (paramObject) {
|
|
1098
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1099
|
+
const param = paramObject[i];
|
|
1100
|
+
const schema = param.schema;
|
|
1101
|
+
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1102
|
+
if (param.required) {
|
|
1103
|
+
parameters.required.push(param.name);
|
|
1104
|
+
}
|
|
1105
|
+
if (!parameters.properties[param.name].description) {
|
|
1106
|
+
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
if (requestBody) {
|
|
1111
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1112
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1113
|
+
if (requestBodySchema.type === "object") {
|
|
1114
|
+
if (requestBodySchema.required) {
|
|
1115
|
+
parameters.required.push(...requestBodySchema.required);
|
|
1116
|
+
}
|
|
1117
|
+
for (const property in requestBodySchema.properties) {
|
|
1118
|
+
const schema = requestBodySchema.properties[property];
|
|
1119
|
+
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const funcObj = {
|
|
1127
|
+
name: operationId,
|
|
1128
|
+
description: description,
|
|
1129
|
+
parameters: parameters,
|
|
1130
|
+
};
|
|
1131
|
+
functions.push(funcObj);
|
|
1132
|
+
functionNames.push(operationId);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const apiPlugin = {
|
|
1139
|
+
schema_version: "v2",
|
|
1140
|
+
name_for_human: appName,
|
|
1141
|
+
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
1142
|
+
functions: functions,
|
|
1143
|
+
runtimes: [
|
|
1144
|
+
{
|
|
1145
|
+
type: "OpenApi",
|
|
1146
|
+
auth: {
|
|
1147
|
+
type: "none", // TODO, support auth in the future
|
|
1148
|
+
},
|
|
1149
|
+
spec: {
|
|
1150
|
+
url: specRelativePath,
|
|
1151
|
+
},
|
|
1152
|
+
run_for_functions: functionNames,
|
|
1153
|
+
},
|
|
1154
|
+
],
|
|
1155
|
+
};
|
|
1156
|
+
return apiPlugin;
|
|
1157
|
+
}
|
|
1158
|
+
static updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
803
1159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
804
1160
|
try {
|
|
805
|
-
const originalManifest = yield fs__default[
|
|
1161
|
+
const originalManifest = yield fs__default['default'].readJSON(manifestPath);
|
|
806
1162
|
const updatedPart = {};
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
commands
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1163
|
+
updatedPart.composeExtensions = [];
|
|
1164
|
+
let warnings = [];
|
|
1165
|
+
if (options.projectType === exports.ProjectType.SME) {
|
|
1166
|
+
const updateResult = yield ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1167
|
+
const commands = updateResult[0];
|
|
1168
|
+
warnings = updateResult[1];
|
|
1169
|
+
const composeExtension = {
|
|
1170
|
+
composeExtensionType: "apiBased",
|
|
1171
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1172
|
+
commands: commands,
|
|
1173
|
+
};
|
|
1174
|
+
if (authInfo) {
|
|
1175
|
+
const auth = authInfo.authScheme;
|
|
1176
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1177
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1178
|
+
composeExtension.authorization = {
|
|
1179
|
+
authType: "apiSecretServiceAuth",
|
|
1180
|
+
apiSecretServiceAuthConfiguration: {
|
|
1181
|
+
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1182
|
+
},
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1186
|
+
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1187
|
+
composeExtension.authorization = {
|
|
1188
|
+
authType: "oAuth2.0",
|
|
1189
|
+
oAuthConfiguration: {
|
|
1190
|
+
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1191
|
+
},
|
|
1192
|
+
};
|
|
1193
|
+
updatedPart.webApplicationInfo = {
|
|
1194
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1195
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
835
1198
|
}
|
|
1199
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
836
1200
|
}
|
|
837
|
-
updatedPart.description =
|
|
838
|
-
|
|
839
|
-
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),
|
|
840
|
-
};
|
|
841
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
1201
|
+
updatedPart.description = originalManifest.description;
|
|
1202
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
842
1203
|
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
843
1204
|
return [updatedManifest, warnings];
|
|
844
1205
|
}
|
|
@@ -847,7 +1208,8 @@ class ManifestUpdater {
|
|
|
847
1208
|
}
|
|
848
1209
|
});
|
|
849
1210
|
}
|
|
850
|
-
static generateCommands(spec,
|
|
1211
|
+
static generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1212
|
+
var _a;
|
|
851
1213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
852
1214
|
const paths = spec.paths;
|
|
853
1215
|
const commands = [];
|
|
@@ -859,16 +1221,28 @@ class ManifestUpdater {
|
|
|
859
1221
|
const operations = pathItem;
|
|
860
1222
|
// Currently only support GET and POST method
|
|
861
1223
|
for (const method in operations) {
|
|
862
|
-
if (
|
|
1224
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
863
1225
|
const operationItem = operations[method];
|
|
864
1226
|
if (operationItem) {
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1227
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1228
|
+
if (command.parameters &&
|
|
1229
|
+
command.parameters.length >= 1 &&
|
|
1230
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1231
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1232
|
+
}
|
|
1233
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1234
|
+
command.parameters = [command.parameters[0]];
|
|
1235
|
+
warnings.push({
|
|
1236
|
+
type: exports.WarningType.OperationOnlyContainsOptionalParam,
|
|
1237
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1238
|
+
data: command.id,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
if (adaptiveCardFolder) {
|
|
1242
|
+
const adaptiveCardPath = path__default['default'].join(adaptiveCardFolder, command.id + ".json");
|
|
1243
|
+
command.apiResponseRenderingTemplateFile = (yield fs__default['default'].pathExists(adaptiveCardPath))
|
|
1244
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1245
|
+
: "";
|
|
872
1246
|
}
|
|
873
1247
|
commands.push(command);
|
|
874
1248
|
}
|
|
@@ -881,8 +1255,17 @@ class ManifestUpdater {
|
|
|
881
1255
|
});
|
|
882
1256
|
}
|
|
883
1257
|
static getRelativePath(from, to) {
|
|
884
|
-
const relativePath = path__default[
|
|
885
|
-
return path__default[
|
|
1258
|
+
const relativePath = path__default['default'].relative(path__default['default'].dirname(from), to);
|
|
1259
|
+
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
|
|
1260
|
+
}
|
|
1261
|
+
static removeEnvs(str) {
|
|
1262
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1263
|
+
const matches = placeHolderReg.exec(str);
|
|
1264
|
+
let newStr = str;
|
|
1265
|
+
if (matches != null) {
|
|
1266
|
+
newStr = newStr.replace(matches[0], "");
|
|
1267
|
+
}
|
|
1268
|
+
return newStr;
|
|
886
1269
|
}
|
|
887
1270
|
}
|
|
888
1271
|
|
|
@@ -890,7 +1273,7 @@ class ManifestUpdater {
|
|
|
890
1273
|
class AdaptiveCardGenerator {
|
|
891
1274
|
static generateAdaptiveCard(operationItem) {
|
|
892
1275
|
try {
|
|
893
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1276
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
894
1277
|
let cardBody = [];
|
|
895
1278
|
let schema = json.schema;
|
|
896
1279
|
let jsonPath = "$";
|
|
@@ -1150,11 +1533,14 @@ class SpecParser {
|
|
|
1150
1533
|
allowMissingId: true,
|
|
1151
1534
|
allowSwagger: true,
|
|
1152
1535
|
allowAPIKeyAuth: false,
|
|
1536
|
+
allowBearerTokenAuth: false,
|
|
1153
1537
|
allowMultipleParameters: false,
|
|
1154
1538
|
allowOauth2: false,
|
|
1539
|
+
allowMethods: ["get", "post"],
|
|
1540
|
+
projectType: exports.ProjectType.SME,
|
|
1155
1541
|
};
|
|
1156
1542
|
this.pathOrSpec = pathOrDoc;
|
|
1157
|
-
this.parser = new SwaggerParser__default[
|
|
1543
|
+
this.parser = new SwaggerParser__default['default']();
|
|
1158
1544
|
this.options = Object.assign(Object.assign({}, this.defaultOptions), (options !== null && options !== void 0 ? options : {}));
|
|
1159
1545
|
}
|
|
1160
1546
|
/**
|
|
@@ -1185,7 +1571,24 @@ class SpecParser {
|
|
|
1185
1571
|
],
|
|
1186
1572
|
};
|
|
1187
1573
|
}
|
|
1188
|
-
|
|
1574
|
+
if (this.options.projectType === exports.ProjectType.SME ||
|
|
1575
|
+
this.options.projectType === exports.ProjectType.Copilot) {
|
|
1576
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
1577
|
+
return {
|
|
1578
|
+
status: exports.ValidationStatus.Error,
|
|
1579
|
+
warnings: [],
|
|
1580
|
+
errors: [
|
|
1581
|
+
{
|
|
1582
|
+
type: exports.ErrorType.SpecVersionNotSupported,
|
|
1583
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
1584
|
+
data: this.spec.openapi,
|
|
1585
|
+
},
|
|
1586
|
+
],
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const apiMap = this.getAPIs(this.spec);
|
|
1591
|
+
return Utils.validateSpec(this.spec, this.parser, apiMap, !!this.isSwaggerFile, this.options);
|
|
1189
1592
|
}
|
|
1190
1593
|
catch (err) {
|
|
1191
1594
|
throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
|
|
@@ -1209,16 +1612,22 @@ class SpecParser {
|
|
|
1209
1612
|
try {
|
|
1210
1613
|
yield this.loadSpec();
|
|
1211
1614
|
const spec = this.spec;
|
|
1212
|
-
const apiMap = this.
|
|
1213
|
-
const result =
|
|
1615
|
+
const apiMap = this.getAPIs(spec);
|
|
1616
|
+
const result = {
|
|
1617
|
+
APIs: [],
|
|
1618
|
+
allAPICount: 0,
|
|
1619
|
+
validAPICount: 0,
|
|
1620
|
+
};
|
|
1214
1621
|
for (const apiKey in apiMap) {
|
|
1622
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1623
|
+
const [method, path] = apiKey.split(" ");
|
|
1215
1624
|
const apiResult = {
|
|
1216
1625
|
api: "",
|
|
1217
1626
|
server: "",
|
|
1218
1627
|
operationId: "",
|
|
1628
|
+
isValid: isValid,
|
|
1629
|
+
reason: reason,
|
|
1219
1630
|
};
|
|
1220
|
-
const [method, path] = apiKey.split(" ");
|
|
1221
|
-
const operation = apiMap[apiKey];
|
|
1222
1631
|
const rootServer = spec.servers && spec.servers[0];
|
|
1223
1632
|
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
1224
1633
|
const operationServer = operation.servers && operation.servers[0];
|
|
@@ -1226,7 +1635,7 @@ class SpecParser {
|
|
|
1226
1635
|
if (!serverUrl) {
|
|
1227
1636
|
throw new SpecParserError(ConstantString.NoServerInformation, exports.ErrorType.NoServerInformation);
|
|
1228
1637
|
}
|
|
1229
|
-
apiResult.server = Utils.
|
|
1638
|
+
apiResult.server = Utils.resolveEnv(serverUrl.url);
|
|
1230
1639
|
let operationId = operation.operationId;
|
|
1231
1640
|
if (!operationId) {
|
|
1232
1641
|
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
@@ -1235,13 +1644,15 @@ class SpecParser {
|
|
|
1235
1644
|
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1236
1645
|
for (const auths of authArray) {
|
|
1237
1646
|
if (auths.length === 1) {
|
|
1238
|
-
apiResult.auth = auths[0]
|
|
1647
|
+
apiResult.auth = auths[0];
|
|
1239
1648
|
break;
|
|
1240
1649
|
}
|
|
1241
1650
|
}
|
|
1242
1651
|
apiResult.api = apiKey;
|
|
1243
|
-
result.push(apiResult);
|
|
1652
|
+
result.APIs.push(apiResult);
|
|
1244
1653
|
}
|
|
1654
|
+
result.allAPICount = result.APIs.length;
|
|
1655
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1245
1656
|
return result;
|
|
1246
1657
|
}
|
|
1247
1658
|
catch (err) {
|
|
@@ -1252,78 +1663,144 @@ class SpecParser {
|
|
|
1252
1663
|
}
|
|
1253
1664
|
});
|
|
1254
1665
|
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Generate specs according to the filters.
|
|
1668
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1669
|
+
*/
|
|
1670
|
+
getFilteredSpecs(filter, signal) {
|
|
1671
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1672
|
+
try {
|
|
1673
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1674
|
+
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1675
|
+
}
|
|
1676
|
+
yield this.loadSpec();
|
|
1677
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1678
|
+
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1679
|
+
}
|
|
1680
|
+
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
|
|
1681
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1682
|
+
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1683
|
+
}
|
|
1684
|
+
const newSpec = (yield this.parser.dereference(newUnResolvedSpec));
|
|
1685
|
+
return [newUnResolvedSpec, newSpec];
|
|
1686
|
+
}
|
|
1687
|
+
catch (err) {
|
|
1688
|
+
if (err instanceof SpecParserError) {
|
|
1689
|
+
throw err;
|
|
1690
|
+
}
|
|
1691
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.GetSpecFailed);
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1255
1695
|
/**
|
|
1256
1696
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1257
1697
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1258
1698
|
* @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
1699
|
* @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
|
|
1700
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1261
1701
|
*/
|
|
1262
|
-
|
|
1702
|
+
generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1263
1703
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1264
1704
|
const result = {
|
|
1265
1705
|
allSuccess: true,
|
|
1266
1706
|
warnings: [],
|
|
1267
1707
|
};
|
|
1268
1708
|
try {
|
|
1269
|
-
|
|
1270
|
-
|
|
1709
|
+
const newSpecs = yield this.getFilteredSpecs(filter, signal);
|
|
1710
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1711
|
+
const newSpec = newSpecs[1];
|
|
1712
|
+
let resultStr;
|
|
1713
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1714
|
+
resultStr = jsyaml__default['default'].dump(newUnResolvedSpec);
|
|
1271
1715
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1716
|
+
else {
|
|
1717
|
+
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1275
1718
|
}
|
|
1276
|
-
|
|
1719
|
+
yield fs__default['default'].outputFile(outputSpecPath, resultStr);
|
|
1277
1720
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1278
1721
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1279
1722
|
}
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1723
|
+
const [updatedManifest, apiPlugin] = yield ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
|
|
1724
|
+
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1725
|
+
yield fs__default['default'].outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1726
|
+
}
|
|
1727
|
+
catch (err) {
|
|
1728
|
+
if (err instanceof SpecParserError) {
|
|
1729
|
+
throw err;
|
|
1730
|
+
}
|
|
1731
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.GenerateFailed);
|
|
1732
|
+
}
|
|
1733
|
+
return result;
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1738
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1739
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1740
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1741
|
+
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
1742
|
+
*/
|
|
1743
|
+
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
1744
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1745
|
+
const result = {
|
|
1746
|
+
allSuccess: true,
|
|
1747
|
+
warnings: [],
|
|
1748
|
+
};
|
|
1749
|
+
try {
|
|
1750
|
+
const newSpecs = yield this.getFilteredSpecs(filter, signal);
|
|
1751
|
+
const newUnResolvedSpec = newSpecs[0];
|
|
1752
|
+
const newSpec = newSpecs[1];
|
|
1753
|
+
let hasMultipleAuth = false;
|
|
1754
|
+
let authInfo = undefined;
|
|
1283
1755
|
for (const url in newSpec.paths) {
|
|
1284
1756
|
for (const method in newSpec.paths[url]) {
|
|
1285
1757
|
const operation = newSpec.paths[url][method];
|
|
1286
1758
|
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1287
1759
|
if (authArray && authArray.length > 0) {
|
|
1288
|
-
|
|
1289
|
-
if (
|
|
1290
|
-
|
|
1760
|
+
const currentAuth = authArray[0][0];
|
|
1761
|
+
if (!authInfo) {
|
|
1762
|
+
authInfo = authArray[0][0];
|
|
1763
|
+
}
|
|
1764
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
1765
|
+
hasMultipleAuth = true;
|
|
1291
1766
|
break;
|
|
1292
1767
|
}
|
|
1293
1768
|
}
|
|
1294
1769
|
}
|
|
1295
1770
|
}
|
|
1296
|
-
if (
|
|
1297
|
-
throw new SpecParserError(ConstantString.
|
|
1771
|
+
if (hasMultipleAuth && this.options.projectType !== exports.ProjectType.TeamsAi) {
|
|
1772
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, exports.ErrorType.MultipleAuthNotSupported);
|
|
1298
1773
|
}
|
|
1299
1774
|
let resultStr;
|
|
1300
1775
|
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1301
|
-
resultStr = jsyaml__default[
|
|
1776
|
+
resultStr = jsyaml__default['default'].dump(newUnResolvedSpec);
|
|
1302
1777
|
}
|
|
1303
1778
|
else {
|
|
1304
1779
|
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1305
1780
|
}
|
|
1306
|
-
yield fs__default[
|
|
1307
|
-
|
|
1308
|
-
for (const
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1781
|
+
yield fs__default['default'].outputFile(outputSpecPath, resultStr);
|
|
1782
|
+
if (adaptiveCardFolder) {
|
|
1783
|
+
for (const url in newSpec.paths) {
|
|
1784
|
+
for (const method in newSpec.paths[url]) {
|
|
1785
|
+
// paths object may contain description/summary which is not a http method, so we need to check if it is a operation object
|
|
1786
|
+
if (this.options.allowMethods.includes(method)) {
|
|
1787
|
+
const operation = newSpec.paths[url][method];
|
|
1788
|
+
try {
|
|
1789
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
|
|
1790
|
+
const fileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.json`);
|
|
1791
|
+
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
|
|
1792
|
+
yield fs__default['default'].outputJSON(fileName, wrappedCard, { spaces: 2 });
|
|
1793
|
+
const dataFileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.data.json`);
|
|
1794
|
+
yield fs__default['default'].outputJSON(dataFileName, {}, { spaces: 2 });
|
|
1795
|
+
}
|
|
1796
|
+
catch (err) {
|
|
1797
|
+
result.allSuccess = false;
|
|
1798
|
+
result.warnings.push({
|
|
1799
|
+
type: exports.WarningType.GenerateCardFailed,
|
|
1800
|
+
content: err.toString(),
|
|
1801
|
+
data: operation.operationId,
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1327
1804
|
}
|
|
1328
1805
|
}
|
|
1329
1806
|
}
|
|
@@ -1331,9 +1808,8 @@ class SpecParser {
|
|
|
1331
1808
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1332
1809
|
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
|
|
1333
1810
|
}
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
yield fs__default["default"].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1811
|
+
const [updatedManifest, warnings] = yield ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1812
|
+
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1337
1813
|
result.warnings.push(...warnings);
|
|
1338
1814
|
}
|
|
1339
1815
|
catch (err) {
|
|
@@ -1351,7 +1827,7 @@ class SpecParser {
|
|
|
1351
1827
|
this.unResolveSpec = (yield this.parser.parse(this.pathOrSpec));
|
|
1352
1828
|
// Convert swagger 2.0 to openapi 3.0
|
|
1353
1829
|
if (!this.unResolveSpec.openapi && this.unResolveSpec.swagger === "2.0") {
|
|
1354
|
-
const specObj = yield converter__default[
|
|
1830
|
+
const specObj = yield converter__default['default'].convert(this.unResolveSpec, {});
|
|
1355
1831
|
this.unResolveSpec = specObj.openapi;
|
|
1356
1832
|
this.isSwaggerFile = true;
|
|
1357
1833
|
}
|
|
@@ -1360,16 +1836,38 @@ class SpecParser {
|
|
|
1360
1836
|
}
|
|
1361
1837
|
});
|
|
1362
1838
|
}
|
|
1363
|
-
|
|
1839
|
+
getAPIs(spec) {
|
|
1364
1840
|
if (this.apiMap !== undefined) {
|
|
1365
1841
|
return this.apiMap;
|
|
1366
1842
|
}
|
|
1367
|
-
const result =
|
|
1843
|
+
const result = this.listAPIs(spec, this.options);
|
|
1368
1844
|
this.apiMap = result;
|
|
1369
1845
|
return result;
|
|
1370
1846
|
}
|
|
1847
|
+
listAPIs(spec, options) {
|
|
1848
|
+
var _a;
|
|
1849
|
+
const paths = spec.paths;
|
|
1850
|
+
const result = {};
|
|
1851
|
+
for (const path in paths) {
|
|
1852
|
+
const methods = paths[path];
|
|
1853
|
+
for (const method in methods) {
|
|
1854
|
+
const operationObject = methods[method];
|
|
1855
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
1856
|
+
const validator = ValidatorFactory.create(spec, options);
|
|
1857
|
+
const validateResult = validator.validateAPI(method, path);
|
|
1858
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
1859
|
+
operation: operationObject,
|
|
1860
|
+
isValid: validateResult.isValid,
|
|
1861
|
+
reason: validateResult.reason,
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
return result;
|
|
1867
|
+
}
|
|
1371
1868
|
}
|
|
1372
1869
|
|
|
1870
|
+
exports.AdaptiveCardGenerator = AdaptiveCardGenerator;
|
|
1373
1871
|
exports.ConstantString = ConstantString;
|
|
1374
1872
|
exports.SpecParser = SpecParser;
|
|
1375
1873
|
exports.SpecParserError = SpecParserError;
|