@microsoft/m365-spec-parser 0.1.1-alpha.a277dba4e.0 → 0.1.1-alpha.ad8f60cf1.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 +606 -333
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1147 -719
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +606 -333
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1092 -660
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +3 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +65 -1
- package/dist/src/manifestUpdater.d.ts +5 -2
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +4 -2
- package/dist/src/utils.d.ts +9 -28
- package/package.json +3 -3
package/dist/index.esm2017.mjs
CHANGED
|
@@ -29,6 +29,21 @@ var ErrorType;
|
|
|
29
29
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
30
30
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
31
31
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
32
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
33
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
34
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
35
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
36
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
37
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
38
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
39
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
40
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
41
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
42
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
43
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
44
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
45
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
46
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
32
47
|
ErrorType["Cancelled"] = "cancelled";
|
|
33
48
|
ErrorType["Unknown"] = "unknown";
|
|
34
49
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -68,7 +83,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
68
83
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
69
84
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
70
85
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
71
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
86
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
72
87
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
73
88
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
74
89
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -88,9 +103,9 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
88
103
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
89
104
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
90
105
|
ConstantString.TextBlockType = "TextBlock";
|
|
106
|
+
ConstantString.ImageType = "Image";
|
|
91
107
|
ConstantString.ContainerType = "Container";
|
|
92
108
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
93
|
-
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
94
109
|
ConstantString.ResponseCodeFor20X = [
|
|
95
110
|
"200",
|
|
96
111
|
"201",
|
|
@@ -151,7 +166,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
151
166
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
152
167
|
ConstantString.CommandTitleMaxLens = 32;
|
|
153
168
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
154
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
169
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
170
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
155
171
|
|
|
156
172
|
// Copyright (c) Microsoft Corporation.
|
|
157
173
|
class SpecParserError extends Error {
|
|
@@ -174,221 +190,9 @@ class Utils {
|
|
|
174
190
|
}
|
|
175
191
|
return false;
|
|
176
192
|
}
|
|
177
|
-
static checkParameters(paramObject, isCopilot) {
|
|
178
|
-
const paramResult = {
|
|
179
|
-
requiredNum: 0,
|
|
180
|
-
optionalNum: 0,
|
|
181
|
-
isValid: true,
|
|
182
|
-
};
|
|
183
|
-
if (!paramObject) {
|
|
184
|
-
return paramResult;
|
|
185
|
-
}
|
|
186
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
187
|
-
const param = paramObject[i];
|
|
188
|
-
const schema = param.schema;
|
|
189
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
190
|
-
paramResult.isValid = false;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
194
|
-
if (isCopilot) {
|
|
195
|
-
if (isRequiredWithoutDefault) {
|
|
196
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
204
|
-
if (isRequiredWithoutDefault) {
|
|
205
|
-
paramResult.isValid = false;
|
|
206
|
-
}
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (schema.type !== "boolean" &&
|
|
210
|
-
schema.type !== "string" &&
|
|
211
|
-
schema.type !== "number" &&
|
|
212
|
-
schema.type !== "integer") {
|
|
213
|
-
if (isRequiredWithoutDefault) {
|
|
214
|
-
paramResult.isValid = false;
|
|
215
|
-
}
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (param.in === "query" || param.in === "path") {
|
|
219
|
-
if (isRequiredWithoutDefault) {
|
|
220
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return paramResult;
|
|
228
|
-
}
|
|
229
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
230
|
-
var _a;
|
|
231
|
-
const paramResult = {
|
|
232
|
-
requiredNum: 0,
|
|
233
|
-
optionalNum: 0,
|
|
234
|
-
isValid: true,
|
|
235
|
-
};
|
|
236
|
-
if (Object.keys(schema).length === 0) {
|
|
237
|
-
return paramResult;
|
|
238
|
-
}
|
|
239
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
240
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
241
|
-
paramResult.isValid = false;
|
|
242
|
-
return paramResult;
|
|
243
|
-
}
|
|
244
|
-
if (schema.type === "string" ||
|
|
245
|
-
schema.type === "integer" ||
|
|
246
|
-
schema.type === "boolean" ||
|
|
247
|
-
schema.type === "number") {
|
|
248
|
-
if (isRequiredWithoutDefault) {
|
|
249
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
else if (schema.type === "object") {
|
|
256
|
-
const { properties } = schema;
|
|
257
|
-
for (const property in properties) {
|
|
258
|
-
let isRequired = false;
|
|
259
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
260
|
-
isRequired = true;
|
|
261
|
-
}
|
|
262
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
263
|
-
paramResult.requiredNum += result.requiredNum;
|
|
264
|
-
paramResult.optionalNum += result.optionalNum;
|
|
265
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
270
|
-
paramResult.isValid = false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return paramResult;
|
|
274
|
-
}
|
|
275
193
|
static containMultipleMediaTypes(bodyObject) {
|
|
276
194
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
277
195
|
}
|
|
278
|
-
/**
|
|
279
|
-
* Checks if the given API is supported.
|
|
280
|
-
* @param {string} method - The HTTP method of the API.
|
|
281
|
-
* @param {string} path - The path of the API.
|
|
282
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
283
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
284
|
-
* @description The following APIs are supported:
|
|
285
|
-
* 1. only support Get/Post operation without auth property
|
|
286
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
287
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
288
|
-
* 4. request body + required parameters <= 1
|
|
289
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
290
|
-
* 6. only support request body with “application/json” content type
|
|
291
|
-
*/
|
|
292
|
-
static isSupportedApi(method, path, spec, options) {
|
|
293
|
-
var _a;
|
|
294
|
-
const pathObj = spec.paths[path];
|
|
295
|
-
method = method.toLocaleLowerCase();
|
|
296
|
-
if (pathObj) {
|
|
297
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
298
|
-
const securities = pathObj[method].security;
|
|
299
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
300
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
301
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
302
|
-
if (!isTeamsAi) {
|
|
303
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
304
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const operationObject = pathObj[method];
|
|
309
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
const paramObject = operationObject.parameters;
|
|
313
|
-
const requestBody = operationObject.requestBody;
|
|
314
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
315
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
319
|
-
if (Object.keys(responseJson).length === 0) {
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
|
-
// Teams AI project doesn't care about request parameters/body
|
|
323
|
-
if (isTeamsAi) {
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
let requestBodyParamResult = {
|
|
327
|
-
requiredNum: 0,
|
|
328
|
-
optionalNum: 0,
|
|
329
|
-
isValid: true,
|
|
330
|
-
};
|
|
331
|
-
if (requestJsonBody) {
|
|
332
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
333
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
337
|
-
}
|
|
338
|
-
if (!requestBodyParamResult.isValid) {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
342
|
-
if (!paramResult.isValid) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
// Copilot support arbitrary parameters
|
|
346
|
-
if (isCopilot) {
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
350
|
-
if (options.allowMultipleParameters &&
|
|
351
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
352
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
else if (requestBodyParamResult.requiredNum +
|
|
358
|
-
requestBodyParamResult.optionalNum +
|
|
359
|
-
paramResult.requiredNum +
|
|
360
|
-
paramResult.optionalNum ===
|
|
361
|
-
0) {
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
372
|
-
if (authSchemeArray.length === 0) {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
376
|
-
// Currently we don't support multiple auth in one operation
|
|
377
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
for (const auths of authSchemeArray) {
|
|
381
|
-
if (auths.length === 1) {
|
|
382
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
383
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
384
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
385
|
-
return true;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
196
|
static isBearerTokenAuth(authScheme) {
|
|
393
197
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
394
198
|
}
|
|
@@ -396,18 +200,18 @@ class Utils {
|
|
|
396
200
|
return authScheme.type === "apiKey";
|
|
397
201
|
}
|
|
398
202
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return false;
|
|
203
|
+
return !!(authScheme.type === "oauth2" &&
|
|
204
|
+
authScheme.flows &&
|
|
205
|
+
authScheme.flows.authorizationCode);
|
|
403
206
|
}
|
|
404
207
|
static getAuthArray(securities, spec) {
|
|
405
208
|
var _a;
|
|
406
209
|
const result = [];
|
|
407
210
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
211
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
212
|
+
if (securitiesArr && securitySchemas) {
|
|
213
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
214
|
+
const security = securitiesArr[i];
|
|
411
215
|
const authArray = [];
|
|
412
216
|
for (const name in security) {
|
|
413
217
|
const auth = securitySchemas[name];
|
|
@@ -424,17 +228,39 @@ class Utils {
|
|
|
424
228
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
425
229
|
return result;
|
|
426
230
|
}
|
|
231
|
+
static getAuthInfo(spec) {
|
|
232
|
+
let authInfo = undefined;
|
|
233
|
+
for (const url in spec.paths) {
|
|
234
|
+
for (const method in spec.paths[url]) {
|
|
235
|
+
const operation = spec.paths[url][method];
|
|
236
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
237
|
+
if (authArray && authArray.length > 0) {
|
|
238
|
+
const currentAuth = authArray[0][0];
|
|
239
|
+
if (!authInfo) {
|
|
240
|
+
authInfo = authArray[0][0];
|
|
241
|
+
}
|
|
242
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
243
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return authInfo;
|
|
249
|
+
}
|
|
427
250
|
static updateFirstLetter(str) {
|
|
428
251
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
429
252
|
}
|
|
430
|
-
static getResponseJson(operationObject
|
|
253
|
+
static getResponseJson(operationObject) {
|
|
431
254
|
var _a, _b;
|
|
432
255
|
let json = {};
|
|
256
|
+
let multipleMediaType = false;
|
|
433
257
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
434
258
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
435
259
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
260
|
+
multipleMediaType = false;
|
|
436
261
|
json = responseObject.content["application/json"];
|
|
437
|
-
if (
|
|
262
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
263
|
+
multipleMediaType = true;
|
|
438
264
|
json = {};
|
|
439
265
|
}
|
|
440
266
|
else {
|
|
@@ -442,7 +268,7 @@ class Utils {
|
|
|
442
268
|
}
|
|
443
269
|
}
|
|
444
270
|
}
|
|
445
|
-
return json;
|
|
271
|
+
return { json, multipleMediaType };
|
|
446
272
|
}
|
|
447
273
|
static convertPathToCamelCase(path) {
|
|
448
274
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -462,10 +288,10 @@ class Utils {
|
|
|
462
288
|
return undefined;
|
|
463
289
|
}
|
|
464
290
|
}
|
|
465
|
-
static
|
|
291
|
+
static resolveEnv(str) {
|
|
466
292
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
467
|
-
let matches = placeHolderReg.exec(
|
|
468
|
-
let
|
|
293
|
+
let matches = placeHolderReg.exec(str);
|
|
294
|
+
let newStr = str;
|
|
469
295
|
while (matches != null) {
|
|
470
296
|
const envVar = matches[1];
|
|
471
297
|
const envVal = process.env[envVar];
|
|
@@ -473,17 +299,17 @@ class Utils {
|
|
|
473
299
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
474
300
|
}
|
|
475
301
|
else {
|
|
476
|
-
|
|
302
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
477
303
|
}
|
|
478
|
-
matches = placeHolderReg.exec(
|
|
304
|
+
matches = placeHolderReg.exec(str);
|
|
479
305
|
}
|
|
480
|
-
return
|
|
306
|
+
return newStr;
|
|
481
307
|
}
|
|
482
308
|
static checkServerUrl(servers) {
|
|
483
309
|
const errors = [];
|
|
484
310
|
let serverUrl;
|
|
485
311
|
try {
|
|
486
|
-
serverUrl = Utils.
|
|
312
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
487
313
|
}
|
|
488
314
|
catch (err) {
|
|
489
315
|
errors.push({
|
|
@@ -514,6 +340,7 @@ class Utils {
|
|
|
514
340
|
return errors;
|
|
515
341
|
}
|
|
516
342
|
static validateServer(spec, options) {
|
|
343
|
+
var _a;
|
|
517
344
|
const errors = [];
|
|
518
345
|
let hasTopLevelServers = false;
|
|
519
346
|
let hasPathLevelServers = false;
|
|
@@ -534,7 +361,7 @@ class Utils {
|
|
|
534
361
|
}
|
|
535
362
|
for (const method in methods) {
|
|
536
363
|
const operationObject = methods[method];
|
|
537
|
-
if (
|
|
364
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
538
365
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
539
366
|
hasOperationLevelServers = true;
|
|
540
367
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -661,13 +488,7 @@ class Utils {
|
|
|
661
488
|
}
|
|
662
489
|
}
|
|
663
490
|
const operationId = operationItem.operationId;
|
|
664
|
-
const parameters = [];
|
|
665
|
-
if (requiredParams.length !== 0) {
|
|
666
|
-
parameters.push(...requiredParams);
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
parameters.push(optionalParams[0]);
|
|
670
|
-
}
|
|
491
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
671
492
|
const command = {
|
|
672
493
|
context: ["compose"],
|
|
673
494
|
type: "query",
|
|
@@ -676,166 +497,883 @@ class Utils {
|
|
|
676
497
|
parameters: parameters,
|
|
677
498
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
678
499
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
500
|
+
return command;
|
|
501
|
+
}
|
|
502
|
+
static format(str, ...args) {
|
|
503
|
+
let index = 0;
|
|
504
|
+
return str.replace(/%s/g, () => {
|
|
505
|
+
const arg = args[index++];
|
|
506
|
+
return arg !== undefined ? arg : "";
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
510
|
+
if (!authName) {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
514
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
515
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
686
516
|
}
|
|
687
|
-
return
|
|
517
|
+
return safeRegistrationIdEnvName;
|
|
688
518
|
}
|
|
689
|
-
static
|
|
690
|
-
const
|
|
519
|
+
static getServerObject(spec, method, path) {
|
|
520
|
+
const pathObj = spec.paths[path];
|
|
521
|
+
const operationObject = pathObj[method];
|
|
522
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
523
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
524
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
525
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
526
|
+
return serverUrl;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Copyright (c) Microsoft Corporation.
|
|
531
|
+
class Validator {
|
|
532
|
+
listAPIs() {
|
|
533
|
+
var _a;
|
|
534
|
+
if (this.apiMap) {
|
|
535
|
+
return this.apiMap;
|
|
536
|
+
}
|
|
537
|
+
const paths = this.spec.paths;
|
|
691
538
|
const result = {};
|
|
692
539
|
for (const path in paths) {
|
|
693
540
|
const methods = paths[path];
|
|
694
541
|
for (const method in methods) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
542
|
+
const operationObject = methods[method];
|
|
543
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
544
|
+
const validateResult = this.validateAPI(method, path);
|
|
545
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
546
|
+
operation: operationObject,
|
|
547
|
+
isValid: validateResult.isValid,
|
|
548
|
+
reason: validateResult.reason,
|
|
549
|
+
};
|
|
698
550
|
}
|
|
699
551
|
}
|
|
700
552
|
}
|
|
553
|
+
this.apiMap = result;
|
|
701
554
|
return result;
|
|
702
555
|
}
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
// Server validation
|
|
713
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
714
|
-
errors.push(...serverErrors);
|
|
715
|
-
// Remote reference not supported
|
|
716
|
-
const refPaths = parser.$refs.paths();
|
|
717
|
-
// refPaths [0] is the current spec file path
|
|
718
|
-
if (refPaths.length > 1) {
|
|
719
|
-
errors.push({
|
|
720
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
721
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
722
|
-
data: refPaths,
|
|
556
|
+
validateSpecVersion() {
|
|
557
|
+
const result = { errors: [], warnings: [] };
|
|
558
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
559
|
+
result.errors.push({
|
|
560
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
561
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
562
|
+
data: this.spec.openapi,
|
|
723
563
|
});
|
|
724
564
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
validateSpecServer() {
|
|
568
|
+
const result = { errors: [], warnings: [] };
|
|
569
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
570
|
+
result.errors.push(...serverErrors);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
validateSpecNoSupportAPI() {
|
|
574
|
+
const result = { errors: [], warnings: [] };
|
|
575
|
+
const apiMap = this.listAPIs();
|
|
576
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
577
|
+
if (validAPIs.length === 0) {
|
|
578
|
+
const data = [];
|
|
579
|
+
for (const key in apiMap) {
|
|
580
|
+
const { reason } = apiMap[key];
|
|
581
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
582
|
+
data.push(apiInvalidReason);
|
|
583
|
+
}
|
|
584
|
+
result.errors.push({
|
|
729
585
|
type: ErrorType.NoSupportedApi,
|
|
730
586
|
content: ConstantString.NoSupportedApi,
|
|
587
|
+
data,
|
|
731
588
|
});
|
|
732
589
|
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
validateSpecOperationId() {
|
|
593
|
+
const result = { errors: [], warnings: [] };
|
|
594
|
+
const apiMap = this.listAPIs();
|
|
733
595
|
// OperationId missing
|
|
734
596
|
const apisMissingOperationId = [];
|
|
735
597
|
for (const key in apiMap) {
|
|
736
|
-
const
|
|
737
|
-
if (!
|
|
598
|
+
const { operation } = apiMap[key];
|
|
599
|
+
if (!operation.operationId) {
|
|
738
600
|
apisMissingOperationId.push(key);
|
|
739
601
|
}
|
|
740
602
|
}
|
|
741
603
|
if (apisMissingOperationId.length > 0) {
|
|
742
|
-
warnings.push({
|
|
604
|
+
result.warnings.push({
|
|
743
605
|
type: WarningType.OperationIdMissing,
|
|
744
606
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
745
607
|
data: apisMissingOperationId,
|
|
746
608
|
});
|
|
747
609
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
610
|
+
return result;
|
|
611
|
+
}
|
|
612
|
+
validateMethodAndPath(method, path) {
|
|
613
|
+
const result = { isValid: true, reason: [] };
|
|
614
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
615
|
+
result.isValid = false;
|
|
616
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
617
|
+
return result;
|
|
751
618
|
}
|
|
752
|
-
|
|
753
|
-
|
|
619
|
+
const pathObj = this.spec.paths[path];
|
|
620
|
+
if (!pathObj || !pathObj[method]) {
|
|
621
|
+
result.isValid = false;
|
|
622
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
623
|
+
return result;
|
|
754
624
|
}
|
|
755
|
-
return
|
|
756
|
-
status,
|
|
757
|
-
warnings,
|
|
758
|
-
errors,
|
|
759
|
-
};
|
|
625
|
+
return result;
|
|
760
626
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
627
|
+
validateResponse(method, path) {
|
|
628
|
+
const result = { isValid: true, reason: [] };
|
|
629
|
+
const operationObject = this.spec.paths[path][method];
|
|
630
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
631
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
632
|
+
// only support response body only contains “application/json” content type
|
|
633
|
+
if (multipleMediaType) {
|
|
634
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
635
|
+
}
|
|
636
|
+
else if (Object.keys(json).length === 0) {
|
|
637
|
+
// response body should not be empty
|
|
638
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
767
642
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
643
|
+
validateServer(method, path) {
|
|
644
|
+
const result = { isValid: true, reason: [] };
|
|
645
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
646
|
+
if (!serverObj) {
|
|
647
|
+
// should contain server URL
|
|
648
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
771
649
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
650
|
+
else {
|
|
651
|
+
// server url should be absolute url with https protocol
|
|
652
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
653
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
775
654
|
}
|
|
776
|
-
return
|
|
655
|
+
return result;
|
|
777
656
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
count++;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
657
|
+
validateAuth(method, path) {
|
|
658
|
+
const pathObj = this.spec.paths[path];
|
|
659
|
+
const operationObject = pathObj[method];
|
|
660
|
+
const securities = operationObject.security;
|
|
661
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
662
|
+
if (authSchemeArray.length === 0) {
|
|
663
|
+
return { isValid: true, reason: [] };
|
|
788
664
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
for (const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
if (!newPaths[path]) {
|
|
806
|
-
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
807
|
-
for (const m of ConstantString.AllOperationMethods) {
|
|
808
|
-
delete newPaths[path][m];
|
|
665
|
+
if (this.options.allowAPIKeyAuth ||
|
|
666
|
+
this.options.allowOauth2 ||
|
|
667
|
+
this.options.allowBearerTokenAuth) {
|
|
668
|
+
// Currently we don't support multiple auth in one operation
|
|
669
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
670
|
+
return {
|
|
671
|
+
isValid: false,
|
|
672
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
for (const auths of authSchemeArray) {
|
|
676
|
+
if (auths.length === 1) {
|
|
677
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
678
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
679
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
680
|
+
return { isValid: true, reason: [] };
|
|
809
681
|
}
|
|
810
682
|
}
|
|
811
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
812
|
-
// Add the operationId if missing
|
|
813
|
-
if (!newPaths[path][methodName].operationId) {
|
|
814
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
815
|
-
}
|
|
816
683
|
}
|
|
817
|
-
newSpec.paths = newPaths;
|
|
818
|
-
return newSpec;
|
|
819
|
-
}
|
|
820
|
-
catch (err) {
|
|
821
|
-
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
822
684
|
}
|
|
685
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
823
686
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
687
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
688
|
+
var _a;
|
|
689
|
+
const paramResult = {
|
|
690
|
+
requiredNum: 0,
|
|
691
|
+
optionalNum: 0,
|
|
692
|
+
isValid: true,
|
|
693
|
+
reason: [],
|
|
694
|
+
};
|
|
695
|
+
if (Object.keys(schema).length === 0) {
|
|
696
|
+
return paramResult;
|
|
697
|
+
}
|
|
698
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
699
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
700
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
701
|
+
paramResult.isValid = false;
|
|
702
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
703
|
+
return paramResult;
|
|
704
|
+
}
|
|
705
|
+
if (schema.type === "string" ||
|
|
706
|
+
schema.type === "integer" ||
|
|
707
|
+
schema.type === "boolean" ||
|
|
708
|
+
schema.type === "number") {
|
|
709
|
+
if (isRequiredWithoutDefault) {
|
|
710
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (schema.type === "object") {
|
|
717
|
+
const { properties } = schema;
|
|
718
|
+
for (const property in properties) {
|
|
719
|
+
let isRequired = false;
|
|
720
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
721
|
+
isRequired = true;
|
|
722
|
+
}
|
|
723
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
724
|
+
paramResult.requiredNum += result.requiredNum;
|
|
725
|
+
paramResult.optionalNum += result.optionalNum;
|
|
726
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
727
|
+
paramResult.reason.push(...result.reason);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
732
|
+
paramResult.isValid = false;
|
|
733
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return paramResult;
|
|
737
|
+
}
|
|
738
|
+
checkParamSchema(paramObject) {
|
|
739
|
+
const paramResult = {
|
|
740
|
+
requiredNum: 0,
|
|
741
|
+
optionalNum: 0,
|
|
742
|
+
isValid: true,
|
|
743
|
+
reason: [],
|
|
744
|
+
};
|
|
745
|
+
if (!paramObject) {
|
|
746
|
+
return paramResult;
|
|
747
|
+
}
|
|
748
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
749
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
750
|
+
const param = paramObject[i];
|
|
751
|
+
const schema = param.schema;
|
|
752
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
753
|
+
paramResult.isValid = false;
|
|
754
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
758
|
+
if (isCopilot) {
|
|
759
|
+
if (isRequiredWithoutDefault) {
|
|
760
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
768
|
+
if (isRequiredWithoutDefault) {
|
|
769
|
+
paramResult.isValid = false;
|
|
770
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
771
|
+
}
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (schema.type !== "boolean" &&
|
|
775
|
+
schema.type !== "string" &&
|
|
776
|
+
schema.type !== "number" &&
|
|
777
|
+
schema.type !== "integer") {
|
|
778
|
+
if (isRequiredWithoutDefault) {
|
|
779
|
+
paramResult.isValid = false;
|
|
780
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
781
|
+
}
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (param.in === "query" || param.in === "path") {
|
|
785
|
+
if (isRequiredWithoutDefault) {
|
|
786
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return paramResult;
|
|
794
|
+
}
|
|
795
|
+
hasNestedObjectInSchema(schema) {
|
|
796
|
+
if (schema.type === "object") {
|
|
797
|
+
for (const property in schema.properties) {
|
|
798
|
+
const nestedSchema = schema.properties[property];
|
|
799
|
+
if (nestedSchema.type === "object") {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Copyright (c) Microsoft Corporation.
|
|
809
|
+
class CopilotValidator extends Validator {
|
|
810
|
+
constructor(spec, options) {
|
|
811
|
+
super();
|
|
812
|
+
this.projectType = ProjectType.Copilot;
|
|
813
|
+
this.options = options;
|
|
814
|
+
this.spec = spec;
|
|
815
|
+
}
|
|
816
|
+
validateSpec() {
|
|
817
|
+
const result = { errors: [], warnings: [] };
|
|
818
|
+
// validate spec version
|
|
819
|
+
let validationResult = this.validateSpecVersion();
|
|
820
|
+
result.errors.push(...validationResult.errors);
|
|
821
|
+
// validate spec server
|
|
822
|
+
validationResult = this.validateSpecServer();
|
|
823
|
+
result.errors.push(...validationResult.errors);
|
|
824
|
+
// validate no supported API
|
|
825
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
826
|
+
result.errors.push(...validationResult.errors);
|
|
827
|
+
// validate operationId missing
|
|
828
|
+
validationResult = this.validateSpecOperationId();
|
|
829
|
+
result.warnings.push(...validationResult.warnings);
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
validateAPI(method, path) {
|
|
833
|
+
const result = { isValid: true, reason: [] };
|
|
834
|
+
method = method.toLocaleLowerCase();
|
|
835
|
+
// validate method and path
|
|
836
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
837
|
+
if (!methodAndPathResult.isValid) {
|
|
838
|
+
return methodAndPathResult;
|
|
839
|
+
}
|
|
840
|
+
const operationObject = this.spec.paths[path][method];
|
|
841
|
+
// validate auth
|
|
842
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
843
|
+
result.reason.push(...authCheckResult.reason);
|
|
844
|
+
// validate operationId
|
|
845
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
846
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
847
|
+
}
|
|
848
|
+
// validate server
|
|
849
|
+
const validateServerResult = this.validateServer(method, path);
|
|
850
|
+
result.reason.push(...validateServerResult.reason);
|
|
851
|
+
// validate response
|
|
852
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
853
|
+
result.reason.push(...validateResponseResult.reason);
|
|
854
|
+
// validate requestBody
|
|
855
|
+
const requestBody = operationObject.requestBody;
|
|
856
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
857
|
+
if (requestJsonBody) {
|
|
858
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
859
|
+
if (requestBodySchema.type !== "object") {
|
|
860
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
861
|
+
}
|
|
862
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
863
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
864
|
+
}
|
|
865
|
+
// validate parameters
|
|
866
|
+
const paramObject = operationObject.parameters;
|
|
867
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
868
|
+
result.reason.push(...paramResult.reason);
|
|
869
|
+
if (result.reason.length > 0) {
|
|
870
|
+
result.isValid = false;
|
|
871
|
+
}
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Copyright (c) Microsoft Corporation.
|
|
877
|
+
class SMEValidator extends Validator {
|
|
878
|
+
constructor(spec, options) {
|
|
879
|
+
super();
|
|
880
|
+
this.projectType = ProjectType.SME;
|
|
881
|
+
this.options = options;
|
|
882
|
+
this.spec = spec;
|
|
883
|
+
}
|
|
884
|
+
validateSpec() {
|
|
885
|
+
const result = { errors: [], warnings: [] };
|
|
886
|
+
// validate spec version
|
|
887
|
+
let validationResult = this.validateSpecVersion();
|
|
888
|
+
result.errors.push(...validationResult.errors);
|
|
889
|
+
// validate spec server
|
|
890
|
+
validationResult = this.validateSpecServer();
|
|
891
|
+
result.errors.push(...validationResult.errors);
|
|
892
|
+
// validate no supported API
|
|
893
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
894
|
+
result.errors.push(...validationResult.errors);
|
|
895
|
+
// validate operationId missing
|
|
896
|
+
if (this.options.allowMissingId) {
|
|
897
|
+
validationResult = this.validateSpecOperationId();
|
|
898
|
+
result.warnings.push(...validationResult.warnings);
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
validateAPI(method, path) {
|
|
903
|
+
const result = { isValid: true, reason: [] };
|
|
904
|
+
method = method.toLocaleLowerCase();
|
|
905
|
+
// validate method and path
|
|
906
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
907
|
+
if (!methodAndPathResult.isValid) {
|
|
908
|
+
return methodAndPathResult;
|
|
909
|
+
}
|
|
910
|
+
const operationObject = this.spec.paths[path][method];
|
|
911
|
+
// validate auth
|
|
912
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
913
|
+
result.reason.push(...authCheckResult.reason);
|
|
914
|
+
// validate operationId
|
|
915
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
916
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
917
|
+
}
|
|
918
|
+
// validate server
|
|
919
|
+
const validateServerResult = this.validateServer(method, path);
|
|
920
|
+
result.reason.push(...validateServerResult.reason);
|
|
921
|
+
// validate response
|
|
922
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
923
|
+
result.reason.push(...validateResponseResult.reason);
|
|
924
|
+
let postBodyResult = {
|
|
925
|
+
requiredNum: 0,
|
|
926
|
+
optionalNum: 0,
|
|
927
|
+
isValid: true,
|
|
928
|
+
reason: [],
|
|
929
|
+
};
|
|
930
|
+
// validate requestBody
|
|
931
|
+
const requestBody = operationObject.requestBody;
|
|
932
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
933
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
934
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
935
|
+
}
|
|
936
|
+
if (requestJsonBody) {
|
|
937
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
938
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
939
|
+
result.reason.push(...postBodyResult.reason);
|
|
940
|
+
}
|
|
941
|
+
// validate parameters
|
|
942
|
+
const paramObject = operationObject.parameters;
|
|
943
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
944
|
+
result.reason.push(...paramResult.reason);
|
|
945
|
+
// validate total parameters count
|
|
946
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
947
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
948
|
+
result.reason.push(...paramCountResult.reason);
|
|
949
|
+
}
|
|
950
|
+
if (result.reason.length > 0) {
|
|
951
|
+
result.isValid = false;
|
|
952
|
+
}
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
956
|
+
const result = { isValid: true, reason: [] };
|
|
957
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
958
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
959
|
+
if (totalRequiredParams > 1) {
|
|
960
|
+
if (!this.options.allowMultipleParameters ||
|
|
961
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
962
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
else if (totalParams === 0) {
|
|
966
|
+
result.reason.push(ErrorType.NoParameter);
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
972
|
+
|
|
973
|
+
// Copyright (c) Microsoft Corporation.
|
|
974
|
+
class TeamsAIValidator extends Validator {
|
|
975
|
+
constructor(spec, options) {
|
|
976
|
+
super();
|
|
977
|
+
this.projectType = ProjectType.TeamsAi;
|
|
978
|
+
this.options = options;
|
|
979
|
+
this.spec = spec;
|
|
980
|
+
}
|
|
981
|
+
validateSpec() {
|
|
982
|
+
const result = { errors: [], warnings: [] };
|
|
983
|
+
// validate spec server
|
|
984
|
+
let validationResult = this.validateSpecServer();
|
|
985
|
+
result.errors.push(...validationResult.errors);
|
|
986
|
+
// validate no supported API
|
|
987
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
988
|
+
result.errors.push(...validationResult.errors);
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
validateAPI(method, path) {
|
|
992
|
+
const result = { isValid: true, reason: [] };
|
|
993
|
+
method = method.toLocaleLowerCase();
|
|
994
|
+
// validate method and path
|
|
995
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
996
|
+
if (!methodAndPathResult.isValid) {
|
|
997
|
+
return methodAndPathResult;
|
|
998
|
+
}
|
|
999
|
+
const operationObject = this.spec.paths[path][method];
|
|
1000
|
+
// validate operationId
|
|
1001
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1002
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1003
|
+
}
|
|
1004
|
+
// validate server
|
|
1005
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1006
|
+
result.reason.push(...validateServerResult.reason);
|
|
1007
|
+
if (result.reason.length > 0) {
|
|
1008
|
+
result.isValid = false;
|
|
1009
|
+
}
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
class ValidatorFactory {
|
|
1015
|
+
static create(spec, options) {
|
|
1016
|
+
var _a;
|
|
1017
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1018
|
+
switch (type) {
|
|
1019
|
+
case ProjectType.SME:
|
|
1020
|
+
return new SMEValidator(spec, options);
|
|
1021
|
+
case ProjectType.Copilot:
|
|
1022
|
+
return new CopilotValidator(spec, options);
|
|
1023
|
+
case ProjectType.TeamsAi:
|
|
1024
|
+
return new TeamsAIValidator(spec, options);
|
|
1025
|
+
default:
|
|
1026
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Copyright (c) Microsoft Corporation.
|
|
1032
|
+
class SpecFilter {
|
|
1033
|
+
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1034
|
+
var _a;
|
|
1035
|
+
try {
|
|
1036
|
+
const newSpec = Object.assign({}, unResolveSpec);
|
|
1037
|
+
const newPaths = {};
|
|
1038
|
+
for (const filterItem of filter) {
|
|
1039
|
+
const [method, path] = filterItem.split(" ");
|
|
1040
|
+
const methodName = method.toLowerCase();
|
|
1041
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1042
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1043
|
+
pathObj &&
|
|
1044
|
+
pathObj[methodName]) {
|
|
1045
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1046
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1047
|
+
if (!validateResult.isValid) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (!newPaths[path]) {
|
|
1051
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1052
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1053
|
+
delete newPaths[path][m];
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1057
|
+
// Add the operationId if missing
|
|
1058
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1059
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
newSpec.paths = newPaths;
|
|
1064
|
+
return newSpec;
|
|
1065
|
+
}
|
|
1066
|
+
catch (err) {
|
|
1067
|
+
throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Copyright (c) Microsoft Corporation.
|
|
1073
|
+
class AdaptiveCardGenerator {
|
|
1074
|
+
static generateAdaptiveCard(operationItem) {
|
|
1075
|
+
try {
|
|
1076
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1077
|
+
let cardBody = [];
|
|
1078
|
+
let schema = json.schema;
|
|
1079
|
+
let jsonPath = "$";
|
|
1080
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
1081
|
+
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1082
|
+
if (jsonPath !== "$") {
|
|
1083
|
+
schema = schema.properties[jsonPath];
|
|
1084
|
+
}
|
|
1085
|
+
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1086
|
+
}
|
|
1087
|
+
// if no schema, try to use example value
|
|
1088
|
+
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1089
|
+
cardBody = [
|
|
1090
|
+
{
|
|
1091
|
+
type: ConstantString.TextBlockType,
|
|
1092
|
+
text: "${jsonStringify($root)}",
|
|
1093
|
+
wrap: true,
|
|
1094
|
+
},
|
|
1095
|
+
];
|
|
1096
|
+
}
|
|
1097
|
+
// if no example value, use default success response
|
|
1098
|
+
if (cardBody.length === 0) {
|
|
1099
|
+
cardBody = [
|
|
1100
|
+
{
|
|
1101
|
+
type: ConstantString.TextBlockType,
|
|
1102
|
+
text: "success",
|
|
1103
|
+
wrap: true,
|
|
1104
|
+
},
|
|
1105
|
+
];
|
|
1106
|
+
}
|
|
1107
|
+
const fullCard = {
|
|
1108
|
+
type: ConstantString.AdaptiveCardType,
|
|
1109
|
+
$schema: ConstantString.AdaptiveCardSchema,
|
|
1110
|
+
version: ConstantString.AdaptiveCardVersion,
|
|
1111
|
+
body: cardBody,
|
|
1112
|
+
};
|
|
1113
|
+
return [fullCard, jsonPath];
|
|
1114
|
+
}
|
|
1115
|
+
catch (err) {
|
|
1116
|
+
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1120
|
+
if (schema.type === "array") {
|
|
1121
|
+
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1122
|
+
if (Object.keys(schema.items).length === 0) {
|
|
1123
|
+
return [
|
|
1124
|
+
{
|
|
1125
|
+
type: ConstantString.TextBlockType,
|
|
1126
|
+
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1127
|
+
wrap: true,
|
|
1128
|
+
},
|
|
1129
|
+
];
|
|
1130
|
+
}
|
|
1131
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1132
|
+
const template = {
|
|
1133
|
+
type: ConstantString.ContainerType,
|
|
1134
|
+
$data: name ? `\${${name}}` : "${$root}",
|
|
1135
|
+
items: Array(),
|
|
1136
|
+
};
|
|
1137
|
+
template.items.push(...obj);
|
|
1138
|
+
return [template];
|
|
1139
|
+
}
|
|
1140
|
+
// some schema may not contain type but contain properties
|
|
1141
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1142
|
+
const { properties } = schema;
|
|
1143
|
+
const result = [];
|
|
1144
|
+
for (const property in properties) {
|
|
1145
|
+
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1146
|
+
result.push(...obj);
|
|
1147
|
+
}
|
|
1148
|
+
if (schema.additionalProperties) {
|
|
1149
|
+
// TODO: better ways to handler warnings.
|
|
1150
|
+
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1151
|
+
}
|
|
1152
|
+
return result;
|
|
1153
|
+
}
|
|
1154
|
+
if (schema.type === "string" ||
|
|
1155
|
+
schema.type === "integer" ||
|
|
1156
|
+
schema.type === "boolean" ||
|
|
1157
|
+
schema.type === "number") {
|
|
1158
|
+
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1159
|
+
// string in root: "ddd"
|
|
1160
|
+
let text = "result: ${$root}";
|
|
1161
|
+
if (name) {
|
|
1162
|
+
// object { id: "1" }
|
|
1163
|
+
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1164
|
+
if (parentArrayName) {
|
|
1165
|
+
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1166
|
+
text = `${parentArrayName}.${text}`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
else if (parentArrayName) {
|
|
1170
|
+
// string array: photoUrls: ["1", "2"]
|
|
1171
|
+
text = `${parentArrayName}: ` + "${$data}";
|
|
1172
|
+
}
|
|
1173
|
+
return [
|
|
1174
|
+
{
|
|
1175
|
+
type: ConstantString.TextBlockType,
|
|
1176
|
+
text,
|
|
1177
|
+
wrap: true,
|
|
1178
|
+
},
|
|
1179
|
+
];
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
if (name) {
|
|
1183
|
+
return [
|
|
1184
|
+
{
|
|
1185
|
+
type: "Image",
|
|
1186
|
+
url: `\${${name}}`,
|
|
1187
|
+
$when: `\${${name} != null}`,
|
|
1188
|
+
},
|
|
1189
|
+
];
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
return [
|
|
1193
|
+
{
|
|
1194
|
+
type: "Image",
|
|
1195
|
+
url: "${$data}",
|
|
1196
|
+
$when: "${$data != null}",
|
|
1197
|
+
},
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1203
|
+
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1204
|
+
}
|
|
1205
|
+
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1206
|
+
}
|
|
1207
|
+
// Find the first array property in the response schema object with the well-known name
|
|
1208
|
+
static getResponseJsonPathFromSchema(schema) {
|
|
1209
|
+
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1210
|
+
const { properties } = schema;
|
|
1211
|
+
for (const property in properties) {
|
|
1212
|
+
const schema = properties[property];
|
|
1213
|
+
if (schema.type === "array" &&
|
|
1214
|
+
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1215
|
+
return property;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return "$";
|
|
1220
|
+
}
|
|
1221
|
+
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1222
|
+
const propertyName = name ? name : parentArrayName;
|
|
1223
|
+
return (!!propertyName &&
|
|
1224
|
+
schema.type === "string" &&
|
|
1225
|
+
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1226
|
+
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Copyright (c) Microsoft Corporation.
|
|
1231
|
+
function wrapAdaptiveCard(card, jsonPath) {
|
|
1232
|
+
const result = {
|
|
1233
|
+
version: ConstantString.WrappedCardVersion,
|
|
1234
|
+
$schema: ConstantString.WrappedCardSchema,
|
|
1235
|
+
jsonPath: jsonPath,
|
|
1236
|
+
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1237
|
+
responseCardTemplate: card,
|
|
1238
|
+
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1239
|
+
};
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1242
|
+
function wrapResponseSemantics(card, jsonPath) {
|
|
1243
|
+
const props = inferProperties(card);
|
|
1244
|
+
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
|
|
1245
|
+
const result = {
|
|
1246
|
+
data_path: dataPath,
|
|
1247
|
+
};
|
|
1248
|
+
if (props.title || props.subtitle || props.imageUrl) {
|
|
1249
|
+
result.properties = {};
|
|
1250
|
+
if (props.title) {
|
|
1251
|
+
result.properties.title = "$." + props.title;
|
|
1252
|
+
}
|
|
1253
|
+
if (props.subtitle) {
|
|
1254
|
+
result.properties.subtitle = "$." + props.subtitle;
|
|
1255
|
+
}
|
|
1256
|
+
if (props.imageUrl) {
|
|
1257
|
+
result.properties.url = "$." + props.imageUrl;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
result.static_template = card;
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1265
|
+
* The preview card template includes a title and an optional subtitle and image.
|
|
1266
|
+
* It populates the preview card template with the first text block that matches
|
|
1267
|
+
* each well-known name, in the order of title, subtitle, and image.
|
|
1268
|
+
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1269
|
+
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1270
|
+
* @param card The Adaptive Card to infer the preview card template from.
|
|
1271
|
+
* @param jsonPath The JSON path to the root object in the card body.
|
|
1272
|
+
* @returns The inferred preview card template.
|
|
1273
|
+
*/
|
|
1274
|
+
function inferPreviewCardTemplate(card) {
|
|
1275
|
+
const result = {
|
|
1276
|
+
title: "result",
|
|
1277
|
+
};
|
|
1278
|
+
const inferredProperties = inferProperties(card);
|
|
1279
|
+
if (inferredProperties.title) {
|
|
1280
|
+
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
|
|
1281
|
+
}
|
|
1282
|
+
if (inferredProperties.subtitle) {
|
|
1283
|
+
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
|
|
1284
|
+
}
|
|
1285
|
+
if (inferredProperties.imageUrl) {
|
|
1286
|
+
result.image = {
|
|
1287
|
+
url: `\${${inferredProperties.imageUrl}}`,
|
|
1288
|
+
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
|
|
1289
|
+
$when: `\${${inferredProperties.imageUrl} != null}`,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return result;
|
|
1293
|
+
}
|
|
1294
|
+
function inferProperties(card) {
|
|
1295
|
+
var _a;
|
|
1296
|
+
const result = {};
|
|
1297
|
+
const nameSet = new Set();
|
|
1298
|
+
let rootObject;
|
|
1299
|
+
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1300
|
+
rootObject = card.body[0].items;
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
rootObject = card.body;
|
|
1304
|
+
}
|
|
1305
|
+
for (const element of rootObject) {
|
|
1306
|
+
if (element.type === ConstantString.TextBlockType) {
|
|
1307
|
+
const textElement = element;
|
|
1308
|
+
const index = textElement.text.indexOf("${if(");
|
|
1309
|
+
if (index > 0) {
|
|
1310
|
+
const text = textElement.text.substring(index);
|
|
1311
|
+
const match = text.match(/\${if\(([^,]+),/);
|
|
1312
|
+
const property = match ? match[1] : "";
|
|
1313
|
+
if (property) {
|
|
1314
|
+
nameSet.add(property);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else if (element.type === ConstantString.ImageType) {
|
|
1319
|
+
const imageElement = element;
|
|
1320
|
+
const match = imageElement.url.match(/\${([^,]+)}/);
|
|
1321
|
+
const property = match ? match[1] : "";
|
|
1322
|
+
if (property) {
|
|
1323
|
+
nameSet.add(property);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
for (const name of nameSet) {
|
|
1328
|
+
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
|
|
1329
|
+
result.title = name;
|
|
1330
|
+
nameSet.delete(name);
|
|
1331
|
+
}
|
|
1332
|
+
else if (!result.subtitle &&
|
|
1333
|
+
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
|
|
1334
|
+
result.subtitle = name;
|
|
1335
|
+
nameSet.delete(name);
|
|
1336
|
+
}
|
|
1337
|
+
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
|
|
1338
|
+
result.imageUrl = name;
|
|
1339
|
+
nameSet.delete(name);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const name of nameSet) {
|
|
1343
|
+
if (!result.title) {
|
|
1344
|
+
result.title = name;
|
|
1345
|
+
nameSet.delete(name);
|
|
1346
|
+
}
|
|
1347
|
+
else if (!result.subtitle) {
|
|
1348
|
+
result.subtitle = name;
|
|
1349
|
+
nameSet.delete(name);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (!result.title && result.subtitle) {
|
|
1353
|
+
result.title = result.subtitle;
|
|
1354
|
+
delete result.subtitle;
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Copyright (c) Microsoft Corporation.
|
|
1360
|
+
class ManifestUpdater {
|
|
1361
|
+
static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
|
|
1362
|
+
const manifest = await fs.readJSON(manifestPath);
|
|
1363
|
+
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
1364
|
+
// Insert plugins in manifest.json if it is plugin for Copilot.
|
|
1365
|
+
if (!options.isGptPlugin) {
|
|
1366
|
+
manifest.plugins = [
|
|
1367
|
+
{
|
|
1368
|
+
file: apiPluginRelativePath,
|
|
1369
|
+
id: ConstantString.DefaultPluginId,
|
|
1370
|
+
},
|
|
1371
|
+
];
|
|
1372
|
+
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
1373
|
+
}
|
|
1374
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
1375
|
+
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
1376
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
|
|
839
1377
|
return [manifest, apiPlugin];
|
|
840
1378
|
}
|
|
841
1379
|
static updateManifestDescription(manifest, spec) {
|
|
@@ -847,23 +1385,56 @@ class ManifestUpdater {
|
|
|
847
1385
|
}
|
|
848
1386
|
static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
|
|
849
1387
|
let parameter;
|
|
850
|
-
if (schema.type === "
|
|
1388
|
+
if (schema.type === "array") {
|
|
1389
|
+
const items = schema.items;
|
|
1390
|
+
parameter = {
|
|
1391
|
+
type: "array",
|
|
1392
|
+
items: ManifestUpdater.mapOpenAPISchemaToFuncParam(items, method, pathUrl),
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
else if (schema.type === "string" ||
|
|
851
1396
|
schema.type === "boolean" ||
|
|
852
1397
|
schema.type === "integer" ||
|
|
853
|
-
schema.type === "number"
|
|
854
|
-
|
|
855
|
-
|
|
1398
|
+
schema.type === "number") {
|
|
1399
|
+
parameter = {
|
|
1400
|
+
type: schema.type,
|
|
1401
|
+
};
|
|
856
1402
|
}
|
|
857
1403
|
else {
|
|
858
1404
|
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
|
|
859
1405
|
}
|
|
1406
|
+
if (schema.enum) {
|
|
1407
|
+
parameter.enum = schema.enum;
|
|
1408
|
+
}
|
|
1409
|
+
if (schema.description) {
|
|
1410
|
+
parameter.description = schema.description;
|
|
1411
|
+
}
|
|
1412
|
+
if (schema.default) {
|
|
1413
|
+
parameter.default = schema.default;
|
|
1414
|
+
}
|
|
860
1415
|
return parameter;
|
|
861
1416
|
}
|
|
862
|
-
static generatePluginManifestSchema(spec, specRelativePath, options) {
|
|
863
|
-
var _a, _b, _c;
|
|
1417
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
|
|
1418
|
+
var _a, _b, _c, _d, _e;
|
|
864
1419
|
const functions = [];
|
|
865
1420
|
const functionNames = [];
|
|
1421
|
+
const conversationStarters = [];
|
|
866
1422
|
const paths = spec.paths;
|
|
1423
|
+
const pluginAuthObj = {
|
|
1424
|
+
type: "None",
|
|
1425
|
+
};
|
|
1426
|
+
if (authInfo) {
|
|
1427
|
+
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
|
|
1428
|
+
pluginAuthObj.type = "OAuthPluginVault";
|
|
1429
|
+
}
|
|
1430
|
+
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
|
|
1431
|
+
pluginAuthObj.type = "ApiKeyPluginVault";
|
|
1432
|
+
}
|
|
1433
|
+
if (pluginAuthObj.type !== "None") {
|
|
1434
|
+
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
1435
|
+
pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
867
1438
|
for (const pathUrl in paths) {
|
|
868
1439
|
const pathItem = paths[pathUrl];
|
|
869
1440
|
if (pathItem) {
|
|
@@ -871,6 +1442,7 @@ class ManifestUpdater {
|
|
|
871
1442
|
for (const method in operations) {
|
|
872
1443
|
if (options.allowMethods.includes(method)) {
|
|
873
1444
|
const operationItem = operations[method];
|
|
1445
|
+
const confirmationBodies = [];
|
|
874
1446
|
if (operationItem) {
|
|
875
1447
|
const operationId = operationItem.operationId;
|
|
876
1448
|
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
|
|
@@ -886,6 +1458,7 @@ class ManifestUpdater {
|
|
|
886
1458
|
const param = paramObject[i];
|
|
887
1459
|
const schema = param.schema;
|
|
888
1460
|
parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1461
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
|
|
889
1462
|
if (param.required) {
|
|
890
1463
|
parameters.required.push(param.name);
|
|
891
1464
|
}
|
|
@@ -904,6 +1477,7 @@ class ManifestUpdater {
|
|
|
904
1477
|
for (const property in requestBodySchema.properties) {
|
|
905
1478
|
const schema = requestBodySchema.properties[property];
|
|
906
1479
|
parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
|
|
1480
|
+
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
|
|
907
1481
|
}
|
|
908
1482
|
}
|
|
909
1483
|
else {
|
|
@@ -913,33 +1487,108 @@ class ManifestUpdater {
|
|
|
913
1487
|
const funcObj = {
|
|
914
1488
|
name: operationId,
|
|
915
1489
|
description: description,
|
|
916
|
-
parameters: parameters,
|
|
917
1490
|
};
|
|
1491
|
+
if (paramObject || requestBody) {
|
|
1492
|
+
funcObj.parameters = parameters;
|
|
1493
|
+
}
|
|
1494
|
+
if (options.allowResponseSemantics) {
|
|
1495
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1496
|
+
if (json.schema) {
|
|
1497
|
+
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
|
|
1498
|
+
const responseSemantic = wrapResponseSemantics(card, jsonPath);
|
|
1499
|
+
funcObj.capabilities = {
|
|
1500
|
+
response_semantics: responseSemantic,
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
|
|
1505
|
+
if (!funcObj.capabilities) {
|
|
1506
|
+
funcObj.capabilities = {};
|
|
1507
|
+
}
|
|
1508
|
+
funcObj.capabilities.confirmation = {
|
|
1509
|
+
type: "AdaptiveCard",
|
|
1510
|
+
title: (_c = operationItem.summary) !== null && _c !== void 0 ? _c : description,
|
|
1511
|
+
};
|
|
1512
|
+
if (confirmationBodies.length > 0) {
|
|
1513
|
+
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
918
1516
|
functions.push(funcObj);
|
|
919
1517
|
functionNames.push(operationId);
|
|
1518
|
+
if (description) {
|
|
1519
|
+
conversationStarters.push(description);
|
|
1520
|
+
}
|
|
920
1521
|
}
|
|
921
1522
|
}
|
|
922
1523
|
}
|
|
923
1524
|
}
|
|
924
1525
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1526
|
+
let apiPlugin;
|
|
1527
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1528
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
apiPlugin = {
|
|
1532
|
+
schema_version: "v2.1",
|
|
1533
|
+
name_for_human: "",
|
|
1534
|
+
description_for_human: "",
|
|
1535
|
+
namespace: "",
|
|
1536
|
+
functions: [],
|
|
1537
|
+
runtimes: [],
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1541
|
+
for (const func of functions) {
|
|
1542
|
+
const index = (_d = apiPlugin.functions) === null || _d === void 0 ? void 0 : _d.findIndex((f) => f.name === func.name);
|
|
1543
|
+
if (index === -1) {
|
|
1544
|
+
apiPlugin.functions.push(func);
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
apiPlugin.functions[index] = func;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1551
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => {
|
|
1552
|
+
var _a, _b;
|
|
1553
|
+
return runtime.spec.url === specRelativePath &&
|
|
1554
|
+
runtime.type === "OpenApi" &&
|
|
1555
|
+
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
|
|
1556
|
+
});
|
|
1557
|
+
if (index === -1) {
|
|
1558
|
+
apiPlugin.runtimes.push({
|
|
1559
|
+
type: "OpenApi",
|
|
1560
|
+
auth: pluginAuthObj,
|
|
1561
|
+
spec: {
|
|
1562
|
+
url: specRelativePath,
|
|
940
1563
|
},
|
|
941
|
-
|
|
942
|
-
|
|
1564
|
+
run_for_functions: functionNames,
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1569
|
+
}
|
|
1570
|
+
if (!apiPlugin.name_for_human) {
|
|
1571
|
+
apiPlugin.name_for_human = appName;
|
|
1572
|
+
}
|
|
1573
|
+
if (!apiPlugin.namespace) {
|
|
1574
|
+
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
|
|
1575
|
+
}
|
|
1576
|
+
if (!apiPlugin.description_for_human) {
|
|
1577
|
+
apiPlugin.description_for_human =
|
|
1578
|
+
(_e = spec.info.description) !== null && _e !== void 0 ? _e : "<Please add description of the plugin>";
|
|
1579
|
+
}
|
|
1580
|
+
if (options.allowConversationStarters && conversationStarters.length > 0) {
|
|
1581
|
+
if (!apiPlugin.capabilities) {
|
|
1582
|
+
apiPlugin.capabilities = {
|
|
1583
|
+
localization: {},
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
if (!apiPlugin.capabilities.conversation_starters) {
|
|
1587
|
+
apiPlugin.capabilities.conversation_starters = conversationStarters
|
|
1588
|
+
.slice(0, 5)
|
|
1589
|
+
.map((text) => ({ text }));
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
943
1592
|
return apiPlugin;
|
|
944
1593
|
}
|
|
945
1594
|
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
@@ -959,21 +1608,21 @@ class ManifestUpdater {
|
|
|
959
1608
|
};
|
|
960
1609
|
if (authInfo) {
|
|
961
1610
|
const auth = authInfo.authScheme;
|
|
1611
|
+
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
962
1612
|
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
|
|
963
1613
|
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
|
|
964
1614
|
composeExtension.authorization = {
|
|
965
1615
|
authType: "apiSecretServiceAuth",
|
|
966
1616
|
apiSecretServiceAuthConfiguration: {
|
|
967
|
-
apiSecretRegistrationId: `\${{${
|
|
1617
|
+
apiSecretRegistrationId: `\${{${safeRegistrationIdName}}}`,
|
|
968
1618
|
},
|
|
969
1619
|
};
|
|
970
1620
|
}
|
|
971
1621
|
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
|
|
972
|
-
const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
|
|
973
1622
|
composeExtension.authorization = {
|
|
974
1623
|
authType: "oAuth2.0",
|
|
975
1624
|
oAuthConfiguration: {
|
|
976
|
-
oauthConfigurationId: `\${{${
|
|
1625
|
+
oauthConfigurationId: `\${{${safeRegistrationIdName}}}`,
|
|
977
1626
|
},
|
|
978
1627
|
};
|
|
979
1628
|
updatedPart.webApplicationInfo = {
|
|
@@ -1008,16 +1657,26 @@ class ManifestUpdater {
|
|
|
1008
1657
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1009
1658
|
const operationItem = operations[method];
|
|
1010
1659
|
if (operationItem) {
|
|
1011
|
-
const
|
|
1660
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1661
|
+
if (command.parameters &&
|
|
1662
|
+
command.parameters.length >= 1 &&
|
|
1663
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1664
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1665
|
+
}
|
|
1666
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1667
|
+
command.parameters = [command.parameters[0]];
|
|
1668
|
+
warnings.push({
|
|
1669
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1670
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1671
|
+
data: command.id,
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1012
1674
|
if (adaptiveCardFolder) {
|
|
1013
1675
|
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1014
1676
|
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1015
1677
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1016
1678
|
: "";
|
|
1017
1679
|
}
|
|
1018
|
-
if (warning) {
|
|
1019
|
-
warnings.push(warning);
|
|
1020
|
-
}
|
|
1021
1680
|
commands.push(command);
|
|
1022
1681
|
}
|
|
1023
1682
|
}
|
|
@@ -1031,255 +1690,21 @@ class ManifestUpdater {
|
|
|
1031
1690
|
const relativePath = path.relative(path.dirname(from), to);
|
|
1032
1691
|
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1033
1692
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1041
|
-
let cardBody = [];
|
|
1042
|
-
let schema = json.schema;
|
|
1043
|
-
let jsonPath = "$";
|
|
1044
|
-
if (schema && Object.keys(schema).length > 0) {
|
|
1045
|
-
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
1046
|
-
if (jsonPath !== "$") {
|
|
1047
|
-
schema = schema.properties[jsonPath];
|
|
1048
|
-
}
|
|
1049
|
-
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
1050
|
-
}
|
|
1051
|
-
// if no schema, try to use example value
|
|
1052
|
-
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
1053
|
-
cardBody = [
|
|
1054
|
-
{
|
|
1055
|
-
type: ConstantString.TextBlockType,
|
|
1056
|
-
text: "${jsonStringify($root)}",
|
|
1057
|
-
wrap: true,
|
|
1058
|
-
},
|
|
1059
|
-
];
|
|
1060
|
-
}
|
|
1061
|
-
// if no example value, use default success response
|
|
1062
|
-
if (cardBody.length === 0) {
|
|
1063
|
-
cardBody = [
|
|
1064
|
-
{
|
|
1065
|
-
type: ConstantString.TextBlockType,
|
|
1066
|
-
text: "success",
|
|
1067
|
-
wrap: true,
|
|
1068
|
-
},
|
|
1069
|
-
];
|
|
1070
|
-
}
|
|
1071
|
-
const fullCard = {
|
|
1072
|
-
type: ConstantString.AdaptiveCardType,
|
|
1073
|
-
$schema: ConstantString.AdaptiveCardSchema,
|
|
1074
|
-
version: ConstantString.AdaptiveCardVersion,
|
|
1075
|
-
body: cardBody,
|
|
1076
|
-
};
|
|
1077
|
-
return [fullCard, jsonPath];
|
|
1078
|
-
}
|
|
1079
|
-
catch (err) {
|
|
1080
|
-
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
1084
|
-
if (schema.type === "array") {
|
|
1085
|
-
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
1086
|
-
if (Object.keys(schema.items).length === 0) {
|
|
1087
|
-
return [
|
|
1088
|
-
{
|
|
1089
|
-
type: ConstantString.TextBlockType,
|
|
1090
|
-
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
1091
|
-
wrap: true,
|
|
1092
|
-
},
|
|
1093
|
-
];
|
|
1094
|
-
}
|
|
1095
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
1096
|
-
const template = {
|
|
1097
|
-
type: ConstantString.ContainerType,
|
|
1098
|
-
$data: name ? `\${${name}}` : "${$root}",
|
|
1099
|
-
items: Array(),
|
|
1100
|
-
};
|
|
1101
|
-
template.items.push(...obj);
|
|
1102
|
-
return [template];
|
|
1103
|
-
}
|
|
1104
|
-
// some schema may not contain type but contain properties
|
|
1105
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1106
|
-
const { properties } = schema;
|
|
1107
|
-
const result = [];
|
|
1108
|
-
for (const property in properties) {
|
|
1109
|
-
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
1110
|
-
result.push(...obj);
|
|
1111
|
-
}
|
|
1112
|
-
if (schema.additionalProperties) {
|
|
1113
|
-
// TODO: better ways to handler warnings.
|
|
1114
|
-
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
1115
|
-
}
|
|
1116
|
-
return result;
|
|
1117
|
-
}
|
|
1118
|
-
if (schema.type === "string" ||
|
|
1119
|
-
schema.type === "integer" ||
|
|
1120
|
-
schema.type === "boolean" ||
|
|
1121
|
-
schema.type === "number") {
|
|
1122
|
-
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
1123
|
-
// string in root: "ddd"
|
|
1124
|
-
let text = "result: ${$root}";
|
|
1125
|
-
if (name) {
|
|
1126
|
-
// object { id: "1" }
|
|
1127
|
-
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
1128
|
-
if (parentArrayName) {
|
|
1129
|
-
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
1130
|
-
text = `${parentArrayName}.${text}`;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
else if (parentArrayName) {
|
|
1134
|
-
// string array: photoUrls: ["1", "2"]
|
|
1135
|
-
text = `${parentArrayName}: ` + "${$data}";
|
|
1136
|
-
}
|
|
1137
|
-
return [
|
|
1138
|
-
{
|
|
1139
|
-
type: ConstantString.TextBlockType,
|
|
1140
|
-
text,
|
|
1141
|
-
wrap: true,
|
|
1142
|
-
},
|
|
1143
|
-
];
|
|
1144
|
-
}
|
|
1145
|
-
else {
|
|
1146
|
-
if (name) {
|
|
1147
|
-
return [
|
|
1148
|
-
{
|
|
1149
|
-
type: "Image",
|
|
1150
|
-
url: `\${${name}}`,
|
|
1151
|
-
$when: `\${${name} != null}`,
|
|
1152
|
-
},
|
|
1153
|
-
];
|
|
1154
|
-
}
|
|
1155
|
-
else {
|
|
1156
|
-
return [
|
|
1157
|
-
{
|
|
1158
|
-
type: "Image",
|
|
1159
|
-
url: "${$data}",
|
|
1160
|
-
$when: "${$data != null}",
|
|
1161
|
-
},
|
|
1162
|
-
];
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
1167
|
-
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
1168
|
-
}
|
|
1169
|
-
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
1170
|
-
}
|
|
1171
|
-
// Find the first array property in the response schema object with the well-known name
|
|
1172
|
-
static getResponseJsonPathFromSchema(schema) {
|
|
1173
|
-
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
1174
|
-
const { properties } = schema;
|
|
1175
|
-
for (const property in properties) {
|
|
1176
|
-
const schema = properties[property];
|
|
1177
|
-
if (schema.type === "array" &&
|
|
1178
|
-
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
1179
|
-
return property;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
return "$";
|
|
1184
|
-
}
|
|
1185
|
-
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
1186
|
-
const propertyName = name ? name : parentArrayName;
|
|
1187
|
-
return (!!propertyName &&
|
|
1188
|
-
schema.type === "string" &&
|
|
1189
|
-
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
1190
|
-
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// Copyright (c) Microsoft Corporation.
|
|
1195
|
-
function wrapAdaptiveCard(card, jsonPath) {
|
|
1196
|
-
const result = {
|
|
1197
|
-
version: ConstantString.WrappedCardVersion,
|
|
1198
|
-
$schema: ConstantString.WrappedCardSchema,
|
|
1199
|
-
jsonPath: jsonPath,
|
|
1200
|
-
responseLayout: ConstantString.WrappedCardResponseLayout,
|
|
1201
|
-
responseCardTemplate: card,
|
|
1202
|
-
previewCardTemplate: inferPreviewCardTemplate(card),
|
|
1203
|
-
};
|
|
1204
|
-
return result;
|
|
1205
|
-
}
|
|
1206
|
-
/**
|
|
1207
|
-
* Infers the preview card template from an Adaptive Card and a JSON path.
|
|
1208
|
-
* The preview card template includes a title and an optional subtitle and image.
|
|
1209
|
-
* It populates the preview card template with the first text block that matches
|
|
1210
|
-
* each well-known name, in the order of title, subtitle, and image.
|
|
1211
|
-
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
|
|
1212
|
-
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
|
|
1213
|
-
* @param card The Adaptive Card to infer the preview card template from.
|
|
1214
|
-
* @param jsonPath The JSON path to the root object in the card body.
|
|
1215
|
-
* @returns The inferred preview card template.
|
|
1216
|
-
*/
|
|
1217
|
-
function inferPreviewCardTemplate(card) {
|
|
1218
|
-
var _a;
|
|
1219
|
-
const result = {
|
|
1220
|
-
title: "",
|
|
1221
|
-
};
|
|
1222
|
-
const textBlockElements = new Set();
|
|
1223
|
-
let rootObject;
|
|
1224
|
-
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
|
|
1225
|
-
rootObject = card.body[0].items;
|
|
1226
|
-
}
|
|
1227
|
-
else {
|
|
1228
|
-
rootObject = card.body;
|
|
1229
|
-
}
|
|
1230
|
-
for (const element of rootObject) {
|
|
1231
|
-
if (element.type === ConstantString.TextBlockType) {
|
|
1232
|
-
const textElement = element;
|
|
1233
|
-
const index = textElement.text.indexOf("${if(");
|
|
1234
|
-
if (index > 0) {
|
|
1235
|
-
textElement.text = textElement.text.substring(index);
|
|
1236
|
-
textBlockElements.add(textElement);
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
for (const element of textBlockElements) {
|
|
1241
|
-
const text = element.text;
|
|
1242
|
-
if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
|
|
1243
|
-
result.title = text;
|
|
1244
|
-
textBlockElements.delete(element);
|
|
1245
|
-
}
|
|
1246
|
-
else if (!result.subtitle &&
|
|
1247
|
-
Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
|
|
1248
|
-
result.subtitle = text;
|
|
1249
|
-
textBlockElements.delete(element);
|
|
1250
|
-
}
|
|
1251
|
-
else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
|
|
1252
|
-
const match = text.match(/\${if\(([^,]+),/);
|
|
1253
|
-
const property = match ? match[1] : "";
|
|
1254
|
-
if (property) {
|
|
1255
|
-
result.image = {
|
|
1256
|
-
url: `\${${property}}`,
|
|
1257
|
-
alt: text,
|
|
1258
|
-
$when: `\${${property} != null}`,
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
textBlockElements.delete(element);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
for (const element of textBlockElements) {
|
|
1265
|
-
const text = element.text;
|
|
1266
|
-
if (!result.title) {
|
|
1267
|
-
result.title = text;
|
|
1268
|
-
textBlockElements.delete(element);
|
|
1269
|
-
}
|
|
1270
|
-
else if (!result.subtitle) {
|
|
1271
|
-
result.subtitle = text;
|
|
1272
|
-
textBlockElements.delete(element);
|
|
1693
|
+
static removeEnvs(str) {
|
|
1694
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1695
|
+
const matches = placeHolderReg.exec(str);
|
|
1696
|
+
let newStr = str;
|
|
1697
|
+
if (matches != null) {
|
|
1698
|
+
newStr = newStr.replace(matches[0], "");
|
|
1273
1699
|
}
|
|
1700
|
+
return newStr;
|
|
1274
1701
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
delete result.subtitle;
|
|
1702
|
+
static removeAllSpecialCharacters(str) {
|
|
1703
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1278
1704
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1705
|
+
static getConfirmationBodyItem(paramName) {
|
|
1706
|
+
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
|
|
1281
1707
|
}
|
|
1282
|
-
return result;
|
|
1283
1708
|
}
|
|
1284
1709
|
|
|
1285
1710
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -1301,7 +1726,11 @@ class SpecParser {
|
|
|
1301
1726
|
allowMultipleParameters: false,
|
|
1302
1727
|
allowOauth2: false,
|
|
1303
1728
|
allowMethods: ["get", "post"],
|
|
1729
|
+
allowConversationStarters: false,
|
|
1730
|
+
allowResponseSemantics: false,
|
|
1731
|
+
allowConfirmation: false,
|
|
1304
1732
|
projectType: ProjectType.SME,
|
|
1733
|
+
isGptPlugin: false,
|
|
1305
1734
|
};
|
|
1306
1735
|
this.pathOrSpec = pathOrDoc;
|
|
1307
1736
|
this.parser = new SwaggerParser();
|
|
@@ -1325,6 +1754,8 @@ class SpecParser {
|
|
|
1325
1754
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1326
1755
|
};
|
|
1327
1756
|
}
|
|
1757
|
+
const errors = [];
|
|
1758
|
+
const warnings = [];
|
|
1328
1759
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1329
1760
|
return {
|
|
1330
1761
|
status: ValidationStatus.Error,
|
|
@@ -1334,23 +1765,38 @@ class SpecParser {
|
|
|
1334
1765
|
],
|
|
1335
1766
|
};
|
|
1336
1767
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1768
|
+
// Remote reference not supported
|
|
1769
|
+
const refPaths = this.parser.$refs.paths();
|
|
1770
|
+
// refPaths [0] is the current spec file path
|
|
1771
|
+
if (refPaths.length > 1) {
|
|
1772
|
+
errors.push({
|
|
1773
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1774
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1775
|
+
data: refPaths,
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1779
|
+
warnings.push({
|
|
1780
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1781
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1782
|
+
});
|
|
1352
1783
|
}
|
|
1353
|
-
|
|
1784
|
+
const validator = this.getValidator(this.spec);
|
|
1785
|
+
const validationResult = validator.validateSpec();
|
|
1786
|
+
warnings.push(...validationResult.warnings);
|
|
1787
|
+
errors.push(...validationResult.errors);
|
|
1788
|
+
let status = ValidationStatus.Valid;
|
|
1789
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1790
|
+
status = ValidationStatus.Warning;
|
|
1791
|
+
}
|
|
1792
|
+
else if (errors.length > 0) {
|
|
1793
|
+
status = ValidationStatus.Error;
|
|
1794
|
+
}
|
|
1795
|
+
return {
|
|
1796
|
+
status: status,
|
|
1797
|
+
warnings: warnings,
|
|
1798
|
+
errors: errors,
|
|
1799
|
+
};
|
|
1354
1800
|
}
|
|
1355
1801
|
catch (err) {
|
|
1356
1802
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1370,45 +1816,40 @@ class SpecParser {
|
|
|
1370
1816
|
try {
|
|
1371
1817
|
await this.loadSpec();
|
|
1372
1818
|
const spec = this.spec;
|
|
1373
|
-
const apiMap = this.
|
|
1819
|
+
const apiMap = this.getAPIs(spec);
|
|
1374
1820
|
const result = {
|
|
1375
|
-
|
|
1821
|
+
APIs: [],
|
|
1376
1822
|
allAPICount: 0,
|
|
1377
1823
|
validAPICount: 0,
|
|
1378
1824
|
};
|
|
1379
1825
|
for (const apiKey in apiMap) {
|
|
1826
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1827
|
+
const [method, path] = apiKey.split(" ");
|
|
1828
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1380
1829
|
const apiResult = {
|
|
1381
|
-
api:
|
|
1830
|
+
api: apiKey,
|
|
1382
1831
|
server: "",
|
|
1383
|
-
operationId:
|
|
1832
|
+
operationId: operationId,
|
|
1833
|
+
isValid: isValid,
|
|
1834
|
+
reason: reason,
|
|
1384
1835
|
};
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (!operationId) {
|
|
1397
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1398
|
-
}
|
|
1399
|
-
apiResult.operationId = operationId;
|
|
1400
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1401
|
-
for (const auths of authArray) {
|
|
1402
|
-
if (auths.length === 1) {
|
|
1403
|
-
apiResult.auth = auths[0];
|
|
1404
|
-
break;
|
|
1836
|
+
if (isValid) {
|
|
1837
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1838
|
+
if (serverObj) {
|
|
1839
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1840
|
+
}
|
|
1841
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1842
|
+
for (const auths of authArray) {
|
|
1843
|
+
if (auths.length === 1) {
|
|
1844
|
+
apiResult.auth = auths[0];
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1405
1847
|
}
|
|
1406
1848
|
}
|
|
1407
|
-
apiResult
|
|
1408
|
-
result.validAPIs.push(apiResult);
|
|
1849
|
+
result.APIs.push(apiResult);
|
|
1409
1850
|
}
|
|
1410
|
-
result.allAPICount =
|
|
1411
|
-
result.validAPICount = result.
|
|
1851
|
+
result.allAPICount = result.APIs.length;
|
|
1852
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1412
1853
|
return result;
|
|
1413
1854
|
}
|
|
1414
1855
|
catch (err) {
|
|
@@ -1461,18 +1902,12 @@ class SpecParser {
|
|
|
1461
1902
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1462
1903
|
const newUnResolvedSpec = newSpecs[0];
|
|
1463
1904
|
const newSpec = newSpecs[1];
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1467
|
-
}
|
|
1468
|
-
else {
|
|
1469
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1470
|
-
}
|
|
1471
|
-
await fs.outputFile(outputSpecPath, resultStr);
|
|
1905
|
+
const authInfo = Utils.getAuthInfo(newSpec);
|
|
1906
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1472
1907
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1473
1908
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1474
1909
|
}
|
|
1475
|
-
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
|
|
1910
|
+
const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
|
|
1476
1911
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1477
1912
|
await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
|
|
1478
1913
|
}
|
|
@@ -1500,32 +1935,11 @@ class SpecParser {
|
|
|
1500
1935
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1501
1936
|
const newUnResolvedSpec = newSpecs[0];
|
|
1502
1937
|
const newSpec = newSpecs[1];
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
for (const method in newSpec.paths[url]) {
|
|
1507
|
-
const operation = newSpec.paths[url][method];
|
|
1508
|
-
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1509
|
-
if (authArray && authArray.length > 0) {
|
|
1510
|
-
authSet.add(authArray[0][0]);
|
|
1511
|
-
if (authSet.size > 1) {
|
|
1512
|
-
hasMultipleAuth = true;
|
|
1513
|
-
break;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
|
|
1519
|
-
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
1520
|
-
}
|
|
1521
|
-
let resultStr;
|
|
1522
|
-
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
1523
|
-
resultStr = jsyaml.dump(newUnResolvedSpec);
|
|
1524
|
-
}
|
|
1525
|
-
else {
|
|
1526
|
-
resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
|
|
1938
|
+
let authInfo = undefined;
|
|
1939
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
1940
|
+
authInfo = Utils.getAuthInfo(newSpec);
|
|
1527
1941
|
}
|
|
1528
|
-
await
|
|
1942
|
+
await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
|
|
1529
1943
|
if (adaptiveCardFolder) {
|
|
1530
1944
|
for (const url in newSpec.paths) {
|
|
1531
1945
|
for (const method in newSpec.paths[url]) {
|
|
@@ -1555,7 +1969,6 @@ class SpecParser {
|
|
|
1555
1969
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1556
1970
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1557
1971
|
}
|
|
1558
|
-
const authInfo = Array.from(authSet)[0];
|
|
1559
1972
|
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1560
1973
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1561
1974
|
result.warnings.push(...warnings);
|
|
@@ -1581,13 +1994,28 @@ class SpecParser {
|
|
|
1581
1994
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1582
1995
|
}
|
|
1583
1996
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1997
|
+
getAPIs(spec) {
|
|
1998
|
+
const validator = this.getValidator(spec);
|
|
1999
|
+
const apiMap = validator.listAPIs();
|
|
2000
|
+
return apiMap;
|
|
2001
|
+
}
|
|
2002
|
+
getValidator(spec) {
|
|
2003
|
+
if (this.validator) {
|
|
2004
|
+
return this.validator;
|
|
1587
2005
|
}
|
|
1588
|
-
const
|
|
1589
|
-
this.
|
|
1590
|
-
return
|
|
2006
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
2007
|
+
this.validator = validator;
|
|
2008
|
+
return validator;
|
|
2009
|
+
}
|
|
2010
|
+
async saveFilterSpec(outputSpecPath, unResolvedSpec) {
|
|
2011
|
+
let resultStr;
|
|
2012
|
+
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
|
|
2013
|
+
resultStr = jsyaml.dump(unResolvedSpec);
|
|
2014
|
+
}
|
|
2015
|
+
else {
|
|
2016
|
+
resultStr = JSON.stringify(unResolvedSpec, null, 2);
|
|
2017
|
+
}
|
|
2018
|
+
await fs.outputFile(outputSpecPath, resultStr);
|
|
1591
2019
|
}
|
|
1592
2020
|
}
|
|
1593
2021
|
|