@microsoft/m365-spec-parser 0.1.1-alpha.4cb5c08a8.0 → 0.1.1-alpha.4e708f092.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 +539 -360
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +924 -641
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +539 -360
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +924 -639
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +27 -0
- package/dist/src/manifestUpdater.d.ts +1 -1
- package/dist/src/specParser.browser.d.ts +3 -3
- package/dist/src/specParser.d.ts +2 -1
- package/dist/src/utils.d.ts +4 -25
- package/package.json +3 -3
package/dist/index.node.cjs.js
CHANGED
|
@@ -145,6 +145,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
145
145
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
146
146
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
147
147
|
ConstantString.TextBlockType = "TextBlock";
|
|
148
|
+
ConstantString.ImageType = "Image";
|
|
148
149
|
ConstantString.ContainerType = "Container";
|
|
149
150
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
150
151
|
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
@@ -208,7 +209,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
208
209
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
209
210
|
ConstantString.CommandTitleMaxLens = 32;
|
|
210
211
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
211
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
212
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
213
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
212
214
|
|
|
213
215
|
// Copyright (c) Microsoft Corporation.
|
|
214
216
|
class SpecParserError extends Error {
|
|
@@ -231,249 +233,9 @@ class Utils {
|
|
|
231
233
|
}
|
|
232
234
|
return false;
|
|
233
235
|
}
|
|
234
|
-
static checkParameters(paramObject, isCopilot) {
|
|
235
|
-
const paramResult = {
|
|
236
|
-
requiredNum: 0,
|
|
237
|
-
optionalNum: 0,
|
|
238
|
-
isValid: true,
|
|
239
|
-
reason: [],
|
|
240
|
-
};
|
|
241
|
-
if (!paramObject) {
|
|
242
|
-
return paramResult;
|
|
243
|
-
}
|
|
244
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
245
|
-
const param = paramObject[i];
|
|
246
|
-
const schema = param.schema;
|
|
247
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
248
|
-
paramResult.isValid = false;
|
|
249
|
-
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
253
|
-
if (isCopilot) {
|
|
254
|
-
if (isRequiredWithoutDefault) {
|
|
255
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
259
|
-
}
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
263
|
-
if (isRequiredWithoutDefault) {
|
|
264
|
-
paramResult.isValid = false;
|
|
265
|
-
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
266
|
-
}
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (schema.type !== "boolean" &&
|
|
270
|
-
schema.type !== "string" &&
|
|
271
|
-
schema.type !== "number" &&
|
|
272
|
-
schema.type !== "integer") {
|
|
273
|
-
if (isRequiredWithoutDefault) {
|
|
274
|
-
paramResult.isValid = false;
|
|
275
|
-
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
276
|
-
}
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
if (param.in === "query" || param.in === "path") {
|
|
280
|
-
if (isRequiredWithoutDefault) {
|
|
281
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return paramResult;
|
|
289
|
-
}
|
|
290
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
291
|
-
var _a;
|
|
292
|
-
const paramResult = {
|
|
293
|
-
requiredNum: 0,
|
|
294
|
-
optionalNum: 0,
|
|
295
|
-
isValid: true,
|
|
296
|
-
reason: [],
|
|
297
|
-
};
|
|
298
|
-
if (Object.keys(schema).length === 0) {
|
|
299
|
-
return paramResult;
|
|
300
|
-
}
|
|
301
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
302
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
303
|
-
paramResult.isValid = false;
|
|
304
|
-
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
|
|
305
|
-
return paramResult;
|
|
306
|
-
}
|
|
307
|
-
if (schema.type === "string" ||
|
|
308
|
-
schema.type === "integer" ||
|
|
309
|
-
schema.type === "boolean" ||
|
|
310
|
-
schema.type === "number") {
|
|
311
|
-
if (isRequiredWithoutDefault) {
|
|
312
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else if (schema.type === "object") {
|
|
319
|
-
const { properties } = schema;
|
|
320
|
-
for (const property in properties) {
|
|
321
|
-
let isRequired = false;
|
|
322
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
323
|
-
isRequired = true;
|
|
324
|
-
}
|
|
325
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
326
|
-
paramResult.requiredNum += result.requiredNum;
|
|
327
|
-
paramResult.optionalNum += result.optionalNum;
|
|
328
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
329
|
-
paramResult.reason.push(...result.reason);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
334
|
-
paramResult.isValid = false;
|
|
335
|
-
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return paramResult;
|
|
339
|
-
}
|
|
340
236
|
static containMultipleMediaTypes(bodyObject) {
|
|
341
237
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
342
238
|
}
|
|
343
|
-
/**
|
|
344
|
-
* Checks if the given API is supported.
|
|
345
|
-
* @param {string} method - The HTTP method of the API.
|
|
346
|
-
* @param {string} path - The path of the API.
|
|
347
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
348
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
349
|
-
* @description The following APIs are supported:
|
|
350
|
-
* 1. only support Get/Post operation without auth property
|
|
351
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
352
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
353
|
-
* 4. request body + required parameters <= 1
|
|
354
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
355
|
-
* 6. only support request body with “application/json” content type
|
|
356
|
-
*/
|
|
357
|
-
static isSupportedApi(method, path, spec, options) {
|
|
358
|
-
var _a;
|
|
359
|
-
const result = { isValid: true, reason: [] };
|
|
360
|
-
method = method.toLocaleLowerCase();
|
|
361
|
-
if (options.allowMethods && !options.allowMethods.includes(method)) {
|
|
362
|
-
result.isValid = false;
|
|
363
|
-
result.reason.push(exports.ErrorType.MethodNotAllowed);
|
|
364
|
-
return result;
|
|
365
|
-
}
|
|
366
|
-
const pathObj = spec.paths[path];
|
|
367
|
-
if (!pathObj || !pathObj[method]) {
|
|
368
|
-
result.isValid = false;
|
|
369
|
-
result.reason.push(exports.ErrorType.UrlPathNotExist);
|
|
370
|
-
return result;
|
|
371
|
-
}
|
|
372
|
-
const securities = pathObj[method].security;
|
|
373
|
-
const isTeamsAi = options.projectType === exports.ProjectType.TeamsAi;
|
|
374
|
-
const isCopilot = options.projectType === exports.ProjectType.Copilot;
|
|
375
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
376
|
-
if (!isTeamsAi) {
|
|
377
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
378
|
-
const authCheckResult = Utils.isSupportedAuth(authArray, options);
|
|
379
|
-
if (!authCheckResult.isValid) {
|
|
380
|
-
result.reason.push(...authCheckResult.reason);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
const operationObject = pathObj[method];
|
|
384
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
385
|
-
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
386
|
-
}
|
|
387
|
-
const rootServer = spec.servers && spec.servers[0];
|
|
388
|
-
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
389
|
-
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
390
|
-
const serverUrl = operationServer || methodServer || rootServer;
|
|
391
|
-
if (!serverUrl) {
|
|
392
|
-
result.reason.push(exports.ErrorType.NoServerInformation);
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
396
|
-
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
397
|
-
}
|
|
398
|
-
const paramObject = operationObject.parameters;
|
|
399
|
-
const requestBody = operationObject.requestBody;
|
|
400
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
401
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
402
|
-
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
403
|
-
}
|
|
404
|
-
const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
405
|
-
if (multipleMediaType && !isTeamsAi) {
|
|
406
|
-
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
|
|
407
|
-
}
|
|
408
|
-
else if (Object.keys(json).length === 0) {
|
|
409
|
-
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
|
|
410
|
-
}
|
|
411
|
-
// Teams AI project doesn't care about request parameters/body
|
|
412
|
-
if (!isTeamsAi) {
|
|
413
|
-
let requestBodyParamResult = {
|
|
414
|
-
requiredNum: 0,
|
|
415
|
-
optionalNum: 0,
|
|
416
|
-
isValid: true,
|
|
417
|
-
reason: [],
|
|
418
|
-
};
|
|
419
|
-
if (requestJsonBody) {
|
|
420
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
421
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
422
|
-
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
|
|
423
|
-
}
|
|
424
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
425
|
-
if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
|
|
426
|
-
result.reason.push(...requestBodyParamResult.reason);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
430
|
-
if (!paramResult.isValid && paramResult.reason) {
|
|
431
|
-
result.reason.push(...paramResult.reason);
|
|
432
|
-
}
|
|
433
|
-
// Copilot support arbitrary parameters
|
|
434
|
-
if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
|
|
435
|
-
const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
|
|
436
|
-
const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
|
|
437
|
-
if (totalRequiredParams > 1) {
|
|
438
|
-
if (!options.allowMultipleParameters ||
|
|
439
|
-
totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
|
|
440
|
-
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
else if (totalParams === 0) {
|
|
444
|
-
result.reason.push(exports.ErrorType.NoParameter);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (result.reason.length > 0) {
|
|
449
|
-
result.isValid = false;
|
|
450
|
-
}
|
|
451
|
-
return result;
|
|
452
|
-
}
|
|
453
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
454
|
-
if (authSchemeArray.length === 0) {
|
|
455
|
-
return { isValid: true, reason: [] };
|
|
456
|
-
}
|
|
457
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
458
|
-
// Currently we don't support multiple auth in one operation
|
|
459
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
460
|
-
return {
|
|
461
|
-
isValid: false,
|
|
462
|
-
reason: [exports.ErrorType.MultipleAuthNotSupported],
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
for (const auths of authSchemeArray) {
|
|
466
|
-
if (auths.length === 1) {
|
|
467
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
468
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
469
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
470
|
-
return { isValid: true, reason: [] };
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
|
|
476
|
-
}
|
|
477
239
|
static isBearerTokenAuth(authScheme) {
|
|
478
240
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
479
241
|
}
|
|
@@ -481,10 +243,9 @@ class Utils {
|
|
|
481
243
|
return authScheme.type === "apiKey";
|
|
482
244
|
}
|
|
483
245
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return false;
|
|
246
|
+
return !!(authScheme.type === "oauth2" &&
|
|
247
|
+
authScheme.flows &&
|
|
248
|
+
authScheme.flows.authorizationCode);
|
|
488
249
|
}
|
|
489
250
|
static getAuthArray(securities, spec) {
|
|
490
251
|
var _a;
|
|
@@ -512,7 +273,7 @@ class Utils {
|
|
|
512
273
|
static updateFirstLetter(str) {
|
|
513
274
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
514
275
|
}
|
|
515
|
-
static getResponseJson(operationObject
|
|
276
|
+
static getResponseJson(operationObject) {
|
|
516
277
|
var _a, _b;
|
|
517
278
|
let json = {};
|
|
518
279
|
let multipleMediaType = false;
|
|
@@ -523,9 +284,6 @@ class Utils {
|
|
|
523
284
|
json = responseObject.content["application/json"];
|
|
524
285
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
525
286
|
multipleMediaType = true;
|
|
526
|
-
if (isTeamsAiProject) {
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
287
|
json = {};
|
|
530
288
|
}
|
|
531
289
|
else {
|
|
@@ -753,13 +511,7 @@ class Utils {
|
|
|
753
511
|
}
|
|
754
512
|
}
|
|
755
513
|
const operationId = operationItem.operationId;
|
|
756
|
-
const parameters = [];
|
|
757
|
-
if (requiredParams.length !== 0) {
|
|
758
|
-
parameters.push(...requiredParams);
|
|
759
|
-
}
|
|
760
|
-
else {
|
|
761
|
-
parameters.push(optionalParams[0]);
|
|
762
|
-
}
|
|
514
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
763
515
|
const command = {
|
|
764
516
|
context: ["compose"],
|
|
765
517
|
type: "query",
|
|
@@ -768,26 +520,51 @@ class Utils {
|
|
|
768
520
|
parameters: parameters,
|
|
769
521
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
770
522
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
523
|
+
return command;
|
|
524
|
+
}
|
|
525
|
+
static format(str, ...args) {
|
|
526
|
+
let index = 0;
|
|
527
|
+
return str.replace(/%s/g, () => {
|
|
528
|
+
const arg = args[index++];
|
|
529
|
+
return arg !== undefined ? arg : "";
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
533
|
+
if (!authName) {
|
|
534
|
+
return "";
|
|
778
535
|
}
|
|
779
|
-
|
|
536
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
537
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
538
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
539
|
+
}
|
|
540
|
+
return safeRegistrationIdEnvName;
|
|
541
|
+
}
|
|
542
|
+
static getServerObject(spec, method, path) {
|
|
543
|
+
const pathObj = spec.paths[path];
|
|
544
|
+
const operationObject = pathObj[method];
|
|
545
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
546
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
547
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
548
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
549
|
+
return serverUrl;
|
|
780
550
|
}
|
|
781
|
-
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Copyright (c) Microsoft Corporation.
|
|
554
|
+
class Validator {
|
|
555
|
+
listAPIs() {
|
|
782
556
|
var _a;
|
|
783
|
-
|
|
557
|
+
if (this.apiMap) {
|
|
558
|
+
return this.apiMap;
|
|
559
|
+
}
|
|
560
|
+
const paths = this.spec.paths;
|
|
784
561
|
const result = {};
|
|
785
562
|
for (const path in paths) {
|
|
786
563
|
const methods = paths[path];
|
|
787
564
|
for (const method in methods) {
|
|
788
565
|
const operationObject = methods[method];
|
|
789
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
790
|
-
const validateResult =
|
|
566
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
567
|
+
const validateResult = this.validateAPI(method, path);
|
|
791
568
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
792
569
|
operation: operationObject,
|
|
793
570
|
isValid: validateResult.isValid,
|
|
@@ -796,38 +573,48 @@ class Utils {
|
|
|
796
573
|
}
|
|
797
574
|
}
|
|
798
575
|
}
|
|
576
|
+
this.apiMap = result;
|
|
799
577
|
return result;
|
|
800
578
|
}
|
|
801
|
-
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
579
|
+
validateSpecVersion() {
|
|
580
|
+
const result = { errors: [], warnings: [] };
|
|
581
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
582
|
+
result.errors.push({
|
|
583
|
+
type: exports.ErrorType.SpecVersionNotSupported,
|
|
584
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
585
|
+
data: this.spec.openapi,
|
|
809
586
|
});
|
|
810
587
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
// No supported API
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
validateSpecServer() {
|
|
591
|
+
const result = { errors: [], warnings: [] };
|
|
592
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
593
|
+
result.errors.push(...serverErrors);
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
validateSpecNoSupportAPI() {
|
|
597
|
+
const result = { errors: [], warnings: [] };
|
|
598
|
+
const apiMap = this.listAPIs();
|
|
824
599
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
825
600
|
if (validAPIs.length === 0) {
|
|
826
|
-
|
|
601
|
+
const data = [];
|
|
602
|
+
for (const key in apiMap) {
|
|
603
|
+
const { reason } = apiMap[key];
|
|
604
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
605
|
+
data.push(apiInvalidReason);
|
|
606
|
+
}
|
|
607
|
+
result.errors.push({
|
|
827
608
|
type: exports.ErrorType.NoSupportedApi,
|
|
828
609
|
content: ConstantString.NoSupportedApi,
|
|
610
|
+
data,
|
|
829
611
|
});
|
|
830
612
|
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
validateSpecOperationId() {
|
|
616
|
+
const result = { errors: [], warnings: [] };
|
|
617
|
+
const apiMap = this.listAPIs();
|
|
831
618
|
// OperationId missing
|
|
832
619
|
const apisMissingOperationId = [];
|
|
833
620
|
for (const key in apiMap) {
|
|
@@ -837,320 +624,472 @@ class Utils {
|
|
|
837
624
|
}
|
|
838
625
|
}
|
|
839
626
|
if (apisMissingOperationId.length > 0) {
|
|
840
|
-
warnings.push({
|
|
627
|
+
result.warnings.push({
|
|
841
628
|
type: exports.WarningType.OperationIdMissing,
|
|
842
629
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
843
630
|
data: apisMissingOperationId,
|
|
844
631
|
});
|
|
845
632
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
validateMethodAndPath(method, path) {
|
|
636
|
+
const result = { isValid: true, reason: [] };
|
|
637
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
638
|
+
result.isValid = false;
|
|
639
|
+
result.reason.push(exports.ErrorType.MethodNotAllowed);
|
|
640
|
+
return result;
|
|
849
641
|
}
|
|
850
|
-
|
|
851
|
-
|
|
642
|
+
const pathObj = this.spec.paths[path];
|
|
643
|
+
if (!pathObj || !pathObj[method]) {
|
|
644
|
+
result.isValid = false;
|
|
645
|
+
result.reason.push(exports.ErrorType.UrlPathNotExist);
|
|
646
|
+
return result;
|
|
852
647
|
}
|
|
853
|
-
return
|
|
854
|
-
status,
|
|
855
|
-
warnings,
|
|
856
|
-
errors,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
static format(str, ...args) {
|
|
860
|
-
let index = 0;
|
|
861
|
-
return str.replace(/%s/g, () => {
|
|
862
|
-
const arg = args[index++];
|
|
863
|
-
return arg !== undefined ? arg : "";
|
|
864
|
-
});
|
|
648
|
+
return result;
|
|
865
649
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
650
|
+
validateResponse(method, path) {
|
|
651
|
+
const result = { isValid: true, reason: [] };
|
|
652
|
+
const operationObject = this.spec.paths[path][method];
|
|
653
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
654
|
+
// only support response body only contains “application/json” content type
|
|
655
|
+
if (multipleMediaType) {
|
|
656
|
+
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
|
|
869
657
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
658
|
+
else if (Object.keys(json).length === 0) {
|
|
659
|
+
// response body should not be empty
|
|
660
|
+
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
|
|
873
661
|
}
|
|
874
|
-
return
|
|
662
|
+
return result;
|
|
875
663
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
664
|
+
validateServer(method, path) {
|
|
665
|
+
const result = { isValid: true, reason: [] };
|
|
666
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
667
|
+
if (!serverObj) {
|
|
668
|
+
// should contain server URL
|
|
669
|
+
result.reason.push(exports.ErrorType.NoServerInformation);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
// server url should be absolute url with https protocol
|
|
673
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
674
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
675
|
+
}
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
validateAuth(method, path) {
|
|
679
|
+
const pathObj = this.spec.paths[path];
|
|
680
|
+
const operationObject = pathObj[method];
|
|
681
|
+
const securities = operationObject.security;
|
|
682
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
683
|
+
if (authSchemeArray.length === 0) {
|
|
684
|
+
return { isValid: true, reason: [] };
|
|
685
|
+
}
|
|
686
|
+
if (this.options.allowAPIKeyAuth ||
|
|
687
|
+
this.options.allowOauth2 ||
|
|
688
|
+
this.options.allowBearerTokenAuth) {
|
|
689
|
+
// Currently we don't support multiple auth in one operation
|
|
690
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
691
|
+
return {
|
|
692
|
+
isValid: false,
|
|
693
|
+
reason: [exports.ErrorType.MultipleAuthNotSupported],
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
for (const auths of authSchemeArray) {
|
|
697
|
+
if (auths.length === 1) {
|
|
698
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
699
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
700
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
701
|
+
return { isValid: true, reason: [] };
|
|
702
|
+
}
|
|
884
703
|
}
|
|
885
704
|
}
|
|
886
705
|
}
|
|
887
|
-
return
|
|
706
|
+
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
|
|
888
707
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
// Copyright (c) Microsoft Corporation.
|
|
892
|
-
class SpecFilter {
|
|
893
|
-
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
708
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
894
709
|
var _a;
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
710
|
+
const paramResult = {
|
|
711
|
+
requiredNum: 0,
|
|
712
|
+
optionalNum: 0,
|
|
713
|
+
isValid: true,
|
|
714
|
+
reason: [],
|
|
715
|
+
};
|
|
716
|
+
if (Object.keys(schema).length === 0) {
|
|
717
|
+
return paramResult;
|
|
718
|
+
}
|
|
719
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
720
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
721
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
722
|
+
paramResult.isValid = false;
|
|
723
|
+
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
|
|
724
|
+
return paramResult;
|
|
725
|
+
}
|
|
726
|
+
if (schema.type === "string" ||
|
|
727
|
+
schema.type === "integer" ||
|
|
728
|
+
schema.type === "boolean" ||
|
|
729
|
+
schema.type === "number") {
|
|
730
|
+
if (isRequiredWithoutDefault) {
|
|
731
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (schema.type === "object") {
|
|
738
|
+
const { properties } = schema;
|
|
739
|
+
for (const property in properties) {
|
|
740
|
+
let isRequired = false;
|
|
741
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
742
|
+
isRequired = true;
|
|
920
743
|
}
|
|
744
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
745
|
+
paramResult.requiredNum += result.requiredNum;
|
|
746
|
+
paramResult.optionalNum += result.optionalNum;
|
|
747
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
748
|
+
paramResult.reason.push(...result.reason);
|
|
921
749
|
}
|
|
922
|
-
newSpec.paths = newPaths;
|
|
923
|
-
return newSpec;
|
|
924
750
|
}
|
|
925
|
-
|
|
926
|
-
|
|
751
|
+
else {
|
|
752
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
753
|
+
paramResult.isValid = false;
|
|
754
|
+
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return paramResult;
|
|
758
|
+
}
|
|
759
|
+
checkParamSchema(paramObject) {
|
|
760
|
+
const paramResult = {
|
|
761
|
+
requiredNum: 0,
|
|
762
|
+
optionalNum: 0,
|
|
763
|
+
isValid: true,
|
|
764
|
+
reason: [],
|
|
765
|
+
};
|
|
766
|
+
if (!paramObject) {
|
|
767
|
+
return paramResult;
|
|
768
|
+
}
|
|
769
|
+
const isCopilot = this.projectType === exports.ProjectType.Copilot;
|
|
770
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
771
|
+
const param = paramObject[i];
|
|
772
|
+
const schema = param.schema;
|
|
773
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
774
|
+
paramResult.isValid = false;
|
|
775
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
779
|
+
if (isCopilot) {
|
|
780
|
+
if (isRequiredWithoutDefault) {
|
|
781
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
785
|
+
}
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
789
|
+
if (isRequiredWithoutDefault) {
|
|
790
|
+
paramResult.isValid = false;
|
|
791
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
792
|
+
}
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (schema.type !== "boolean" &&
|
|
796
|
+
schema.type !== "string" &&
|
|
797
|
+
schema.type !== "number" &&
|
|
798
|
+
schema.type !== "integer") {
|
|
799
|
+
if (isRequiredWithoutDefault) {
|
|
800
|
+
paramResult.isValid = false;
|
|
801
|
+
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
802
|
+
}
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (param.in === "query" || param.in === "path") {
|
|
806
|
+
if (isRequiredWithoutDefault) {
|
|
807
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
927
813
|
}
|
|
814
|
+
return paramResult;
|
|
815
|
+
}
|
|
816
|
+
hasNestedObjectInSchema(schema) {
|
|
817
|
+
if (schema.type === "object") {
|
|
818
|
+
for (const property in schema.properties) {
|
|
819
|
+
const nestedSchema = schema.properties[property];
|
|
820
|
+
if (nestedSchema.type === "object") {
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return false;
|
|
928
826
|
}
|
|
929
827
|
}
|
|
930
828
|
|
|
931
829
|
// Copyright (c) Microsoft Corporation.
|
|
932
|
-
class
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
{
|
|
939
|
-
pluginFile: apiPluginRelativePath,
|
|
940
|
-
},
|
|
941
|
-
];
|
|
942
|
-
const appName = this.removeEnvs(manifest.name.short);
|
|
943
|
-
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
944
|
-
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
945
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
946
|
-
return [manifest, apiPlugin];
|
|
947
|
-
});
|
|
830
|
+
class CopilotValidator extends Validator {
|
|
831
|
+
constructor(spec, options) {
|
|
832
|
+
super();
|
|
833
|
+
this.projectType = exports.ProjectType.Copilot;
|
|
834
|
+
this.options = options;
|
|
835
|
+
this.spec = spec;
|
|
948
836
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
837
|
+
validateSpec() {
|
|
838
|
+
const result = { errors: [], warnings: [] };
|
|
839
|
+
// validate spec version
|
|
840
|
+
let validationResult = this.validateSpecVersion();
|
|
841
|
+
result.errors.push(...validationResult.errors);
|
|
842
|
+
// validate spec server
|
|
843
|
+
validationResult = this.validateSpecServer();
|
|
844
|
+
result.errors.push(...validationResult.errors);
|
|
845
|
+
// validate no supported API
|
|
846
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
847
|
+
result.errors.push(...validationResult.errors);
|
|
848
|
+
// validate operationId missing
|
|
849
|
+
validationResult = this.validateSpecOperationId();
|
|
850
|
+
result.warnings.push(...validationResult.warnings);
|
|
851
|
+
return result;
|
|
955
852
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
853
|
+
validateAPI(method, path) {
|
|
854
|
+
const result = { isValid: true, reason: [] };
|
|
855
|
+
method = method.toLocaleLowerCase();
|
|
856
|
+
// validate method and path
|
|
857
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
858
|
+
if (!methodAndPathResult.isValid) {
|
|
859
|
+
return methodAndPathResult;
|
|
860
|
+
}
|
|
861
|
+
const operationObject = this.spec.paths[path][method];
|
|
862
|
+
// validate auth
|
|
863
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
864
|
+
result.reason.push(...authCheckResult.reason);
|
|
865
|
+
// validate operationId
|
|
866
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
867
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
964
868
|
}
|
|
965
|
-
|
|
966
|
-
|
|
869
|
+
// validate server
|
|
870
|
+
const validateServerResult = this.validateServer(method, path);
|
|
871
|
+
result.reason.push(...validateServerResult.reason);
|
|
872
|
+
// validate response
|
|
873
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
874
|
+
result.reason.push(...validateResponseResult.reason);
|
|
875
|
+
// validate requestBody
|
|
876
|
+
const requestBody = operationObject.requestBody;
|
|
877
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
878
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
879
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
967
880
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const functions = [];
|
|
973
|
-
const functionNames = [];
|
|
974
|
-
const paths = spec.paths;
|
|
975
|
-
for (const pathUrl in paths) {
|
|
976
|
-
const pathItem = paths[pathUrl];
|
|
977
|
-
if (pathItem) {
|
|
978
|
-
const operations = pathItem;
|
|
979
|
-
for (const method in operations) {
|
|
980
|
-
if (options.allowMethods.includes(method)) {
|
|
981
|
-
const operationItem = operations[method];
|
|
982
|
-
if (operationItem) {
|
|
983
|
-
const operationId = operationItem.operationId;
|
|
984
|
-
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
985
|
-
const paramObject = operationItem.parameters;
|
|
986
|
-
const requestBody = operationItem.requestBody;
|
|
987
|
-
const parameters = {
|
|
988
|
-
type: "object",
|
|
989
|
-
properties: {},
|
|
990
|
-
required: [],
|
|
991
|
-
};
|
|
992
|
-
if (paramObject) {
|
|
993
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
994
|
-
const param = paramObject[i];
|
|
995
|
-
const schema = param.schema;
|
|
996
|
-
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
997
|
-
if (param.required) {
|
|
998
|
-
parameters.required.push(param.name);
|
|
999
|
-
}
|
|
1000
|
-
if (!parameters.properties[param.name].description) {
|
|
1001
|
-
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
if (requestBody) {
|
|
1006
|
-
const requestJsonBody = requestBody.content["application/json"];
|
|
1007
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
1008
|
-
if (requestBodySchema.type === "object") {
|
|
1009
|
-
if (requestBodySchema.required) {
|
|
1010
|
-
parameters.required.push(...requestBodySchema.required);
|
|
1011
|
-
}
|
|
1012
|
-
for (const property in requestBodySchema.properties) {
|
|
1013
|
-
const schema = requestBodySchema.properties[property];
|
|
1014
|
-
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
else {
|
|
1018
|
-
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
const funcObj = {
|
|
1022
|
-
name: operationId,
|
|
1023
|
-
description: description,
|
|
1024
|
-
parameters: parameters,
|
|
1025
|
-
};
|
|
1026
|
-
functions.push(funcObj);
|
|
1027
|
-
functionNames.push(operationId);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
881
|
+
if (requestJsonBody) {
|
|
882
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
883
|
+
if (requestBodySchema.type !== "object") {
|
|
884
|
+
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
|
|
1031
885
|
}
|
|
886
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
887
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
1032
888
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
889
|
+
// validate parameters
|
|
890
|
+
const paramObject = operationObject.parameters;
|
|
891
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
892
|
+
result.reason.push(...paramResult.reason);
|
|
893
|
+
if (result.reason.length > 0) {
|
|
894
|
+
result.isValid = false;
|
|
895
|
+
}
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Copyright (c) Microsoft Corporation.
|
|
901
|
+
class SMEValidator extends Validator {
|
|
902
|
+
constructor(spec, options) {
|
|
903
|
+
super();
|
|
904
|
+
this.projectType = exports.ProjectType.SME;
|
|
905
|
+
this.options = options;
|
|
906
|
+
this.spec = spec;
|
|
907
|
+
}
|
|
908
|
+
validateSpec() {
|
|
909
|
+
const result = { errors: [], warnings: [] };
|
|
910
|
+
// validate spec version
|
|
911
|
+
let validationResult = this.validateSpecVersion();
|
|
912
|
+
result.errors.push(...validationResult.errors);
|
|
913
|
+
// validate spec server
|
|
914
|
+
validationResult = this.validateSpecServer();
|
|
915
|
+
result.errors.push(...validationResult.errors);
|
|
916
|
+
// validate no supported API
|
|
917
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
918
|
+
result.errors.push(...validationResult.errors);
|
|
919
|
+
// validate operationId missing
|
|
920
|
+
if (this.options.allowMissingId) {
|
|
921
|
+
validationResult = this.validateSpecOperationId();
|
|
922
|
+
result.warnings.push(...validationResult.warnings);
|
|
923
|
+
}
|
|
924
|
+
return result;
|
|
925
|
+
}
|
|
926
|
+
validateAPI(method, path) {
|
|
927
|
+
const result = { isValid: true, reason: [] };
|
|
928
|
+
method = method.toLocaleLowerCase();
|
|
929
|
+
// validate method and path
|
|
930
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
931
|
+
if (!methodAndPathResult.isValid) {
|
|
932
|
+
return methodAndPathResult;
|
|
933
|
+
}
|
|
934
|
+
const operationObject = this.spec.paths[path][method];
|
|
935
|
+
// validate auth
|
|
936
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
937
|
+
result.reason.push(...authCheckResult.reason);
|
|
938
|
+
// validate operationId
|
|
939
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
940
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
941
|
+
}
|
|
942
|
+
// validate server
|
|
943
|
+
const validateServerResult = this.validateServer(method, path);
|
|
944
|
+
result.reason.push(...validateServerResult.reason);
|
|
945
|
+
// validate response
|
|
946
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
947
|
+
result.reason.push(...validateResponseResult.reason);
|
|
948
|
+
let postBodyResult = {
|
|
949
|
+
requiredNum: 0,
|
|
950
|
+
optionalNum: 0,
|
|
951
|
+
isValid: true,
|
|
952
|
+
reason: [],
|
|
1050
953
|
};
|
|
1051
|
-
|
|
954
|
+
// validate requestBody
|
|
955
|
+
const requestBody = operationObject.requestBody;
|
|
956
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
957
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
958
|
+
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
|
|
959
|
+
}
|
|
960
|
+
if (requestJsonBody) {
|
|
961
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
962
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
963
|
+
result.reason.push(...postBodyResult.reason);
|
|
964
|
+
}
|
|
965
|
+
// validate parameters
|
|
966
|
+
const paramObject = operationObject.parameters;
|
|
967
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
968
|
+
result.reason.push(...paramResult.reason);
|
|
969
|
+
// validate total parameters count
|
|
970
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
971
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
972
|
+
result.reason.push(...paramCountResult.reason);
|
|
973
|
+
}
|
|
974
|
+
if (result.reason.length > 0) {
|
|
975
|
+
result.isValid = false;
|
|
976
|
+
}
|
|
977
|
+
return result;
|
|
1052
978
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const updateResult = yield ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1062
|
-
const commands = updateResult[0];
|
|
1063
|
-
warnings = updateResult[1];
|
|
1064
|
-
const composeExtension = {
|
|
1065
|
-
composeExtensionType: "apiBased",
|
|
1066
|
-
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1067
|
-
commands: commands,
|
|
1068
|
-
};
|
|
1069
|
-
if (authInfo) {
|
|
1070
|
-
const auth = authInfo.authScheme;
|
|
1071
|
-
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1072
|
-
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1073
|
-
composeExtension.authorization = {
|
|
1074
|
-
authType: "apiSecretServiceAuth",
|
|
1075
|
-
apiSecretServiceAuthConfiguration: {
|
|
1076
|
-
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1077
|
-
},
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1081
|
-
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1082
|
-
composeExtension.authorization = {
|
|
1083
|
-
authType: "oAuth2.0",
|
|
1084
|
-
oAuthConfiguration: {
|
|
1085
|
-
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1086
|
-
},
|
|
1087
|
-
};
|
|
1088
|
-
updatedPart.webApplicationInfo = {
|
|
1089
|
-
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1090
|
-
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
updatedPart.composeExtensions = [composeExtension];
|
|
1095
|
-
}
|
|
1096
|
-
updatedPart.description = originalManifest.description;
|
|
1097
|
-
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
1098
|
-
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
1099
|
-
return [updatedManifest, warnings];
|
|
1100
|
-
}
|
|
1101
|
-
catch (err) {
|
|
1102
|
-
throw new SpecParserError(err.toString(), exports.ErrorType.UpdateManifestFailed);
|
|
979
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
980
|
+
const result = { isValid: true, reason: [] };
|
|
981
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
982
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
983
|
+
if (totalRequiredParams > 1) {
|
|
984
|
+
if (!this.options.allowMultipleParameters ||
|
|
985
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
986
|
+
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
|
|
1103
987
|
}
|
|
1104
|
-
}
|
|
988
|
+
}
|
|
989
|
+
else if (totalParams === 0) {
|
|
990
|
+
result.reason.push(exports.ErrorType.NoParameter);
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
1105
993
|
}
|
|
1106
|
-
|
|
994
|
+
}
|
|
995
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
996
|
+
|
|
997
|
+
// Copyright (c) Microsoft Corporation.
|
|
998
|
+
class TeamsAIValidator extends Validator {
|
|
999
|
+
constructor(spec, options) {
|
|
1000
|
+
super();
|
|
1001
|
+
this.projectType = exports.ProjectType.TeamsAi;
|
|
1002
|
+
this.options = options;
|
|
1003
|
+
this.spec = spec;
|
|
1004
|
+
}
|
|
1005
|
+
validateSpec() {
|
|
1006
|
+
const result = { errors: [], warnings: [] };
|
|
1007
|
+
// validate spec server
|
|
1008
|
+
let validationResult = this.validateSpecServer();
|
|
1009
|
+
result.errors.push(...validationResult.errors);
|
|
1010
|
+
// validate no supported API
|
|
1011
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1012
|
+
result.errors.push(...validationResult.errors);
|
|
1013
|
+
return result;
|
|
1014
|
+
}
|
|
1015
|
+
validateAPI(method, path) {
|
|
1016
|
+
const result = { isValid: true, reason: [] };
|
|
1017
|
+
method = method.toLocaleLowerCase();
|
|
1018
|
+
// validate method and path
|
|
1019
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1020
|
+
if (!methodAndPathResult.isValid) {
|
|
1021
|
+
return methodAndPathResult;
|
|
1022
|
+
}
|
|
1023
|
+
const operationObject = this.spec.paths[path][method];
|
|
1024
|
+
// validate operationId
|
|
1025
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1026
|
+
result.reason.push(exports.ErrorType.MissingOperationId);
|
|
1027
|
+
}
|
|
1028
|
+
// validate server
|
|
1029
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1030
|
+
result.reason.push(...validateServerResult.reason);
|
|
1031
|
+
if (result.reason.length > 0) {
|
|
1032
|
+
result.isValid = false;
|
|
1033
|
+
}
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
class ValidatorFactory {
|
|
1039
|
+
static create(spec, options) {
|
|
1107
1040
|
var _a;
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1041
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : exports.ProjectType.SME;
|
|
1042
|
+
switch (type) {
|
|
1043
|
+
case exports.ProjectType.SME:
|
|
1044
|
+
return new SMEValidator(spec, options);
|
|
1045
|
+
case exports.ProjectType.Copilot:
|
|
1046
|
+
return new CopilotValidator(spec, options);
|
|
1047
|
+
case exports.ProjectType.TeamsAi:
|
|
1048
|
+
return new TeamsAIValidator(spec, options);
|
|
1049
|
+
default:
|
|
1050
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Copyright (c) Microsoft Corporation.
|
|
1056
|
+
class SpecFilter {
|
|
1057
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1058
|
+
var _a;
|
|
1059
|
+
try {
|
|
1060
|
+
const newSpec = Object.assign({}, unResolveSpec);
|
|
1061
|
+
const newPaths = {};
|
|
1062
|
+
for (const filterItem of filter) {
|
|
1063
|
+
const [method, path] = filterItem.split(" ");
|
|
1064
|
+
const methodName = method.toLowerCase();
|
|
1065
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1066
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1067
|
+
pathObj &&
|
|
1068
|
+
pathObj[methodName]) {
|
|
1069
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1070
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1071
|
+
if (!validateResult.isValid) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
if (!newPaths[path]) {
|
|
1075
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1076
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1077
|
+
delete newPaths[path][m];
|
|
1135
1078
|
}
|
|
1136
1079
|
}
|
|
1080
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1081
|
+
// Add the operationId if missing
|
|
1082
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1083
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
1084
|
+
}
|
|
1137
1085
|
}
|
|
1138
1086
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
|
|
1145
|
-
}
|
|
1146
|
-
static removeEnvs(str) {
|
|
1147
|
-
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1148
|
-
const matches = placeHolderReg.exec(str);
|
|
1149
|
-
let newStr = str;
|
|
1150
|
-
if (matches != null) {
|
|
1151
|
-
newStr = newStr.replace(matches[0], "");
|
|
1087
|
+
newSpec.paths = newPaths;
|
|
1088
|
+
return newSpec;
|
|
1089
|
+
}
|
|
1090
|
+
catch (err) {
|
|
1091
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.FilterSpecFailed);
|
|
1152
1092
|
}
|
|
1153
|
-
return newStr;
|
|
1154
1093
|
}
|
|
1155
1094
|
}
|
|
1156
1095
|
|
|
@@ -1324,6 +1263,27 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1324
1263
|
};
|
|
1325
1264
|
return result;
|
|
1326
1265
|
}
|
|
1266
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1267
|
+
const props = inferProperties(card);
|
|
1268
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1269
|
+
const result = {
|
|
1270
|
+
data_path: dataPath,
|
|
1271
|
+
};
|
|
1272
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1273
|
+
result.properties = {};
|
|
1274
|
+
if (props.title) {
|
|
1275
|
+
result.properties.title = "$." + props.title;
|
|
1276
|
+
}
|
|
1277
|
+
if (props.subtitle) {
|
|
1278
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1279
|
+
}
|
|
1280
|
+
if (props.imageUrl) {
|
|
1281
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
result.static_template = card;
|
|
1285
|
+
return result;
|
|
1286
|
+
}
|
|
1327
1287
|
/**
|
|
1328
1288
|
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1329
1289
|
* The preview card template includes a title and an optional subtitle and image.
|
|
@@ -1336,11 +1296,29 @@ function wrapAdaptiveCard(card, jsonPath) {
|
|
|
1336
1296
|
* @returns The inferred preview card template.
|
|
1337
1297
|
*/
|
|
1338
1298
|
function inferPreviewCardTemplate(card) {
|
|
1339
|
-
var _a;
|
|
1340
1299
|
const result = {
|
|
1341
|
-
title: "",
|
|
1300
|
+
title: "result",
|
|
1342
1301
|
};
|
|
1343
|
-
const
|
|
1302
|
+
const inferredProperties = inferProperties(card);
|
|
1303
|
+
if (inferredProperties.title) {
|
|
1304
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1305
|
+
}
|
|
1306
|
+
if (inferredProperties.subtitle) {
|
|
1307
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1308
|
+
}
|
|
1309
|
+
if (inferredProperties.imageUrl) {
|
|
1310
|
+
result.image = {
|
|
1311
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1312
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1313
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
return result;
|
|
1317
|
+
}
|
|
1318
|
+
function inferProperties(card) {
|
|
1319
|
+
var _a;
|
|
1320
|
+
const result = {};
|
|
1321
|
+
const nameSet = new Set();
|
|
1344
1322
|
let rootObject;
|
|
1345
1323
|
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1346
1324
|
rootObject = card.body[0].items;
|
|
@@ -1353,56 +1331,346 @@ function inferPreviewCardTemplate(card) {
|
|
|
1353
1331
|
const textElement = element;
|
|
1354
1332
|
const index = textElement.text.indexOf("${if(");
|
|
1355
1333
|
if (index > 0) {
|
|
1356
|
-
|
|
1357
|
-
|
|
1334
|
+
const text = textElement.text.substring(index);
|
|
1335
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1336
|
+
const property = match ? match[1] : "";
|
|
1337
|
+
if (property) {
|
|
1338
|
+
nameSet.add(property);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1343
|
+
const imageElement = element;
|
|
1344
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1345
|
+
const property = match ? match[1] : "";
|
|
1346
|
+
if (property) {
|
|
1347
|
+
nameSet.add(property);
|
|
1358
1348
|
}
|
|
1359
1349
|
}
|
|
1360
1350
|
}
|
|
1361
|
-
for (const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
textBlockElements.delete(element);
|
|
1351
|
+
for (const name of nameSet) {
|
|
1352
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1353
|
+
result.title = name;
|
|
1354
|
+
nameSet.delete(name);
|
|
1366
1355
|
}
|
|
1367
1356
|
else if (!result.subtitle &&
|
|
1368
|
-
Utils.isWellKnownName(
|
|
1369
|
-
result.subtitle =
|
|
1370
|
-
|
|
1357
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1358
|
+
result.subtitle = name;
|
|
1359
|
+
nameSet.delete(name);
|
|
1371
1360
|
}
|
|
1372
|
-
else if (!result.
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (property) {
|
|
1376
|
-
result.image = {
|
|
1377
|
-
url: `\${${property}}`,
|
|
1378
|
-
alt: text,
|
|
1379
|
-
$when: `\${${property} != null}`,
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
textBlockElements.delete(element);
|
|
1361
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1362
|
+
result.imageUrl = name;
|
|
1363
|
+
nameSet.delete(name);
|
|
1383
1364
|
}
|
|
1384
1365
|
}
|
|
1385
|
-
for (const
|
|
1386
|
-
const text = element.text;
|
|
1366
|
+
for (const name of nameSet) {
|
|
1387
1367
|
if (!result.title) {
|
|
1388
|
-
result.title =
|
|
1389
|
-
|
|
1368
|
+
result.title = name;
|
|
1369
|
+
nameSet.delete(name);
|
|
1390
1370
|
}
|
|
1391
1371
|
else if (!result.subtitle) {
|
|
1392
|
-
result.subtitle =
|
|
1393
|
-
|
|
1372
|
+
result.subtitle = name;
|
|
1373
|
+
nameSet.delete(name);
|
|
1394
1374
|
}
|
|
1395
1375
|
}
|
|
1396
1376
|
if (!result.title && result.subtitle) {
|
|
1397
1377
|
result.title = result.subtitle;
|
|
1398
1378
|
delete result.subtitle;
|
|
1399
1379
|
}
|
|
1400
|
-
if (!result.title) {
|
|
1401
|
-
result.title = "result";
|
|
1402
|
-
}
|
|
1403
1380
|
return result;
|
|
1404
1381
|
}
|
|
1405
1382
|
|
|
1383
|
+
// Copyright (c) Microsoft Corporation.
|
|
1384
|
+
class ManifestUpdater {
|
|
1385
|
+
static updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
|
|
1386
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1387
|
+
const manifest = yield fs__default['default'].readJSON(manifestPath);
|
|
1388
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1389
|
+
manifest.plugins = [
|
|
1390
|
+
{
|
|
1391
|
+
file: apiPluginRelativePath,
|
|
1392
|
+
id: ConstantString.DefaultPluginId,
|
|
1393
|
+
},
|
|
1394
|
+
];
|
|
1395
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1396
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1397
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1398
|
+
const apiPlugin = yield ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
|
|
1399
|
+
return [manifest, apiPlugin];
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
static updateManifestDescription(manifest, spec) {
|
|
1403
|
+
var _a, _b;
|
|
1404
|
+
manifest.description = {
|
|
1405
|
+
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
|
|
1406
|
+
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),
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
1410
|
+
let parameter;
|
|
1411
|
+
if (schema.type === "string" ||
|
|
1412
|
+
schema.type === "boolean" ||
|
|
1413
|
+
schema.type === "integer" ||
|
|
1414
|
+
schema.type === "number" ||
|
|
1415
|
+
schema.type === "array") {
|
|
1416
|
+
parameter = schema;
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), exports.ErrorType.UpdateManifestFailed);
|
|
1420
|
+
}
|
|
1421
|
+
return parameter;
|
|
1422
|
+
}
|
|
1423
|
+
static generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
|
|
1424
|
+
var _a, _b, _c, _d;
|
|
1425
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1426
|
+
const functions = [];
|
|
1427
|
+
const functionNames = [];
|
|
1428
|
+
const conversationStarters = [];
|
|
1429
|
+
const paths = spec.paths;
|
|
1430
|
+
for (const pathUrl in paths) {
|
|
1431
|
+
const pathItem = paths[pathUrl];
|
|
1432
|
+
if (pathItem) {
|
|
1433
|
+
const operations = pathItem;
|
|
1434
|
+
for (const method in operations) {
|
|
1435
|
+
if (options.allowMethods.includes(method)) {
|
|
1436
|
+
const operationItem = operations[method];
|
|
1437
|
+
if (operationItem) {
|
|
1438
|
+
const operationId = operationItem.operationId;
|
|
1439
|
+
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
1440
|
+
const paramObject = operationItem.parameters;
|
|
1441
|
+
const requestBody = operationItem.requestBody;
|
|
1442
|
+
const parameters = {
|
|
1443
|
+
type: "object",
|
|
1444
|
+
properties: {},
|
|
1445
|
+
required: [],
|
|
1446
|
+
};
|
|
1447
|
+
if (paramObject) {
|
|
1448
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
1449
|
+
const param = paramObject[i];
|
|
1450
|
+
const schema = param.schema;
|
|
1451
|
+
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1452
|
+
if (param.required) {
|
|
1453
|
+
parameters.required.push(param.name);
|
|
1454
|
+
}
|
|
1455
|
+
if (!parameters.properties[param.name].description) {
|
|
1456
|
+
parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (requestBody) {
|
|
1461
|
+
const requestJsonBody = requestBody.content["application/json"];
|
|
1462
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
1463
|
+
if (requestBodySchema.type === "object") {
|
|
1464
|
+
if (requestBodySchema.required) {
|
|
1465
|
+
parameters.required.push(...requestBodySchema.required);
|
|
1466
|
+
}
|
|
1467
|
+
for (const property in requestBodySchema.properties) {
|
|
1468
|
+
const schema = requestBodySchema.properties[property];
|
|
1469
|
+
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const funcObj = {
|
|
1477
|
+
name: operationId,
|
|
1478
|
+
description: description,
|
|
1479
|
+
parameters: parameters,
|
|
1480
|
+
};
|
|
1481
|
+
if (options.allowResponseSemantics) {
|
|
1482
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1483
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1484
|
+
funcObj.capabilities = {
|
|
1485
|
+
response_semantics: responseSemantic,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
functions.push(funcObj);
|
|
1489
|
+
functionNames.push(operationId);
|
|
1490
|
+
if (description) {
|
|
1491
|
+
conversationStarters.push(description);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
let apiPlugin;
|
|
1499
|
+
if (yield fs__default['default'].pathExists(apiPluginFilePath)) {
|
|
1500
|
+
apiPlugin = yield fs__default['default'].readJSON(apiPluginFilePath);
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
apiPlugin = {
|
|
1504
|
+
schema_version: "v2",
|
|
1505
|
+
name_for_human: "",
|
|
1506
|
+
description_for_human: "",
|
|
1507
|
+
functions: [],
|
|
1508
|
+
runtimes: [],
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1512
|
+
for (const func of functions) {
|
|
1513
|
+
const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
|
|
1514
|
+
if (index === -1) {
|
|
1515
|
+
apiPlugin.functions.push(func);
|
|
1516
|
+
}
|
|
1517
|
+
else {
|
|
1518
|
+
apiPlugin.functions[index] = func;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1522
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath);
|
|
1523
|
+
if (index === -1) {
|
|
1524
|
+
apiPlugin.runtimes.push({
|
|
1525
|
+
type: "OpenApi",
|
|
1526
|
+
auth: {
|
|
1527
|
+
type: "none",
|
|
1528
|
+
},
|
|
1529
|
+
spec: {
|
|
1530
|
+
url: specRelativePath,
|
|
1531
|
+
},
|
|
1532
|
+
run_for_functions: functionNames,
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
else {
|
|
1536
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1537
|
+
}
|
|
1538
|
+
if (!apiPlugin.name_for_human) {
|
|
1539
|
+
apiPlugin.name_for_human = appName;
|
|
1540
|
+
}
|
|
1541
|
+
if (!apiPlugin.description_for_human) {
|
|
1542
|
+
apiPlugin.description_for_human =
|
|
1543
|
+
(_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
|
|
1544
|
+
}
|
|
1545
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1546
|
+
if (!apiPlugin.capabilities) {
|
|
1547
|
+
apiPlugin.capabilities = {
|
|
1548
|
+
localization: {},
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1552
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1553
|
+
.slice(0, 5)
|
|
1554
|
+
.map((text) => ({ text }));
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return apiPlugin;
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
static updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
1561
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1562
|
+
try {
|
|
1563
|
+
const originalManifest = yield fs__default['default'].readJSON(manifestPath);
|
|
1564
|
+
const updatedPart = {};
|
|
1565
|
+
updatedPart.composeExtensions = [];
|
|
1566
|
+
let warnings = [];
|
|
1567
|
+
if (options.projectType === exports.ProjectType.SME) {
|
|
1568
|
+
const updateResult = yield ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
|
|
1569
|
+
const commands = updateResult[0];
|
|
1570
|
+
warnings = updateResult[1];
|
|
1571
|
+
const composeExtension = {
|
|
1572
|
+
composeExtensionType: "apiBased",
|
|
1573
|
+
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
|
|
1574
|
+
commands: commands,
|
|
1575
|
+
};
|
|
1576
|
+
if (authInfo) {
|
|
1577
|
+
const auth = authInfo.authScheme;
|
|
1578
|
+
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
1579
|
+
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1580
|
+
composeExtension.authorization = {
|
|
1581
|
+
authType: "apiSecretServiceAuth",
|
|
1582
|
+
apiSecretServiceAuthConfiguration: {
|
|
1583
|
+
apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
|
|
1584
|
+
},
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
1588
|
+
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
1589
|
+
composeExtension.authorization = {
|
|
1590
|
+
authType: "oAuth2.0",
|
|
1591
|
+
oAuthConfiguration: {
|
|
1592
|
+
oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
|
|
1593
|
+
},
|
|
1594
|
+
};
|
|
1595
|
+
updatedPart.webApplicationInfo = {
|
|
1596
|
+
id: "${{AAD_APP_CLIENT_ID}}",
|
|
1597
|
+
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
updatedPart.composeExtensions = [composeExtension];
|
|
1602
|
+
}
|
|
1603
|
+
updatedPart.description = originalManifest.description;
|
|
1604
|
+
ManifestUpdater.updateManifestDescription(updatedPart, spec);
|
|
1605
|
+
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
|
|
1606
|
+
return [updatedManifest, warnings];
|
|
1607
|
+
}
|
|
1608
|
+
catch (err) {
|
|
1609
|
+
throw new SpecParserError(err.toString(), exports.ErrorType.UpdateManifestFailed);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
static generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
|
|
1614
|
+
var _a;
|
|
1615
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1616
|
+
const paths = spec.paths;
|
|
1617
|
+
const commands = [];
|
|
1618
|
+
const warnings = [];
|
|
1619
|
+
if (paths) {
|
|
1620
|
+
for (const pathUrl in paths) {
|
|
1621
|
+
const pathItem = paths[pathUrl];
|
|
1622
|
+
if (pathItem) {
|
|
1623
|
+
const operations = pathItem;
|
|
1624
|
+
// Currently only support GET and POST method
|
|
1625
|
+
for (const method in operations) {
|
|
1626
|
+
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1627
|
+
const operationItem = operations[method];
|
|
1628
|
+
if (operationItem) {
|
|
1629
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1630
|
+
if (command.parameters &&
|
|
1631
|
+
command.parameters.length >= 1 &&
|
|
1632
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1633
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1634
|
+
}
|
|
1635
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1636
|
+
command.parameters = [command.parameters[0]];
|
|
1637
|
+
warnings.push({
|
|
1638
|
+
type: exports.WarningType.OperationOnlyContainsOptionalParam,
|
|
1639
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1640
|
+
data: command.id,
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
if (adaptiveCardFolder) {
|
|
1644
|
+
const adaptiveCardPath = path__default['default'].join(adaptiveCardFolder, command.id + ".json");
|
|
1645
|
+
command.apiResponseRenderingTemplateFile = (yield fs__default['default'].pathExists(adaptiveCardPath))
|
|
1646
|
+
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1647
|
+
: "";
|
|
1648
|
+
}
|
|
1649
|
+
commands.push(command);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return [commands, warnings];
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
static getRelativePath(from, to) {
|
|
1660
|
+
const relativePath = path__default['default'].relative(path__default['default'].dirname(from), to);
|
|
1661
|
+
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
|
|
1662
|
+
}
|
|
1663
|
+
static removeEnvs(str) {
|
|
1664
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1665
|
+
const matches = placeHolderReg.exec(str);
|
|
1666
|
+
let newStr = str;
|
|
1667
|
+
if (matches != null) {
|
|
1668
|
+
newStr = newStr.replace(matches[0], "");
|
|
1669
|
+
}
|
|
1670
|
+
return newStr;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1406
1674
|
// Copyright (c) Microsoft Corporation.
|
|
1407
1675
|
/**
|
|
1408
1676
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1422,6 +1690,8 @@ class SpecParser {
|
|
|
1422
1690
|
allowMultipleParameters: false,
|
|
1423
1691
|
allowOauth2: false,
|
|
1424
1692
|
allowMethods: ["get", "post"],
|
|
1693
|
+
allowConversationStarters: false,
|
|
1694
|
+
allowResponseSemantics: false,
|
|
1425
1695
|
projectType: exports.ProjectType.SME,
|
|
1426
1696
|
};
|
|
1427
1697
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -1447,6 +1717,8 @@ class SpecParser {
|
|
|
1447
1717
|
errors: [{ type: exports.ErrorType.SpecNotValid, content: e.toString() }],
|
|
1448
1718
|
};
|
|
1449
1719
|
}
|
|
1720
|
+
const errors = [];
|
|
1721
|
+
const warnings = [];
|
|
1450
1722
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1451
1723
|
return {
|
|
1452
1724
|
status: exports.ValidationStatus.Error,
|
|
@@ -1456,23 +1728,38 @@ class SpecParser {
|
|
|
1456
1728
|
],
|
|
1457
1729
|
};
|
|
1458
1730
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1731
|
+
// Remote reference not supported
|
|
1732
|
+
const refPaths = this.parser.$refs.paths();
|
|
1733
|
+
// refPaths [0] is the current spec file path
|
|
1734
|
+
if (refPaths.length > 1) {
|
|
1735
|
+
errors.push({
|
|
1736
|
+
type: exports.ErrorType.RemoteRefNotSupported,
|
|
1737
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1738
|
+
data: refPaths,
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1742
|
+
warnings.push({
|
|
1743
|
+
type: exports.WarningType.ConvertSwaggerToOpenAPI,
|
|
1744
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
const validator = this.getValidator(this.spec);
|
|
1748
|
+
const validationResult = validator.validateSpec();
|
|
1749
|
+
warnings.push(...validationResult.warnings);
|
|
1750
|
+
errors.push(...validationResult.errors);
|
|
1751
|
+
let status = exports.ValidationStatus.Valid;
|
|
1752
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1753
|
+
status = exports.ValidationStatus.Warning;
|
|
1474
1754
|
}
|
|
1475
|
-
|
|
1755
|
+
else if (errors.length > 0) {
|
|
1756
|
+
status = exports.ValidationStatus.Error;
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
status: status,
|
|
1760
|
+
warnings: warnings,
|
|
1761
|
+
errors: errors,
|
|
1762
|
+
};
|
|
1476
1763
|
}
|
|
1477
1764
|
catch (err) {
|
|
1478
1765
|
throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
|
|
@@ -1505,34 +1792,27 @@ class SpecParser {
|
|
|
1505
1792
|
for (const apiKey in apiMap) {
|
|
1506
1793
|
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1507
1794
|
const [method, path] = apiKey.split(" ");
|
|
1795
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1508
1796
|
const apiResult = {
|
|
1509
|
-
api:
|
|
1797
|
+
api: apiKey,
|
|
1510
1798
|
server: "",
|
|
1511
|
-
operationId:
|
|
1799
|
+
operationId: operationId,
|
|
1512
1800
|
isValid: isValid,
|
|
1513
1801
|
reason: reason,
|
|
1514
1802
|
};
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1527
|
-
apiResult.operationId = operationId;
|
|
1528
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1529
|
-
for (const auths of authArray) {
|
|
1530
|
-
if (auths.length === 1) {
|
|
1531
|
-
apiResult.auth = auths[0];
|
|
1532
|
-
break;
|
|
1803
|
+
if (isValid) {
|
|
1804
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1805
|
+
if (serverObj) {
|
|
1806
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1807
|
+
}
|
|
1808
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1809
|
+
for (const auths of authArray) {
|
|
1810
|
+
if (auths.length === 1) {
|
|
1811
|
+
apiResult.auth = auths[0];
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1533
1814
|
}
|
|
1534
1815
|
}
|
|
1535
|
-
apiResult.api = apiKey;
|
|
1536
1816
|
result.APIs.push(apiResult);
|
|
1537
1817
|
}
|
|
1538
1818
|
result.allAPICount = result.APIs.length;
|
|
@@ -1721,12 +2001,17 @@ class SpecParser {
|
|
|
1721
2001
|
});
|
|
1722
2002
|
}
|
|
1723
2003
|
getAPIs(spec) {
|
|
1724
|
-
|
|
1725
|
-
|
|
2004
|
+
const validator = this.getValidator(spec);
|
|
2005
|
+
const apiMap = validator.listAPIs();
|
|
2006
|
+
return apiMap;
|
|
2007
|
+
}
|
|
2008
|
+
getValidator(spec) {
|
|
2009
|
+
if (this.validator) {
|
|
2010
|
+
return this.validator;
|
|
1726
2011
|
}
|
|
1727
|
-
const
|
|
1728
|
-
this.
|
|
1729
|
-
return
|
|
2012
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
2013
|
+
this.validator = validator;
|
|
2014
|
+
return validator;
|
|
1730
2015
|
}
|
|
1731
2016
|
}
|
|
1732
2017
|
|