@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.esm2017.mjs
CHANGED
|
@@ -103,6 +103,7 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
103
103
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
104
104
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
105
105
|
ConstantString.TextBlockType = "TextBlock";
|
|
106
|
+
ConstantString.ImageType = "Image";
|
|
106
107
|
ConstantString.ContainerType = "Container";
|
|
107
108
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
108
109
|
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
@@ -166,7 +167,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
166
167
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
167
168
|
ConstantString.CommandTitleMaxLens = 32;
|
|
168
169
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
169
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
170
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
171
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
170
172
|
|
|
171
173
|
// Copyright (c) Microsoft Corporation.
|
|
172
174
|
class SpecParserError extends Error {
|
|
@@ -189,249 +191,9 @@ class Utils {
|
|
|
189
191
|
}
|
|
190
192
|
return false;
|
|
191
193
|
}
|
|
192
|
-
static checkParameters(paramObject, isCopilot) {
|
|
193
|
-
const paramResult = {
|
|
194
|
-
requiredNum: 0,
|
|
195
|
-
optionalNum: 0,
|
|
196
|
-
isValid: true,
|
|
197
|
-
reason: [],
|
|
198
|
-
};
|
|
199
|
-
if (!paramObject) {
|
|
200
|
-
return paramResult;
|
|
201
|
-
}
|
|
202
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
203
|
-
const param = paramObject[i];
|
|
204
|
-
const schema = param.schema;
|
|
205
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
206
|
-
paramResult.isValid = false;
|
|
207
|
-
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
211
|
-
if (isCopilot) {
|
|
212
|
-
if (isRequiredWithoutDefault) {
|
|
213
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
217
|
-
}
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
221
|
-
if (isRequiredWithoutDefault) {
|
|
222
|
-
paramResult.isValid = false;
|
|
223
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
224
|
-
}
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (schema.type !== "boolean" &&
|
|
228
|
-
schema.type !== "string" &&
|
|
229
|
-
schema.type !== "number" &&
|
|
230
|
-
schema.type !== "integer") {
|
|
231
|
-
if (isRequiredWithoutDefault) {
|
|
232
|
-
paramResult.isValid = false;
|
|
233
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
234
|
-
}
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (param.in === "query" || param.in === "path") {
|
|
238
|
-
if (isRequiredWithoutDefault) {
|
|
239
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return paramResult;
|
|
247
|
-
}
|
|
248
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
249
|
-
var _a;
|
|
250
|
-
const paramResult = {
|
|
251
|
-
requiredNum: 0,
|
|
252
|
-
optionalNum: 0,
|
|
253
|
-
isValid: true,
|
|
254
|
-
reason: [],
|
|
255
|
-
};
|
|
256
|
-
if (Object.keys(schema).length === 0) {
|
|
257
|
-
return paramResult;
|
|
258
|
-
}
|
|
259
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
260
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
261
|
-
paramResult.isValid = false;
|
|
262
|
-
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
263
|
-
return paramResult;
|
|
264
|
-
}
|
|
265
|
-
if (schema.type === "string" ||
|
|
266
|
-
schema.type === "integer" ||
|
|
267
|
-
schema.type === "boolean" ||
|
|
268
|
-
schema.type === "number") {
|
|
269
|
-
if (isRequiredWithoutDefault) {
|
|
270
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
else if (schema.type === "object") {
|
|
277
|
-
const { properties } = schema;
|
|
278
|
-
for (const property in properties) {
|
|
279
|
-
let isRequired = false;
|
|
280
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
281
|
-
isRequired = true;
|
|
282
|
-
}
|
|
283
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
284
|
-
paramResult.requiredNum += result.requiredNum;
|
|
285
|
-
paramResult.optionalNum += result.optionalNum;
|
|
286
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
287
|
-
paramResult.reason.push(...result.reason);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
292
|
-
paramResult.isValid = false;
|
|
293
|
-
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return paramResult;
|
|
297
|
-
}
|
|
298
194
|
static containMultipleMediaTypes(bodyObject) {
|
|
299
195
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
300
196
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Checks if the given API is supported.
|
|
303
|
-
* @param {string} method - The HTTP method of the API.
|
|
304
|
-
* @param {string} path - The path of the API.
|
|
305
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
306
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
307
|
-
* @description The following APIs are supported:
|
|
308
|
-
* 1. only support Get/Post operation without auth property
|
|
309
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
310
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
311
|
-
* 4. request body + required parameters <= 1
|
|
312
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
313
|
-
* 6. only support request body with “application/json” content type
|
|
314
|
-
*/
|
|
315
|
-
static isSupportedApi(method, path, spec, options) {
|
|
316
|
-
var _a;
|
|
317
|
-
const result = { isValid: true, reason: [] };
|
|
318
|
-
method = method.toLocaleLowerCase();
|
|
319
|
-
if (options.allowMethods && !options.allowMethods.includes(method)) {
|
|
320
|
-
result.isValid = false;
|
|
321
|
-
result.reason.push(ErrorType.MethodNotAllowed);
|
|
322
|
-
return result;
|
|
323
|
-
}
|
|
324
|
-
const pathObj = spec.paths[path];
|
|
325
|
-
if (!pathObj || !pathObj[method]) {
|
|
326
|
-
result.isValid = false;
|
|
327
|
-
result.reason.push(ErrorType.UrlPathNotExist);
|
|
328
|
-
return result;
|
|
329
|
-
}
|
|
330
|
-
const securities = pathObj[method].security;
|
|
331
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
332
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
333
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
334
|
-
if (!isTeamsAi) {
|
|
335
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
336
|
-
const authCheckResult = Utils.isSupportedAuth(authArray, options);
|
|
337
|
-
if (!authCheckResult.isValid) {
|
|
338
|
-
result.reason.push(...authCheckResult.reason);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
const operationObject = pathObj[method];
|
|
342
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
343
|
-
result.reason.push(ErrorType.MissingOperationId);
|
|
344
|
-
}
|
|
345
|
-
const rootServer = spec.servers && spec.servers[0];
|
|
346
|
-
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
347
|
-
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
348
|
-
const serverUrl = operationServer || methodServer || rootServer;
|
|
349
|
-
if (!serverUrl) {
|
|
350
|
-
result.reason.push(ErrorType.NoServerInformation);
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
354
|
-
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
355
|
-
}
|
|
356
|
-
const paramObject = operationObject.parameters;
|
|
357
|
-
const requestBody = operationObject.requestBody;
|
|
358
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
359
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
360
|
-
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
361
|
-
}
|
|
362
|
-
const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
363
|
-
if (multipleMediaType && !isTeamsAi) {
|
|
364
|
-
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
365
|
-
}
|
|
366
|
-
else if (Object.keys(json).length === 0) {
|
|
367
|
-
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
368
|
-
}
|
|
369
|
-
// Teams AI project doesn't care about request parameters/body
|
|
370
|
-
if (!isTeamsAi) {
|
|
371
|
-
let requestBodyParamResult = {
|
|
372
|
-
requiredNum: 0,
|
|
373
|
-
optionalNum: 0,
|
|
374
|
-
isValid: true,
|
|
375
|
-
reason: [],
|
|
376
|
-
};
|
|
377
|
-
if (requestJsonBody) {
|
|
378
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
379
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
380
|
-
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
381
|
-
}
|
|
382
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
383
|
-
if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
|
|
384
|
-
result.reason.push(...requestBodyParamResult.reason);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
388
|
-
if (!paramResult.isValid && paramResult.reason) {
|
|
389
|
-
result.reason.push(...paramResult.reason);
|
|
390
|
-
}
|
|
391
|
-
// Copilot support arbitrary parameters
|
|
392
|
-
if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
|
|
393
|
-
const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
|
|
394
|
-
const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
|
|
395
|
-
if (totalRequiredParams > 1) {
|
|
396
|
-
if (!options.allowMultipleParameters ||
|
|
397
|
-
totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
|
|
398
|
-
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
else if (totalParams === 0) {
|
|
402
|
-
result.reason.push(ErrorType.NoParameter);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (result.reason.length > 0) {
|
|
407
|
-
result.isValid = false;
|
|
408
|
-
}
|
|
409
|
-
return result;
|
|
410
|
-
}
|
|
411
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
412
|
-
if (authSchemeArray.length === 0) {
|
|
413
|
-
return { isValid: true, reason: [] };
|
|
414
|
-
}
|
|
415
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
416
|
-
// Currently we don't support multiple auth in one operation
|
|
417
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
418
|
-
return {
|
|
419
|
-
isValid: false,
|
|
420
|
-
reason: [ErrorType.MultipleAuthNotSupported],
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
for (const auths of authSchemeArray) {
|
|
424
|
-
if (auths.length === 1) {
|
|
425
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
426
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
427
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
428
|
-
return { isValid: true, reason: [] };
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
434
|
-
}
|
|
435
197
|
static isBearerTokenAuth(authScheme) {
|
|
436
198
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
437
199
|
}
|
|
@@ -439,10 +201,9 @@ class Utils {
|
|
|
439
201
|
return authScheme.type === "apiKey";
|
|
440
202
|
}
|
|
441
203
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
return false;
|
|
204
|
+
return !!(authScheme.type === "oauth2" &&
|
|
205
|
+
authScheme.flows &&
|
|
206
|
+
authScheme.flows.authorizationCode);
|
|
446
207
|
}
|
|
447
208
|
static getAuthArray(securities, spec) {
|
|
448
209
|
var _a;
|
|
@@ -470,7 +231,7 @@ class Utils {
|
|
|
470
231
|
static updateFirstLetter(str) {
|
|
471
232
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
472
233
|
}
|
|
473
|
-
static getResponseJson(operationObject
|
|
234
|
+
static getResponseJson(operationObject) {
|
|
474
235
|
var _a, _b;
|
|
475
236
|
let json = {};
|
|
476
237
|
let multipleMediaType = false;
|
|
@@ -481,9 +242,6 @@ class Utils {
|
|
|
481
242
|
json = responseObject.content["application/json"];
|
|
482
243
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
483
244
|
multipleMediaType = true;
|
|
484
|
-
if (isTeamsAiProject) {
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
245
|
json = {};
|
|
488
246
|
}
|
|
489
247
|
else {
|
|
@@ -711,13 +469,7 @@ class Utils {
|
|
|
711
469
|
}
|
|
712
470
|
}
|
|
713
471
|
const operationId = operationItem.operationId;
|
|
714
|
-
const parameters = [];
|
|
715
|
-
if (requiredParams.length !== 0) {
|
|
716
|
-
parameters.push(...requiredParams);
|
|
717
|
-
}
|
|
718
|
-
else {
|
|
719
|
-
parameters.push(optionalParams[0]);
|
|
720
|
-
}
|
|
472
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
721
473
|
const command = {
|
|
722
474
|
context: ["compose"],
|
|
723
475
|
type: "query",
|
|
@@ -726,26 +478,51 @@ class Utils {
|
|
|
726
478
|
parameters: parameters,
|
|
727
479
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
728
480
|
};
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
481
|
+
return command;
|
|
482
|
+
}
|
|
483
|
+
static format(str, ...args) {
|
|
484
|
+
let index = 0;
|
|
485
|
+
return str.replace(/%s/g, () => {
|
|
486
|
+
const arg = args[index++];
|
|
487
|
+
return arg !== undefined ? arg : "";
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
491
|
+
if (!authName) {
|
|
492
|
+
return "";
|
|
736
493
|
}
|
|
737
|
-
|
|
494
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
495
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
496
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
497
|
+
}
|
|
498
|
+
return safeRegistrationIdEnvName;
|
|
499
|
+
}
|
|
500
|
+
static getServerObject(spec, method, path) {
|
|
501
|
+
const pathObj = spec.paths[path];
|
|
502
|
+
const operationObject = pathObj[method];
|
|
503
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
504
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
505
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
506
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
507
|
+
return serverUrl;
|
|
738
508
|
}
|
|
739
|
-
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Copyright (c) Microsoft Corporation.
|
|
512
|
+
class Validator {
|
|
513
|
+
listAPIs() {
|
|
740
514
|
var _a;
|
|
741
|
-
|
|
515
|
+
if (this.apiMap) {
|
|
516
|
+
return this.apiMap;
|
|
517
|
+
}
|
|
518
|
+
const paths = this.spec.paths;
|
|
742
519
|
const result = {};
|
|
743
520
|
for (const path in paths) {
|
|
744
521
|
const methods = paths[path];
|
|
745
522
|
for (const method in methods) {
|
|
746
523
|
const operationObject = methods[method];
|
|
747
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
748
|
-
const validateResult =
|
|
524
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
525
|
+
const validateResult = this.validateAPI(method, path);
|
|
749
526
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
750
527
|
operation: operationObject,
|
|
751
528
|
isValid: validateResult.isValid,
|
|
@@ -754,38 +531,48 @@ class Utils {
|
|
|
754
531
|
}
|
|
755
532
|
}
|
|
756
533
|
}
|
|
534
|
+
this.apiMap = result;
|
|
757
535
|
return result;
|
|
758
536
|
}
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
770
|
-
errors.push(...serverErrors);
|
|
771
|
-
// Remote reference not supported
|
|
772
|
-
const refPaths = parser.$refs.paths();
|
|
773
|
-
// refPaths [0] is the current spec file path
|
|
774
|
-
if (refPaths.length > 1) {
|
|
775
|
-
errors.push({
|
|
776
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
777
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
778
|
-
data: refPaths,
|
|
537
|
+
validateSpecVersion() {
|
|
538
|
+
const result = { errors: [], warnings: [] };
|
|
539
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
540
|
+
result.errors.push({
|
|
541
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
542
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
543
|
+
data: this.spec.openapi,
|
|
779
544
|
});
|
|
780
545
|
}
|
|
781
|
-
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
validateSpecServer() {
|
|
549
|
+
const result = { errors: [], warnings: [] };
|
|
550
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
551
|
+
result.errors.push(...serverErrors);
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
validateSpecNoSupportAPI() {
|
|
555
|
+
const result = { errors: [], warnings: [] };
|
|
556
|
+
const apiMap = this.listAPIs();
|
|
782
557
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
783
558
|
if (validAPIs.length === 0) {
|
|
784
|
-
|
|
559
|
+
const data = [];
|
|
560
|
+
for (const key in apiMap) {
|
|
561
|
+
const { reason } = apiMap[key];
|
|
562
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
563
|
+
data.push(apiInvalidReason);
|
|
564
|
+
}
|
|
565
|
+
result.errors.push({
|
|
785
566
|
type: ErrorType.NoSupportedApi,
|
|
786
567
|
content: ConstantString.NoSupportedApi,
|
|
568
|
+
data,
|
|
787
569
|
});
|
|
788
570
|
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
validateSpecOperationId() {
|
|
574
|
+
const result = { errors: [], warnings: [] };
|
|
575
|
+
const apiMap = this.listAPIs();
|
|
789
576
|
// OperationId missing
|
|
790
577
|
const apisMissingOperationId = [];
|
|
791
578
|
for (const key in apiMap) {
|
|
@@ -795,54 +582,431 @@ class Utils {
|
|
|
795
582
|
}
|
|
796
583
|
}
|
|
797
584
|
if (apisMissingOperationId.length > 0) {
|
|
798
|
-
warnings.push({
|
|
585
|
+
result.warnings.push({
|
|
799
586
|
type: WarningType.OperationIdMissing,
|
|
800
587
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
801
588
|
data: apisMissingOperationId,
|
|
802
589
|
});
|
|
803
590
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
validateMethodAndPath(method, path) {
|
|
594
|
+
const result = { isValid: true, reason: [] };
|
|
595
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
596
|
+
result.isValid = false;
|
|
597
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
598
|
+
return result;
|
|
807
599
|
}
|
|
808
|
-
|
|
809
|
-
|
|
600
|
+
const pathObj = this.spec.paths[path];
|
|
601
|
+
if (!pathObj || !pathObj[method]) {
|
|
602
|
+
result.isValid = false;
|
|
603
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
604
|
+
return result;
|
|
810
605
|
}
|
|
811
|
-
return
|
|
812
|
-
status,
|
|
813
|
-
warnings,
|
|
814
|
-
errors,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
static format(str, ...args) {
|
|
818
|
-
let index = 0;
|
|
819
|
-
return str.replace(/%s/g, () => {
|
|
820
|
-
const arg = args[index++];
|
|
821
|
-
return arg !== undefined ? arg : "";
|
|
822
|
-
});
|
|
606
|
+
return result;
|
|
823
607
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
608
|
+
validateResponse(method, path) {
|
|
609
|
+
const result = { isValid: true, reason: [] };
|
|
610
|
+
const operationObject = this.spec.paths[path][method];
|
|
611
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
612
|
+
// only support response body only contains “application/json” content type
|
|
613
|
+
if (multipleMediaType) {
|
|
614
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
827
615
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
616
|
+
else if (Object.keys(json).length === 0) {
|
|
617
|
+
// response body should not be empty
|
|
618
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
831
619
|
}
|
|
832
|
-
return
|
|
620
|
+
return result;
|
|
833
621
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
622
|
+
validateServer(method, path) {
|
|
623
|
+
const result = { isValid: true, reason: [] };
|
|
624
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
625
|
+
if (!serverObj) {
|
|
626
|
+
// should contain server URL
|
|
627
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
// server url should be absolute url with https protocol
|
|
631
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
632
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
633
|
+
}
|
|
634
|
+
return result;
|
|
635
|
+
}
|
|
636
|
+
validateAuth(method, path) {
|
|
637
|
+
const pathObj = this.spec.paths[path];
|
|
638
|
+
const operationObject = pathObj[method];
|
|
639
|
+
const securities = operationObject.security;
|
|
640
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
641
|
+
if (authSchemeArray.length === 0) {
|
|
642
|
+
return { isValid: true, reason: [] };
|
|
643
|
+
}
|
|
644
|
+
if (this.options.allowAPIKeyAuth ||
|
|
645
|
+
this.options.allowOauth2 ||
|
|
646
|
+
this.options.allowBearerTokenAuth) {
|
|
647
|
+
// Currently we don't support multiple auth in one operation
|
|
648
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
649
|
+
return {
|
|
650
|
+
isValid: false,
|
|
651
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
for (const auths of authSchemeArray) {
|
|
655
|
+
if (auths.length === 1) {
|
|
656
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
657
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
658
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
659
|
+
return { isValid: true, reason: [] };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
665
|
+
}
|
|
666
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
667
|
+
var _a;
|
|
668
|
+
const paramResult = {
|
|
669
|
+
requiredNum: 0,
|
|
670
|
+
optionalNum: 0,
|
|
671
|
+
isValid: true,
|
|
672
|
+
reason: [],
|
|
673
|
+
};
|
|
674
|
+
if (Object.keys(schema).length === 0) {
|
|
675
|
+
return paramResult;
|
|
676
|
+
}
|
|
677
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
678
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
679
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
680
|
+
paramResult.isValid = false;
|
|
681
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
682
|
+
return paramResult;
|
|
683
|
+
}
|
|
684
|
+
if (schema.type === "string" ||
|
|
685
|
+
schema.type === "integer" ||
|
|
686
|
+
schema.type === "boolean" ||
|
|
687
|
+
schema.type === "number") {
|
|
688
|
+
if (isRequiredWithoutDefault) {
|
|
689
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else if (schema.type === "object") {
|
|
696
|
+
const { properties } = schema;
|
|
697
|
+
for (const property in properties) {
|
|
698
|
+
let isRequired = false;
|
|
699
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
700
|
+
isRequired = true;
|
|
842
701
|
}
|
|
702
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
703
|
+
paramResult.requiredNum += result.requiredNum;
|
|
704
|
+
paramResult.optionalNum += result.optionalNum;
|
|
705
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
706
|
+
paramResult.reason.push(...result.reason);
|
|
843
707
|
}
|
|
844
708
|
}
|
|
845
|
-
|
|
709
|
+
else {
|
|
710
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
711
|
+
paramResult.isValid = false;
|
|
712
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return paramResult;
|
|
716
|
+
}
|
|
717
|
+
checkParamSchema(paramObject) {
|
|
718
|
+
const paramResult = {
|
|
719
|
+
requiredNum: 0,
|
|
720
|
+
optionalNum: 0,
|
|
721
|
+
isValid: true,
|
|
722
|
+
reason: [],
|
|
723
|
+
};
|
|
724
|
+
if (!paramObject) {
|
|
725
|
+
return paramResult;
|
|
726
|
+
}
|
|
727
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
728
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
729
|
+
const param = paramObject[i];
|
|
730
|
+
const schema = param.schema;
|
|
731
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
732
|
+
paramResult.isValid = false;
|
|
733
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
737
|
+
if (isCopilot) {
|
|
738
|
+
if (isRequiredWithoutDefault) {
|
|
739
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
743
|
+
}
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
747
|
+
if (isRequiredWithoutDefault) {
|
|
748
|
+
paramResult.isValid = false;
|
|
749
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
750
|
+
}
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (schema.type !== "boolean" &&
|
|
754
|
+
schema.type !== "string" &&
|
|
755
|
+
schema.type !== "number" &&
|
|
756
|
+
schema.type !== "integer") {
|
|
757
|
+
if (isRequiredWithoutDefault) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (param.in === "query" || param.in === "path") {
|
|
764
|
+
if (isRequiredWithoutDefault) {
|
|
765
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return paramResult;
|
|
773
|
+
}
|
|
774
|
+
hasNestedObjectInSchema(schema) {
|
|
775
|
+
if (schema.type === "object") {
|
|
776
|
+
for (const property in schema.properties) {
|
|
777
|
+
const nestedSchema = schema.properties[property];
|
|
778
|
+
if (nestedSchema.type === "object") {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Copyright (c) Microsoft Corporation.
|
|
788
|
+
class CopilotValidator extends Validator {
|
|
789
|
+
constructor(spec, options) {
|
|
790
|
+
super();
|
|
791
|
+
this.projectType = ProjectType.Copilot;
|
|
792
|
+
this.options = options;
|
|
793
|
+
this.spec = spec;
|
|
794
|
+
}
|
|
795
|
+
validateSpec() {
|
|
796
|
+
const result = { errors: [], warnings: [] };
|
|
797
|
+
// validate spec version
|
|
798
|
+
let validationResult = this.validateSpecVersion();
|
|
799
|
+
result.errors.push(...validationResult.errors);
|
|
800
|
+
// validate spec server
|
|
801
|
+
validationResult = this.validateSpecServer();
|
|
802
|
+
result.errors.push(...validationResult.errors);
|
|
803
|
+
// validate no supported API
|
|
804
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
805
|
+
result.errors.push(...validationResult.errors);
|
|
806
|
+
// validate operationId missing
|
|
807
|
+
validationResult = this.validateSpecOperationId();
|
|
808
|
+
result.warnings.push(...validationResult.warnings);
|
|
809
|
+
return result;
|
|
810
|
+
}
|
|
811
|
+
validateAPI(method, path) {
|
|
812
|
+
const result = { isValid: true, reason: [] };
|
|
813
|
+
method = method.toLocaleLowerCase();
|
|
814
|
+
// validate method and path
|
|
815
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
816
|
+
if (!methodAndPathResult.isValid) {
|
|
817
|
+
return methodAndPathResult;
|
|
818
|
+
}
|
|
819
|
+
const operationObject = this.spec.paths[path][method];
|
|
820
|
+
// validate auth
|
|
821
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
822
|
+
result.reason.push(...authCheckResult.reason);
|
|
823
|
+
// validate operationId
|
|
824
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
825
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
826
|
+
}
|
|
827
|
+
// validate server
|
|
828
|
+
const validateServerResult = this.validateServer(method, path);
|
|
829
|
+
result.reason.push(...validateServerResult.reason);
|
|
830
|
+
// validate response
|
|
831
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
832
|
+
result.reason.push(...validateResponseResult.reason);
|
|
833
|
+
// validate requestBody
|
|
834
|
+
const requestBody = operationObject.requestBody;
|
|
835
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
836
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
837
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
838
|
+
}
|
|
839
|
+
if (requestJsonBody) {
|
|
840
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
841
|
+
if (requestBodySchema.type !== "object") {
|
|
842
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
843
|
+
}
|
|
844
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
845
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
846
|
+
}
|
|
847
|
+
// validate parameters
|
|
848
|
+
const paramObject = operationObject.parameters;
|
|
849
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
850
|
+
result.reason.push(...paramResult.reason);
|
|
851
|
+
if (result.reason.length > 0) {
|
|
852
|
+
result.isValid = false;
|
|
853
|
+
}
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Copyright (c) Microsoft Corporation.
|
|
859
|
+
class SMEValidator extends Validator {
|
|
860
|
+
constructor(spec, options) {
|
|
861
|
+
super();
|
|
862
|
+
this.projectType = ProjectType.SME;
|
|
863
|
+
this.options = options;
|
|
864
|
+
this.spec = spec;
|
|
865
|
+
}
|
|
866
|
+
validateSpec() {
|
|
867
|
+
const result = { errors: [], warnings: [] };
|
|
868
|
+
// validate spec version
|
|
869
|
+
let validationResult = this.validateSpecVersion();
|
|
870
|
+
result.errors.push(...validationResult.errors);
|
|
871
|
+
// validate spec server
|
|
872
|
+
validationResult = this.validateSpecServer();
|
|
873
|
+
result.errors.push(...validationResult.errors);
|
|
874
|
+
// validate no supported API
|
|
875
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
876
|
+
result.errors.push(...validationResult.errors);
|
|
877
|
+
// validate operationId missing
|
|
878
|
+
if (this.options.allowMissingId) {
|
|
879
|
+
validationResult = this.validateSpecOperationId();
|
|
880
|
+
result.warnings.push(...validationResult.warnings);
|
|
881
|
+
}
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
validateAPI(method, path) {
|
|
885
|
+
const result = { isValid: true, reason: [] };
|
|
886
|
+
method = method.toLocaleLowerCase();
|
|
887
|
+
// validate method and path
|
|
888
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
889
|
+
if (!methodAndPathResult.isValid) {
|
|
890
|
+
return methodAndPathResult;
|
|
891
|
+
}
|
|
892
|
+
const operationObject = this.spec.paths[path][method];
|
|
893
|
+
// validate auth
|
|
894
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
895
|
+
result.reason.push(...authCheckResult.reason);
|
|
896
|
+
// validate operationId
|
|
897
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
898
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
899
|
+
}
|
|
900
|
+
// validate server
|
|
901
|
+
const validateServerResult = this.validateServer(method, path);
|
|
902
|
+
result.reason.push(...validateServerResult.reason);
|
|
903
|
+
// validate response
|
|
904
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
905
|
+
result.reason.push(...validateResponseResult.reason);
|
|
906
|
+
let postBodyResult = {
|
|
907
|
+
requiredNum: 0,
|
|
908
|
+
optionalNum: 0,
|
|
909
|
+
isValid: true,
|
|
910
|
+
reason: [],
|
|
911
|
+
};
|
|
912
|
+
// validate requestBody
|
|
913
|
+
const requestBody = operationObject.requestBody;
|
|
914
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
915
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
916
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
917
|
+
}
|
|
918
|
+
if (requestJsonBody) {
|
|
919
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
920
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
921
|
+
result.reason.push(...postBodyResult.reason);
|
|
922
|
+
}
|
|
923
|
+
// validate parameters
|
|
924
|
+
const paramObject = operationObject.parameters;
|
|
925
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
926
|
+
result.reason.push(...paramResult.reason);
|
|
927
|
+
// validate total parameters count
|
|
928
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
929
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
930
|
+
result.reason.push(...paramCountResult.reason);
|
|
931
|
+
}
|
|
932
|
+
if (result.reason.length > 0) {
|
|
933
|
+
result.isValid = false;
|
|
934
|
+
}
|
|
935
|
+
return result;
|
|
936
|
+
}
|
|
937
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
938
|
+
const result = { isValid: true, reason: [] };
|
|
939
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
940
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
941
|
+
if (totalRequiredParams > 1) {
|
|
942
|
+
if (!this.options.allowMultipleParameters ||
|
|
943
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
944
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
else if (totalParams === 0) {
|
|
948
|
+
result.reason.push(ErrorType.NoParameter);
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
954
|
+
|
|
955
|
+
// Copyright (c) Microsoft Corporation.
|
|
956
|
+
class TeamsAIValidator extends Validator {
|
|
957
|
+
constructor(spec, options) {
|
|
958
|
+
super();
|
|
959
|
+
this.projectType = ProjectType.TeamsAi;
|
|
960
|
+
this.options = options;
|
|
961
|
+
this.spec = spec;
|
|
962
|
+
}
|
|
963
|
+
validateSpec() {
|
|
964
|
+
const result = { errors: [], warnings: [] };
|
|
965
|
+
// validate spec server
|
|
966
|
+
let validationResult = this.validateSpecServer();
|
|
967
|
+
result.errors.push(...validationResult.errors);
|
|
968
|
+
// validate no supported API
|
|
969
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
970
|
+
result.errors.push(...validationResult.errors);
|
|
971
|
+
return result;
|
|
972
|
+
}
|
|
973
|
+
validateAPI(method, path) {
|
|
974
|
+
const result = { isValid: true, reason: [] };
|
|
975
|
+
method = method.toLocaleLowerCase();
|
|
976
|
+
// validate method and path
|
|
977
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
978
|
+
if (!methodAndPathResult.isValid) {
|
|
979
|
+
return methodAndPathResult;
|
|
980
|
+
}
|
|
981
|
+
const operationObject = this.spec.paths[path][method];
|
|
982
|
+
// validate operationId
|
|
983
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
984
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
985
|
+
}
|
|
986
|
+
// validate server
|
|
987
|
+
const validateServerResult = this.validateServer(method, path);
|
|
988
|
+
result.reason.push(...validateServerResult.reason);
|
|
989
|
+
if (result.reason.length > 0) {
|
|
990
|
+
result.isValid = false;
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
class ValidatorFactory {
|
|
997
|
+
static create(spec, options) {
|
|
998
|
+
var _a;
|
|
999
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1000
|
+
switch (type) {
|
|
1001
|
+
case ProjectType.SME:
|
|
1002
|
+
return new SMEValidator(spec, options);
|
|
1003
|
+
case ProjectType.Copilot:
|
|
1004
|
+
return new CopilotValidator(spec, options);
|
|
1005
|
+
case ProjectType.TeamsAi:
|
|
1006
|
+
return new TeamsAIValidator(spec, options);
|
|
1007
|
+
default:
|
|
1008
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1009
|
+
}
|
|
846
1010
|
}
|
|
847
1011
|
}
|
|
848
1012
|
|
|
@@ -860,7 +1024,8 @@ class SpecFilter {
|
|
|
860
1024
|
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
861
1025
|
pathObj &&
|
|
862
1026
|
pathObj[methodName]) {
|
|
863
|
-
const
|
|
1027
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1028
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
864
1029
|
if (!validateResult.isValid) {
|
|
865
1030
|
continue;
|
|
866
1031
|
}
|
|
@@ -886,6 +1051,293 @@ class SpecFilter {
|
|
|
886
1051
|
}
|
|
887
1052
|
}
|
|
888
1053
|
|
|
1054
|
+
// Copyright (c) Microsoft Corporation.
|
|
1055
|
+
class AdaptiveCardGenerator {
|
|
1056
|
+
static generateAdaptiveCard(operationItem) {
|
|
1057
|
+
try {
|
|
1058
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1059
|
+
let cardBody = [];
|
|
1060
|
+
let schema = json.schema;
|
|
1061
|
+
let jsonPath = "$";
|
|
1062
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
1063
|
+
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1064
|
+
if (jsonPath !== "$") {
|
|
1065
|
+
schema = schema.properties[jsonPath];
|
|
1066
|
+
}
|
|
1067
|
+
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1068
|
+
}
|
|
1069
|
+
// if no schema, try to use example value
|
|
1070
|
+
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1071
|
+
cardBody = [
|
|
1072
|
+
{
|
|
1073
|
+
type: ConstantString.TextBlockType,
|
|
1074
|
+
text: "${jsonStringify($root)}",
|
|
1075
|
+
wrap: true,
|
|
1076
|
+
},
|
|
1077
|
+
];
|
|
1078
|
+
}
|
|
1079
|
+
// if no example value, use default success response
|
|
1080
|
+
if (cardBody.length === 0) {
|
|
1081
|
+
cardBody = [
|
|
1082
|
+
{
|
|
1083
|
+
type: ConstantString.TextBlockType,
|
|
1084
|
+
text: "success",
|
|
1085
|
+
wrap: true,
|
|
1086
|
+
},
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
const fullCard = {
|
|
1090
|
+
type: ConstantString.AdaptiveCardType,
|
|
1091
|
+
$schema: ConstantString.AdaptiveCardSchema,
|
|
1092
|
+
version: ConstantString.AdaptiveCardVersion,
|
|
1093
|
+
body: cardBody,
|
|
1094
|
+
};
|
|
1095
|
+
return [fullCard, jsonPath];
|
|
1096
|
+
}
|
|
1097
|
+
catch (err) {
|
|
1098
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1102
|
+
if (schema.type === "array") {
|
|
1103
|
+
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1104
|
+
if (Object.keys(schema.items).length === 0) {
|
|
1105
|
+
return [
|
|
1106
|
+
{
|
|
1107
|
+
type: ConstantString.TextBlockType,
|
|
1108
|
+
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1109
|
+
wrap: true,
|
|
1110
|
+
},
|
|
1111
|
+
];
|
|
1112
|
+
}
|
|
1113
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1114
|
+
const template = {
|
|
1115
|
+
type: ConstantString.ContainerType,
|
|
1116
|
+
$data: name ? `\${${name}}` : "${$root}",
|
|
1117
|
+
items: Array(),
|
|
1118
|
+
};
|
|
1119
|
+
template.items.push(...obj);
|
|
1120
|
+
return [template];
|
|
1121
|
+
}
|
|
1122
|
+
// some schema may not contain type but contain properties
|
|
1123
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1124
|
+
const { properties } = schema;
|
|
1125
|
+
const result = [];
|
|
1126
|
+
for (const property in properties) {
|
|
1127
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1128
|
+
result.push(...obj);
|
|
1129
|
+
}
|
|
1130
|
+
if (schema.additionalProperties) {
|
|
1131
|
+
// TODO: better ways to handler warnings.
|
|
1132
|
+
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1133
|
+
}
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1136
|
+
if (schema.type === "string" ||
|
|
1137
|
+
schema.type === "integer" ||
|
|
1138
|
+
schema.type === "boolean" ||
|
|
1139
|
+
schema.type === "number") {
|
|
1140
|
+
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1141
|
+
// string in root: "ddd"
|
|
1142
|
+
let text = "result: ${$root}";
|
|
1143
|
+
if (name) {
|
|
1144
|
+
// object { id: "1" }
|
|
1145
|
+
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1146
|
+
if (parentArrayName) {
|
|
1147
|
+
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1148
|
+
text = `${parentArrayName}.${text}`;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
else if (parentArrayName) {
|
|
1152
|
+
// string array: photoUrls: ["1", "2"]
|
|
1153
|
+
text = `${parentArrayName}: ` + "${$data}";
|
|
1154
|
+
}
|
|
1155
|
+
return [
|
|
1156
|
+
{
|
|
1157
|
+
type: ConstantString.TextBlockType,
|
|
1158
|
+
text,
|
|
1159
|
+
wrap: true,
|
|
1160
|
+
},
|
|
1161
|
+
];
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
if (name) {
|
|
1165
|
+
return [
|
|
1166
|
+
{
|
|
1167
|
+
type: "Image",
|
|
1168
|
+
url: `\${${name}}`,
|
|
1169
|
+
$when: `\${${name} != null}`,
|
|
1170
|
+
},
|
|
1171
|
+
];
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
return [
|
|
1175
|
+
{
|
|
1176
|
+
type: "Image",
|
|
1177
|
+
url: "${$data}",
|
|
1178
|
+
$when: "${$data != null}",
|
|
1179
|
+
},
|
|
1180
|
+
];
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1185
|
+
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1186
|
+
}
|
|
1187
|
+
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1188
|
+
}
|
|
1189
|
+
// Find the first array property in the response schema object with the well-known name
|
|
1190
|
+
static getResponseJsonPathFromSchema(schema) {
|
|
1191
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1192
|
+
const { properties } = schema;
|
|
1193
|
+
for (const property in properties) {
|
|
1194
|
+
const schema = properties[property];
|
|
1195
|
+
if (schema.type === "array" &&
|
|
1196
|
+
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1197
|
+
return property;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return "$";
|
|
1202
|
+
}
|
|
1203
|
+
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1204
|
+
const propertyName = name ? name : parentArrayName;
|
|
1205
|
+
return (!!propertyName &&
|
|
1206
|
+
schema.type === "string" &&
|
|
1207
|
+
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1208
|
+
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Copyright (c) Microsoft Corporation.
|
|
1213
|
+
function wrapAdaptiveCard(card, jsonPath) {
|
|
1214
|
+
const result = {
|
|
1215
|
+
version: ConstantString.WrappedCardVersion,
|
|
1216
|
+
$schema: ConstantString.WrappedCardSchema,
|
|
1217
|
+
jsonPath: jsonPath,
|
|
1218
|
+
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1219
|
+
responseCardTemplate: card,
|
|
1220
|
+
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1221
|
+
};
|
|
1222
|
+
return result;
|
|
1223
|
+
}
|
|
1224
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1225
|
+
const props = inferProperties(card);
|
|
1226
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1227
|
+
const result = {
|
|
1228
|
+
data_path: dataPath,
|
|
1229
|
+
};
|
|
1230
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1231
|
+
result.properties = {};
|
|
1232
|
+
if (props.title) {
|
|
1233
|
+
result.properties.title = "$." + props.title;
|
|
1234
|
+
}
|
|
1235
|
+
if (props.subtitle) {
|
|
1236
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1237
|
+
}
|
|
1238
|
+
if (props.imageUrl) {
|
|
1239
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
result.static_template = card;
|
|
1243
|
+
return result;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1247
|
+
* The preview card template includes a title and an optional subtitle and image.
|
|
1248
|
+
* It populates the preview card template with the first text block that matches
|
|
1249
|
+
* each well-known name, in the order of title, subtitle, and image.
|
|
1250
|
+
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1251
|
+
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1252
|
+
* @param card The Adaptive Card to infer the preview card template from.
|
|
1253
|
+
* @param jsonPath The JSON path to the root object in the card body.
|
|
1254
|
+
* @returns The inferred preview card template.
|
|
1255
|
+
*/
|
|
1256
|
+
function inferPreviewCardTemplate(card) {
|
|
1257
|
+
const result = {
|
|
1258
|
+
title: "result",
|
|
1259
|
+
};
|
|
1260
|
+
const inferredProperties = inferProperties(card);
|
|
1261
|
+
if (inferredProperties.title) {
|
|
1262
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1263
|
+
}
|
|
1264
|
+
if (inferredProperties.subtitle) {
|
|
1265
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1266
|
+
}
|
|
1267
|
+
if (inferredProperties.imageUrl) {
|
|
1268
|
+
result.image = {
|
|
1269
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1270
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1271
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
return result;
|
|
1275
|
+
}
|
|
1276
|
+
function inferProperties(card) {
|
|
1277
|
+
var _a;
|
|
1278
|
+
const result = {};
|
|
1279
|
+
const nameSet = new Set();
|
|
1280
|
+
let rootObject;
|
|
1281
|
+
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1282
|
+
rootObject = card.body[0].items;
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
rootObject = card.body;
|
|
1286
|
+
}
|
|
1287
|
+
for (const element of rootObject) {
|
|
1288
|
+
if (element.type === ConstantString.TextBlockType) {
|
|
1289
|
+
const textElement = element;
|
|
1290
|
+
const index = textElement.text.indexOf("${if(");
|
|
1291
|
+
if (index > 0) {
|
|
1292
|
+
const text = textElement.text.substring(index);
|
|
1293
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1294
|
+
const property = match ? match[1] : "";
|
|
1295
|
+
if (property) {
|
|
1296
|
+
nameSet.add(property);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1301
|
+
const imageElement = element;
|
|
1302
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1303
|
+
const property = match ? match[1] : "";
|
|
1304
|
+
if (property) {
|
|
1305
|
+
nameSet.add(property);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
for (const name of nameSet) {
|
|
1310
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1311
|
+
result.title = name;
|
|
1312
|
+
nameSet.delete(name);
|
|
1313
|
+
}
|
|
1314
|
+
else if (!result.subtitle &&
|
|
1315
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1316
|
+
result.subtitle = name;
|
|
1317
|
+
nameSet.delete(name);
|
|
1318
|
+
}
|
|
1319
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1320
|
+
result.imageUrl = name;
|
|
1321
|
+
nameSet.delete(name);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
for (const name of nameSet) {
|
|
1325
|
+
if (!result.title) {
|
|
1326
|
+
result.title = name;
|
|
1327
|
+
nameSet.delete(name);
|
|
1328
|
+
}
|
|
1329
|
+
else if (!result.subtitle) {
|
|
1330
|
+
result.subtitle = name;
|
|
1331
|
+
nameSet.delete(name);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!result.title && result.subtitle) {
|
|
1335
|
+
result.title = result.subtitle;
|
|
1336
|
+
delete result.subtitle;
|
|
1337
|
+
}
|
|
1338
|
+
return result;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
889
1341
|
// Copyright (c) Microsoft Corporation.
|
|
890
1342
|
class ManifestUpdater {
|
|
891
1343
|
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
|
|
@@ -893,13 +1345,14 @@ class ManifestUpdater {
|
|
|
893
1345
|
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
894
1346
|
manifest.plugins = [
|
|
895
1347
|
{
|
|
896
|
-
|
|
1348
|
+
file: apiPluginRelativePath,
|
|
1349
|
+
id: ConstantString.DefaultPluginId,
|
|
897
1350
|
},
|
|
898
1351
|
];
|
|
899
1352
|
const appName = this.removeEnvs(manifest.name.short);
|
|
900
1353
|
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
901
1354
|
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
902
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
1355
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
|
|
903
1356
|
return [manifest, apiPlugin];
|
|
904
1357
|
}
|
|
905
1358
|
static updateManifestDescription(manifest, spec) {
|
|
@@ -923,10 +1376,11 @@ class ManifestUpdater {
|
|
|
923
1376
|
}
|
|
924
1377
|
return parameter;
|
|
925
1378
|
}
|
|
926
|
-
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
927
|
-
var _a, _b, _c;
|
|
1379
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
|
|
1380
|
+
var _a, _b, _c, _d;
|
|
928
1381
|
const functions = [];
|
|
929
1382
|
const functionNames = [];
|
|
1383
|
+
const conversationStarters = [];
|
|
930
1384
|
const paths = spec.paths;
|
|
931
1385
|
for (const pathUrl in paths) {
|
|
932
1386
|
const pathItem = paths[pathUrl];
|
|
@@ -979,31 +1433,82 @@ class ManifestUpdater {
|
|
|
979
1433
|
description: description,
|
|
980
1434
|
parameters: parameters,
|
|
981
1435
|
};
|
|
1436
|
+
if (options.allowResponseSemantics) {
|
|
1437
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1438
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1439
|
+
funcObj.capabilities = {
|
|
1440
|
+
response_semantics: responseSemantic,
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
982
1443
|
functions.push(funcObj);
|
|
983
1444
|
functionNames.push(operationId);
|
|
1445
|
+
if (description) {
|
|
1446
|
+
conversationStarters.push(description);
|
|
1447
|
+
}
|
|
984
1448
|
}
|
|
985
1449
|
}
|
|
986
1450
|
}
|
|
987
1451
|
}
|
|
988
1452
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1453
|
+
let apiPlugin;
|
|
1454
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1455
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
apiPlugin = {
|
|
1459
|
+
schema_version: "v2",
|
|
1460
|
+
name_for_human: "",
|
|
1461
|
+
description_for_human: "",
|
|
1462
|
+
functions: [],
|
|
1463
|
+
runtimes: [],
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1467
|
+
for (const func of functions) {
|
|
1468
|
+
const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
|
|
1469
|
+
if (index === -1) {
|
|
1470
|
+
apiPlugin.functions.push(func);
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
apiPlugin.functions[index] = func;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1477
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath);
|
|
1478
|
+
if (index === -1) {
|
|
1479
|
+
apiPlugin.runtimes.push({
|
|
1480
|
+
type: "OpenApi",
|
|
1481
|
+
auth: {
|
|
1482
|
+
type: "none",
|
|
1004
1483
|
},
|
|
1005
|
-
|
|
1006
|
-
|
|
1484
|
+
spec: {
|
|
1485
|
+
url: specRelativePath,
|
|
1486
|
+
},
|
|
1487
|
+
run_for_functions: functionNames,
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
else {
|
|
1491
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1492
|
+
}
|
|
1493
|
+
if (!apiPlugin.name_for_human) {
|
|
1494
|
+
apiPlugin.name_for_human = appName;
|
|
1495
|
+
}
|
|
1496
|
+
if (!apiPlugin.description_for_human) {
|
|
1497
|
+
apiPlugin.description_for_human =
|
|
1498
|
+
(_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
|
|
1499
|
+
}
|
|
1500
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1501
|
+
if (!apiPlugin.capabilities) {
|
|
1502
|
+
apiPlugin.capabilities = {
|
|
1503
|
+
localization: {},
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1507
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1508
|
+
.slice(0, 5)
|
|
1509
|
+
.map((text) => ({ text }));
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1007
1512
|
return apiPlugin;
|
|
1008
1513
|
}
|
|
1009
1514
|
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
@@ -1072,16 +1577,26 @@ class ManifestUpdater {
|
|
|
1072
1577
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1073
1578
|
const operationItem = operations[method];
|
|
1074
1579
|
if (operationItem) {
|
|
1075
|
-
const
|
|
1580
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1581
|
+
if (command.parameters &&
|
|
1582
|
+
command.parameters.length >= 1 &&
|
|
1583
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1584
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1585
|
+
}
|
|
1586
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1587
|
+
command.parameters = [command.parameters[0]];
|
|
1588
|
+
warnings.push({
|
|
1589
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1590
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1591
|
+
data: command.id,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1076
1594
|
if (adaptiveCardFolder) {
|
|
1077
1595
|
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1078
1596
|
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1079
1597
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1080
1598
|
: "";
|
|
1081
1599
|
}
|
|
1082
|
-
if (warning) {
|
|
1083
|
-
warnings.push(warning);
|
|
1084
|
-
}
|
|
1085
1600
|
commands.push(command);
|
|
1086
1601
|
}
|
|
1087
1602
|
}
|
|
@@ -1106,255 +1621,6 @@ class ManifestUpdater {
|
|
|
1106
1621
|
}
|
|
1107
1622
|
}
|
|
1108
1623
|
|
|
1109
|
-
// Copyright (c) Microsoft Corporation.
|
|
1110
|
-
class AdaptiveCardGenerator {
|
|
1111
|
-
static generateAdaptiveCard(operationItem) {
|
|
1112
|
-
try {
|
|
1113
|
-
const { json } = Utils.getResponseJson(operationItem);
|
|
1114
|
-
let cardBody = [];
|
|
1115
|
-
let schema = json.schema;
|
|
1116
|
-
let jsonPath = "$";
|
|
1117
|
-
if (schema && Object.keys(schema).length > 0) {
|
|
1118
|
-
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1119
|
-
if (jsonPath !== "$") {
|
|
1120
|
-
schema = schema.properties[jsonPath];
|
|
1121
|
-
}
|
|
1122
|
-
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1123
|
-
}
|
|
1124
|
-
// if no schema, try to use example value
|
|
1125
|
-
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1126
|
-
cardBody = [
|
|
1127
|
-
{
|
|
1128
|
-
type: ConstantString.TextBlockType,
|
|
1129
|
-
text: "${jsonStringify($root)}",
|
|
1130
|
-
wrap: true,
|
|
1131
|
-
},
|
|
1132
|
-
];
|
|
1133
|
-
}
|
|
1134
|
-
// if no example value, use default success response
|
|
1135
|
-
if (cardBody.length === 0) {
|
|
1136
|
-
cardBody = [
|
|
1137
|
-
{
|
|
1138
|
-
type: ConstantString.TextBlockType,
|
|
1139
|
-
text: "success",
|
|
1140
|
-
wrap: true,
|
|
1141
|
-
},
|
|
1142
|
-
];
|
|
1143
|
-
}
|
|
1144
|
-
const fullCard = {
|
|
1145
|
-
type: ConstantString.AdaptiveCardType,
|
|
1146
|
-
$schema: ConstantString.AdaptiveCardSchema,
|
|
1147
|
-
version: ConstantString.AdaptiveCardVersion,
|
|
1148
|
-
body: cardBody,
|
|
1149
|
-
};
|
|
1150
|
-
return [fullCard, jsonPath];
|
|
1151
|
-
}
|
|
1152
|
-
catch (err) {
|
|
1153
|
-
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1157
|
-
if (schema.type === "array") {
|
|
1158
|
-
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1159
|
-
if (Object.keys(schema.items).length === 0) {
|
|
1160
|
-
return [
|
|
1161
|
-
{
|
|
1162
|
-
type: ConstantString.TextBlockType,
|
|
1163
|
-
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1164
|
-
wrap: true,
|
|
1165
|
-
},
|
|
1166
|
-
];
|
|
1167
|
-
}
|
|
1168
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1169
|
-
const template = {
|
|
1170
|
-
type: ConstantString.ContainerType,
|
|
1171
|
-
$data: name ? `\${${name}}` : "${$root}",
|
|
1172
|
-
items: Array(),
|
|
1173
|
-
};
|
|
1174
|
-
template.items.push(...obj);
|
|
1175
|
-
return [template];
|
|
1176
|
-
}
|
|
1177
|
-
// some schema may not contain type but contain properties
|
|
1178
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1179
|
-
const { properties } = schema;
|
|
1180
|
-
const result = [];
|
|
1181
|
-
for (const property in properties) {
|
|
1182
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1183
|
-
result.push(...obj);
|
|
1184
|
-
}
|
|
1185
|
-
if (schema.additionalProperties) {
|
|
1186
|
-
// TODO: better ways to handler warnings.
|
|
1187
|
-
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1188
|
-
}
|
|
1189
|
-
return result;
|
|
1190
|
-
}
|
|
1191
|
-
if (schema.type === "string" ||
|
|
1192
|
-
schema.type === "integer" ||
|
|
1193
|
-
schema.type === "boolean" ||
|
|
1194
|
-
schema.type === "number") {
|
|
1195
|
-
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1196
|
-
// string in root: "ddd"
|
|
1197
|
-
let text = "result: ${$root}";
|
|
1198
|
-
if (name) {
|
|
1199
|
-
// object { id: "1" }
|
|
1200
|
-
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1201
|
-
if (parentArrayName) {
|
|
1202
|
-
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1203
|
-
text = `${parentArrayName}.${text}`;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
else if (parentArrayName) {
|
|
1207
|
-
// string array: photoUrls: ["1", "2"]
|
|
1208
|
-
text = `${parentArrayName}: ` + "${$data}";
|
|
1209
|
-
}
|
|
1210
|
-
return [
|
|
1211
|
-
{
|
|
1212
|
-
type: ConstantString.TextBlockType,
|
|
1213
|
-
text,
|
|
1214
|
-
wrap: true,
|
|
1215
|
-
},
|
|
1216
|
-
];
|
|
1217
|
-
}
|
|
1218
|
-
else {
|
|
1219
|
-
if (name) {
|
|
1220
|
-
return [
|
|
1221
|
-
{
|
|
1222
|
-
type: "Image",
|
|
1223
|
-
url: `\${${name}}`,
|
|
1224
|
-
$when: `\${${name} != null}`,
|
|
1225
|
-
},
|
|
1226
|
-
];
|
|
1227
|
-
}
|
|
1228
|
-
else {
|
|
1229
|
-
return [
|
|
1230
|
-
{
|
|
1231
|
-
type: "Image",
|
|
1232
|
-
url: "${$data}",
|
|
1233
|
-
$when: "${$data != null}",
|
|
1234
|
-
},
|
|
1235
|
-
];
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1240
|
-
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1241
|
-
}
|
|
1242
|
-
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1243
|
-
}
|
|
1244
|
-
// Find the first array property in the response schema object with the well-known name
|
|
1245
|
-
static getResponseJsonPathFromSchema(schema) {
|
|
1246
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1247
|
-
const { properties } = schema;
|
|
1248
|
-
for (const property in properties) {
|
|
1249
|
-
const schema = properties[property];
|
|
1250
|
-
if (schema.type === "array" &&
|
|
1251
|
-
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1252
|
-
return property;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
return "$";
|
|
1257
|
-
}
|
|
1258
|
-
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1259
|
-
const propertyName = name ? name : parentArrayName;
|
|
1260
|
-
return (!!propertyName &&
|
|
1261
|
-
schema.type === "string" &&
|
|
1262
|
-
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1263
|
-
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// Copyright (c) Microsoft Corporation.
|
|
1268
|
-
function wrapAdaptiveCard(card, jsonPath) {
|
|
1269
|
-
const result = {
|
|
1270
|
-
version: ConstantString.WrappedCardVersion,
|
|
1271
|
-
$schema: ConstantString.WrappedCardSchema,
|
|
1272
|
-
jsonPath: jsonPath,
|
|
1273
|
-
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1274
|
-
responseCardTemplate: card,
|
|
1275
|
-
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1276
|
-
};
|
|
1277
|
-
return result;
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1281
|
-
* The preview card template includes a title and an optional subtitle and image.
|
|
1282
|
-
* It populates the preview card template with the first text block that matches
|
|
1283
|
-
* each well-known name, in the order of title, subtitle, and image.
|
|
1284
|
-
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1285
|
-
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1286
|
-
* @param card The Adaptive Card to infer the preview card template from.
|
|
1287
|
-
* @param jsonPath The JSON path to the root object in the card body.
|
|
1288
|
-
* @returns The inferred preview card template.
|
|
1289
|
-
*/
|
|
1290
|
-
function inferPreviewCardTemplate(card) {
|
|
1291
|
-
var _a;
|
|
1292
|
-
const result = {
|
|
1293
|
-
title: "",
|
|
1294
|
-
};
|
|
1295
|
-
const textBlockElements = new Set();
|
|
1296
|
-
let rootObject;
|
|
1297
|
-
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1298
|
-
rootObject = card.body[0].items;
|
|
1299
|
-
}
|
|
1300
|
-
else {
|
|
1301
|
-
rootObject = card.body;
|
|
1302
|
-
}
|
|
1303
|
-
for (const element of rootObject) {
|
|
1304
|
-
if (element.type === ConstantString.TextBlockType) {
|
|
1305
|
-
const textElement = element;
|
|
1306
|
-
const index = textElement.text.indexOf("${if(");
|
|
1307
|
-
if (index > 0) {
|
|
1308
|
-
textElement.text = textElement.text.substring(index);
|
|
1309
|
-
textBlockElements.add(textElement);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
for (const element of textBlockElements) {
|
|
1314
|
-
const text = element.text;
|
|
1315
|
-
if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
|
|
1316
|
-
result.title = text;
|
|
1317
|
-
textBlockElements.delete(element);
|
|
1318
|
-
}
|
|
1319
|
-
else if (!result.subtitle &&
|
|
1320
|
-
Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
|
|
1321
|
-
result.subtitle = text;
|
|
1322
|
-
textBlockElements.delete(element);
|
|
1323
|
-
}
|
|
1324
|
-
else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
|
|
1325
|
-
const match = text.match(/\${if\(([^,]+),/);
|
|
1326
|
-
const property = match ? match[1] : "";
|
|
1327
|
-
if (property) {
|
|
1328
|
-
result.image = {
|
|
1329
|
-
url: `\${${property}}`,
|
|
1330
|
-
alt: text,
|
|
1331
|
-
$when: `\${${property} != null}`,
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
textBlockElements.delete(element);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
for (const element of textBlockElements) {
|
|
1338
|
-
const text = element.text;
|
|
1339
|
-
if (!result.title) {
|
|
1340
|
-
result.title = text;
|
|
1341
|
-
textBlockElements.delete(element);
|
|
1342
|
-
}
|
|
1343
|
-
else if (!result.subtitle) {
|
|
1344
|
-
result.subtitle = text;
|
|
1345
|
-
textBlockElements.delete(element);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
if (!result.title && result.subtitle) {
|
|
1349
|
-
result.title = result.subtitle;
|
|
1350
|
-
delete result.subtitle;
|
|
1351
|
-
}
|
|
1352
|
-
if (!result.title) {
|
|
1353
|
-
result.title = "result";
|
|
1354
|
-
}
|
|
1355
|
-
return result;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
1624
|
// Copyright (c) Microsoft Corporation.
|
|
1359
1625
|
/**
|
|
1360
1626
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
@@ -1374,6 +1640,8 @@ class SpecParser {
|
|
|
1374
1640
|
allowMultipleParameters: false,
|
|
1375
1641
|
allowOauth2: false,
|
|
1376
1642
|
allowMethods: ["get", "post"],
|
|
1643
|
+
allowConversationStarters: false,
|
|
1644
|
+
allowResponseSemantics: false,
|
|
1377
1645
|
projectType: ProjectType.SME,
|
|
1378
1646
|
};
|
|
1379
1647
|
this.pathOrSpec = pathOrDoc;
|
|
@@ -1398,6 +1666,8 @@ class SpecParser {
|
|
|
1398
1666
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1399
1667
|
};
|
|
1400
1668
|
}
|
|
1669
|
+
const errors = [];
|
|
1670
|
+
const warnings = [];
|
|
1401
1671
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1402
1672
|
return {
|
|
1403
1673
|
status: ValidationStatus.Error,
|
|
@@ -1407,23 +1677,38 @@ class SpecParser {
|
|
|
1407
1677
|
],
|
|
1408
1678
|
};
|
|
1409
1679
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
1420
|
-
data: this.spec.openapi,
|
|
1421
|
-
},
|
|
1422
|
-
],
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1680
|
+
// Remote reference not supported
|
|
1681
|
+
const refPaths = this.parser.$refs.paths();
|
|
1682
|
+
// refPaths [0] is the current spec file path
|
|
1683
|
+
if (refPaths.length > 1) {
|
|
1684
|
+
errors.push({
|
|
1685
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1686
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1687
|
+
data: refPaths,
|
|
1688
|
+
});
|
|
1425
1689
|
}
|
|
1426
|
-
|
|
1690
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1691
|
+
warnings.push({
|
|
1692
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1693
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
const validator = this.getValidator(this.spec);
|
|
1697
|
+
const validationResult = validator.validateSpec();
|
|
1698
|
+
warnings.push(...validationResult.warnings);
|
|
1699
|
+
errors.push(...validationResult.errors);
|
|
1700
|
+
let status = ValidationStatus.Valid;
|
|
1701
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1702
|
+
status = ValidationStatus.Warning;
|
|
1703
|
+
}
|
|
1704
|
+
else if (errors.length > 0) {
|
|
1705
|
+
status = ValidationStatus.Error;
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
status: status,
|
|
1709
|
+
warnings: warnings,
|
|
1710
|
+
errors: errors,
|
|
1711
|
+
};
|
|
1427
1712
|
}
|
|
1428
1713
|
catch (err) {
|
|
1429
1714
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1452,34 +1737,27 @@ class SpecParser {
|
|
|
1452
1737
|
for (const apiKey in apiMap) {
|
|
1453
1738
|
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1454
1739
|
const [method, path] = apiKey.split(" ");
|
|
1740
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1455
1741
|
const apiResult = {
|
|
1456
|
-
api:
|
|
1742
|
+
api: apiKey,
|
|
1457
1743
|
server: "",
|
|
1458
|
-
operationId:
|
|
1744
|
+
operationId: operationId,
|
|
1459
1745
|
isValid: isValid,
|
|
1460
1746
|
reason: reason,
|
|
1461
1747
|
};
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
apiResult.operationId = operationId;
|
|
1475
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1476
|
-
for (const auths of authArray) {
|
|
1477
|
-
if (auths.length === 1) {
|
|
1478
|
-
apiResult.auth = auths[0];
|
|
1479
|
-
break;
|
|
1748
|
+
if (isValid) {
|
|
1749
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1750
|
+
if (serverObj) {
|
|
1751
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1752
|
+
}
|
|
1753
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1754
|
+
for (const auths of authArray) {
|
|
1755
|
+
if (auths.length === 1) {
|
|
1756
|
+
apiResult.auth = auths[0];
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1480
1759
|
}
|
|
1481
1760
|
}
|
|
1482
|
-
apiResult.api = apiKey;
|
|
1483
1761
|
result.APIs.push(apiResult);
|
|
1484
1762
|
}
|
|
1485
1763
|
result.allAPICount = result.APIs.length;
|
|
@@ -1659,12 +1937,17 @@ class SpecParser {
|
|
|
1659
1937
|
}
|
|
1660
1938
|
}
|
|
1661
1939
|
getAPIs(spec) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1940
|
+
const validator = this.getValidator(spec);
|
|
1941
|
+
const apiMap = validator.listAPIs();
|
|
1942
|
+
return apiMap;
|
|
1943
|
+
}
|
|
1944
|
+
getValidator(spec) {
|
|
1945
|
+
if (this.validator) {
|
|
1946
|
+
return this.validator;
|
|
1664
1947
|
}
|
|
1665
|
-
const
|
|
1666
|
-
this.
|
|
1667
|
-
return
|
|
1948
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1949
|
+
this.validator = validator;
|
|
1950
|
+
return validator;
|
|
1668
1951
|
}
|
|
1669
1952
|
}
|
|
1670
1953
|
|