@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.3bb446445.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 +615 -334
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1053 -635
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +615 -334
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1062 -640
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +8 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +66 -1
- package/dist/src/manifestUpdater.d.ts +7 -4
- 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 -4
package/dist/index.esm5.js
CHANGED
|
@@ -46,6 +46,7 @@ var ErrorType;
|
|
|
46
46
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
47
47
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
48
48
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
49
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
49
50
|
ErrorType["ListFailed"] = "list-failed";
|
|
50
51
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
51
52
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -54,6 +55,21 @@ var ErrorType;
|
|
|
54
55
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
55
56
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
56
57
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
58
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
59
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
60
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
61
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
62
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
63
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
64
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
65
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
66
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
67
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
68
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
69
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
70
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
71
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
72
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
57
73
|
ErrorType["Cancelled"] = "cancelled";
|
|
58
74
|
ErrorType["Unknown"] = "unknown";
|
|
59
75
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -101,7 +117,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
101
117
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
102
118
|
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.";
|
|
103
119
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
104
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
120
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
105
121
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
106
122
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
107
123
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -109,6 +125,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
109
125
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
110
126
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
111
127
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
128
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
112
129
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
113
130
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
114
131
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -120,9 +137,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
120
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
121
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
122
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
123
141
|
ConstantString.ContainerType = "Container";
|
|
124
|
-
ConstantString.RegistrationIdPostfix =
|
|
125
|
-
|
|
142
|
+
ConstantString.RegistrationIdPostfix = {
|
|
143
|
+
apiKey: "REGISTRATION_ID",
|
|
144
|
+
oauth2: "CONFIGURATION_ID",
|
|
145
|
+
http: "REGISTRATION_ID",
|
|
146
|
+
openIdConnect: "REGISTRATION_ID",
|
|
147
|
+
};
|
|
126
148
|
ConstantString.ResponseCodeFor20X = [
|
|
127
149
|
"200",
|
|
128
150
|
"201",
|
|
@@ -181,9 +203,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
181
203
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
182
204
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
183
205
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
206
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
184
207
|
ConstantString.CommandTitleMaxLens = 32;
|
|
185
208
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
186
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
209
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
210
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
187
211
|
|
|
188
212
|
// Copyright (c) Microsoft Corporation.
|
|
189
213
|
class Utils {
|
|
@@ -198,221 +222,9 @@ class Utils {
|
|
|
198
222
|
}
|
|
199
223
|
return false;
|
|
200
224
|
}
|
|
201
|
-
static checkParameters(paramObject, isCopilot) {
|
|
202
|
-
const paramResult = {
|
|
203
|
-
requiredNum: 0,
|
|
204
|
-
optionalNum: 0,
|
|
205
|
-
isValid: true,
|
|
206
|
-
};
|
|
207
|
-
if (!paramObject) {
|
|
208
|
-
return paramResult;
|
|
209
|
-
}
|
|
210
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
211
|
-
const param = paramObject[i];
|
|
212
|
-
const schema = param.schema;
|
|
213
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
214
|
-
paramResult.isValid = false;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
218
|
-
if (isCopilot) {
|
|
219
|
-
if (isRequiredWithoutDefault) {
|
|
220
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
224
|
-
}
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
228
|
-
if (isRequiredWithoutDefault) {
|
|
229
|
-
paramResult.isValid = false;
|
|
230
|
-
}
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (schema.type !== "boolean" &&
|
|
234
|
-
schema.type !== "string" &&
|
|
235
|
-
schema.type !== "number" &&
|
|
236
|
-
schema.type !== "integer") {
|
|
237
|
-
if (isRequiredWithoutDefault) {
|
|
238
|
-
paramResult.isValid = false;
|
|
239
|
-
}
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (param.in === "query" || param.in === "path") {
|
|
243
|
-
if (isRequiredWithoutDefault) {
|
|
244
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return paramResult;
|
|
252
|
-
}
|
|
253
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
254
|
-
var _a;
|
|
255
|
-
const paramResult = {
|
|
256
|
-
requiredNum: 0,
|
|
257
|
-
optionalNum: 0,
|
|
258
|
-
isValid: true,
|
|
259
|
-
};
|
|
260
|
-
if (Object.keys(schema).length === 0) {
|
|
261
|
-
return paramResult;
|
|
262
|
-
}
|
|
263
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
264
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
265
|
-
paramResult.isValid = false;
|
|
266
|
-
return paramResult;
|
|
267
|
-
}
|
|
268
|
-
if (schema.type === "string" ||
|
|
269
|
-
schema.type === "integer" ||
|
|
270
|
-
schema.type === "boolean" ||
|
|
271
|
-
schema.type === "number") {
|
|
272
|
-
if (isRequiredWithoutDefault) {
|
|
273
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else if (schema.type === "object") {
|
|
280
|
-
const { properties } = schema;
|
|
281
|
-
for (const property in properties) {
|
|
282
|
-
let isRequired = false;
|
|
283
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
284
|
-
isRequired = true;
|
|
285
|
-
}
|
|
286
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
287
|
-
paramResult.requiredNum += result.requiredNum;
|
|
288
|
-
paramResult.optionalNum += result.optionalNum;
|
|
289
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
294
|
-
paramResult.isValid = false;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return paramResult;
|
|
298
|
-
}
|
|
299
225
|
static containMultipleMediaTypes(bodyObject) {
|
|
300
226
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
301
227
|
}
|
|
302
|
-
/**
|
|
303
|
-
* Checks if the given API is supported.
|
|
304
|
-
* @param {string} method - The HTTP method of the API.
|
|
305
|
-
* @param {string} path - The path of the API.
|
|
306
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
307
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
308
|
-
* @description The following APIs are supported:
|
|
309
|
-
* 1. only support Get/Post operation without auth property
|
|
310
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
311
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
312
|
-
* 4. request body + required parameters <= 1
|
|
313
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
314
|
-
* 6. only support request body with “application/json” content type
|
|
315
|
-
*/
|
|
316
|
-
static isSupportedApi(method, path, spec, options) {
|
|
317
|
-
var _a;
|
|
318
|
-
const pathObj = spec.paths[path];
|
|
319
|
-
method = method.toLocaleLowerCase();
|
|
320
|
-
if (pathObj) {
|
|
321
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
322
|
-
const securities = pathObj[method].security;
|
|
323
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
324
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
325
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
326
|
-
if (!isTeamsAi) {
|
|
327
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
328
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
const operationObject = pathObj[method];
|
|
333
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
const paramObject = operationObject.parameters;
|
|
337
|
-
const requestBody = operationObject.requestBody;
|
|
338
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
339
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
343
|
-
if (Object.keys(responseJson).length === 0) {
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
// Teams AI project doesn't care about request parameters/body
|
|
347
|
-
if (isTeamsAi) {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
let requestBodyParamResult = {
|
|
351
|
-
requiredNum: 0,
|
|
352
|
-
optionalNum: 0,
|
|
353
|
-
isValid: true,
|
|
354
|
-
};
|
|
355
|
-
if (requestJsonBody) {
|
|
356
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
357
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
361
|
-
}
|
|
362
|
-
if (!requestBodyParamResult.isValid) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
366
|
-
if (!paramResult.isValid) {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
// Copilot support arbitrary parameters
|
|
370
|
-
if (isCopilot) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
374
|
-
if (options.allowMultipleParameters &&
|
|
375
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
376
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
else if (requestBodyParamResult.requiredNum +
|
|
382
|
-
requestBodyParamResult.optionalNum +
|
|
383
|
-
paramResult.requiredNum +
|
|
384
|
-
paramResult.optionalNum ===
|
|
385
|
-
0) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
396
|
-
if (authSchemeArray.length === 0) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
400
|
-
// Currently we don't support multiple auth in one operation
|
|
401
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
for (const auths of authSchemeArray) {
|
|
405
|
-
if (auths.length === 1) {
|
|
406
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
407
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
408
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
228
|
static isBearerTokenAuth(authScheme) {
|
|
417
229
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
418
230
|
}
|
|
@@ -420,18 +232,18 @@ class Utils {
|
|
|
420
232
|
return authScheme.type === "apiKey";
|
|
421
233
|
}
|
|
422
234
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return false;
|
|
235
|
+
return !!(authScheme.type === "oauth2" &&
|
|
236
|
+
authScheme.flows &&
|
|
237
|
+
authScheme.flows.authorizationCode);
|
|
427
238
|
}
|
|
428
239
|
static getAuthArray(securities, spec) {
|
|
429
240
|
var _a;
|
|
430
241
|
const result = [];
|
|
431
242
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
243
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
244
|
+
if (securitiesArr && securitySchemas) {
|
|
245
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
246
|
+
const security = securitiesArr[i];
|
|
435
247
|
const authArray = [];
|
|
436
248
|
for (const name in security) {
|
|
437
249
|
const auth = securitySchemas[name];
|
|
@@ -448,17 +260,39 @@ class Utils {
|
|
|
448
260
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
449
261
|
return result;
|
|
450
262
|
}
|
|
263
|
+
static getAuthInfo(spec) {
|
|
264
|
+
let authInfo = undefined;
|
|
265
|
+
for (const url in spec.paths) {
|
|
266
|
+
for (const method in spec.paths[url]) {
|
|
267
|
+
const operation = spec.paths[url][method];
|
|
268
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
269
|
+
if (authArray && authArray.length > 0) {
|
|
270
|
+
const currentAuth = authArray[0][0];
|
|
271
|
+
if (!authInfo) {
|
|
272
|
+
authInfo = authArray[0][0];
|
|
273
|
+
}
|
|
274
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
275
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return authInfo;
|
|
281
|
+
}
|
|
451
282
|
static updateFirstLetter(str) {
|
|
452
283
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
453
284
|
}
|
|
454
|
-
static getResponseJson(operationObject
|
|
285
|
+
static getResponseJson(operationObject) {
|
|
455
286
|
var _a, _b;
|
|
456
287
|
let json = {};
|
|
288
|
+
let multipleMediaType = false;
|
|
457
289
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
458
290
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
459
291
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
292
|
+
multipleMediaType = false;
|
|
460
293
|
json = responseObject.content["application/json"];
|
|
461
|
-
if (
|
|
294
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
295
|
+
multipleMediaType = true;
|
|
462
296
|
json = {};
|
|
463
297
|
}
|
|
464
298
|
else {
|
|
@@ -466,7 +300,7 @@ class Utils {
|
|
|
466
300
|
}
|
|
467
301
|
}
|
|
468
302
|
}
|
|
469
|
-
return json;
|
|
303
|
+
return { json, multipleMediaType };
|
|
470
304
|
}
|
|
471
305
|
static convertPathToCamelCase(path) {
|
|
472
306
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -486,10 +320,10 @@ class Utils {
|
|
|
486
320
|
return undefined;
|
|
487
321
|
}
|
|
488
322
|
}
|
|
489
|
-
static
|
|
323
|
+
static resolveEnv(str) {
|
|
490
324
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
491
|
-
let matches = placeHolderReg.exec(
|
|
492
|
-
let
|
|
325
|
+
let matches = placeHolderReg.exec(str);
|
|
326
|
+
let newStr = str;
|
|
493
327
|
while (matches != null) {
|
|
494
328
|
const envVar = matches[1];
|
|
495
329
|
const envVal = process.env[envVar];
|
|
@@ -497,17 +331,17 @@ class Utils {
|
|
|
497
331
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
498
332
|
}
|
|
499
333
|
else {
|
|
500
|
-
|
|
334
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
501
335
|
}
|
|
502
|
-
matches = placeHolderReg.exec(
|
|
336
|
+
matches = placeHolderReg.exec(str);
|
|
503
337
|
}
|
|
504
|
-
return
|
|
338
|
+
return newStr;
|
|
505
339
|
}
|
|
506
340
|
static checkServerUrl(servers) {
|
|
507
341
|
const errors = [];
|
|
508
342
|
let serverUrl;
|
|
509
343
|
try {
|
|
510
|
-
serverUrl = Utils.
|
|
344
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
511
345
|
}
|
|
512
346
|
catch (err) {
|
|
513
347
|
errors.push({
|
|
@@ -538,6 +372,7 @@ class Utils {
|
|
|
538
372
|
return errors;
|
|
539
373
|
}
|
|
540
374
|
static validateServer(spec, options) {
|
|
375
|
+
var _a;
|
|
541
376
|
const errors = [];
|
|
542
377
|
let hasTopLevelServers = false;
|
|
543
378
|
let hasPathLevelServers = false;
|
|
@@ -558,7 +393,7 @@ class Utils {
|
|
|
558
393
|
}
|
|
559
394
|
for (const method in methods) {
|
|
560
395
|
const operationObject = methods[method];
|
|
561
|
-
if (
|
|
396
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
562
397
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
563
398
|
hasOperationLevelServers = true;
|
|
564
399
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -685,13 +520,7 @@ class Utils {
|
|
|
685
520
|
}
|
|
686
521
|
}
|
|
687
522
|
const operationId = operationItem.operationId;
|
|
688
|
-
const parameters = [];
|
|
689
|
-
if (requiredParams.length !== 0) {
|
|
690
|
-
parameters.push(...requiredParams);
|
|
691
|
-
}
|
|
692
|
-
else {
|
|
693
|
-
parameters.push(optionalParams[0]);
|
|
694
|
-
}
|
|
523
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
695
524
|
const command = {
|
|
696
525
|
context: ["compose"],
|
|
697
526
|
type: "query",
|
|
@@ -700,117 +529,534 @@ class Utils {
|
|
|
700
529
|
parameters: parameters,
|
|
701
530
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
702
531
|
};
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
532
|
+
return command;
|
|
533
|
+
}
|
|
534
|
+
static format(str, ...args) {
|
|
535
|
+
let index = 0;
|
|
536
|
+
return str.replace(/%s/g, () => {
|
|
537
|
+
const arg = args[index++];
|
|
538
|
+
return arg !== undefined ? arg : "";
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
542
|
+
if (!authName) {
|
|
543
|
+
return "";
|
|
544
|
+
}
|
|
545
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
546
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
547
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
710
548
|
}
|
|
711
|
-
return
|
|
549
|
+
return safeRegistrationIdEnvName;
|
|
712
550
|
}
|
|
713
|
-
static
|
|
714
|
-
const
|
|
551
|
+
static getServerObject(spec, method, path) {
|
|
552
|
+
const pathObj = spec.paths[path];
|
|
553
|
+
const operationObject = pathObj[method];
|
|
554
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
555
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
556
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
557
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
558
|
+
return serverUrl;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Copyright (c) Microsoft Corporation.
|
|
563
|
+
class Validator {
|
|
564
|
+
listAPIs() {
|
|
565
|
+
var _a;
|
|
566
|
+
if (this.apiMap) {
|
|
567
|
+
return this.apiMap;
|
|
568
|
+
}
|
|
569
|
+
const paths = this.spec.paths;
|
|
715
570
|
const result = {};
|
|
716
571
|
for (const path in paths) {
|
|
717
572
|
const methods = paths[path];
|
|
718
573
|
for (const method in methods) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
574
|
+
const operationObject = methods[method];
|
|
575
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
576
|
+
const validateResult = this.validateAPI(method, path);
|
|
577
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
578
|
+
operation: operationObject,
|
|
579
|
+
isValid: validateResult.isValid,
|
|
580
|
+
reason: validateResult.reason,
|
|
581
|
+
};
|
|
722
582
|
}
|
|
723
583
|
}
|
|
724
584
|
}
|
|
585
|
+
this.apiMap = result;
|
|
725
586
|
return result;
|
|
726
587
|
}
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
588
|
+
validateSpecVersion() {
|
|
589
|
+
const result = { errors: [], warnings: [] };
|
|
590
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
591
|
+
result.errors.push({
|
|
592
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
593
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
594
|
+
data: this.spec.openapi,
|
|
734
595
|
});
|
|
735
596
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
validateSpecServer() {
|
|
600
|
+
const result = { errors: [], warnings: [] };
|
|
601
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
602
|
+
result.errors.push(...serverErrors);
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
validateSpecNoSupportAPI() {
|
|
606
|
+
const result = { errors: [], warnings: [] };
|
|
607
|
+
const apiMap = this.listAPIs();
|
|
608
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
609
|
+
if (validAPIs.length === 0) {
|
|
610
|
+
const data = [];
|
|
611
|
+
for (const key in apiMap) {
|
|
612
|
+
const { reason } = apiMap[key];
|
|
613
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
614
|
+
data.push(apiInvalidReason);
|
|
615
|
+
}
|
|
616
|
+
result.errors.push({
|
|
753
617
|
type: ErrorType.NoSupportedApi,
|
|
754
618
|
content: ConstantString.NoSupportedApi,
|
|
619
|
+
data,
|
|
755
620
|
});
|
|
756
621
|
}
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
validateSpecOperationId() {
|
|
625
|
+
const result = { errors: [], warnings: [] };
|
|
626
|
+
const apiMap = this.listAPIs();
|
|
757
627
|
// OperationId missing
|
|
758
628
|
const apisMissingOperationId = [];
|
|
759
629
|
for (const key in apiMap) {
|
|
760
|
-
const
|
|
761
|
-
if (!
|
|
630
|
+
const { operation } = apiMap[key];
|
|
631
|
+
if (!operation.operationId) {
|
|
762
632
|
apisMissingOperationId.push(key);
|
|
763
633
|
}
|
|
764
634
|
}
|
|
765
635
|
if (apisMissingOperationId.length > 0) {
|
|
766
|
-
warnings.push({
|
|
636
|
+
result.warnings.push({
|
|
767
637
|
type: WarningType.OperationIdMissing,
|
|
768
638
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
769
639
|
data: apisMissingOperationId,
|
|
770
640
|
});
|
|
771
641
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
validateMethodAndPath(method, path) {
|
|
645
|
+
const result = { isValid: true, reason: [] };
|
|
646
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
647
|
+
result.isValid = false;
|
|
648
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
649
|
+
return result;
|
|
775
650
|
}
|
|
776
|
-
|
|
777
|
-
|
|
651
|
+
const pathObj = this.spec.paths[path];
|
|
652
|
+
if (!pathObj || !pathObj[method]) {
|
|
653
|
+
result.isValid = false;
|
|
654
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
655
|
+
return result;
|
|
778
656
|
}
|
|
779
|
-
return
|
|
780
|
-
status,
|
|
781
|
-
warnings,
|
|
782
|
-
errors,
|
|
783
|
-
};
|
|
657
|
+
return result;
|
|
784
658
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
659
|
+
validateResponse(method, path) {
|
|
660
|
+
const result = { isValid: true, reason: [] };
|
|
661
|
+
const operationObject = this.spec.paths[path][method];
|
|
662
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
663
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
664
|
+
// only support response body only contains “application/json” content type
|
|
665
|
+
if (multipleMediaType) {
|
|
666
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
667
|
+
}
|
|
668
|
+
else if (Object.keys(json).length === 0) {
|
|
669
|
+
// response body should not be empty
|
|
670
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
791
674
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
675
|
+
validateServer(method, path) {
|
|
676
|
+
const result = { isValid: true, reason: [] };
|
|
677
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
678
|
+
if (!serverObj) {
|
|
679
|
+
// should contain server URL
|
|
680
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
795
681
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
682
|
+
else {
|
|
683
|
+
// server url should be absolute url with https protocol
|
|
684
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
685
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
799
686
|
}
|
|
800
|
-
return
|
|
687
|
+
return result;
|
|
801
688
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
689
|
+
validateAuth(method, path) {
|
|
690
|
+
const pathObj = this.spec.paths[path];
|
|
691
|
+
const operationObject = pathObj[method];
|
|
692
|
+
const securities = operationObject.security;
|
|
693
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
694
|
+
if (authSchemeArray.length === 0) {
|
|
695
|
+
return { isValid: true, reason: [] };
|
|
696
|
+
}
|
|
697
|
+
if (this.options.allowAPIKeyAuth ||
|
|
698
|
+
this.options.allowOauth2 ||
|
|
699
|
+
this.options.allowBearerTokenAuth) {
|
|
700
|
+
// Currently we don't support multiple auth in one operation
|
|
701
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
702
|
+
return {
|
|
703
|
+
isValid: false,
|
|
704
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
for (const auths of authSchemeArray) {
|
|
708
|
+
if (auths.length === 1) {
|
|
709
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
710
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
711
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
712
|
+
return { isValid: true, reason: [] };
|
|
713
|
+
}
|
|
810
714
|
}
|
|
811
715
|
}
|
|
812
716
|
}
|
|
813
|
-
return
|
|
717
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
718
|
+
}
|
|
719
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
720
|
+
var _a;
|
|
721
|
+
const paramResult = {
|
|
722
|
+
requiredNum: 0,
|
|
723
|
+
optionalNum: 0,
|
|
724
|
+
isValid: true,
|
|
725
|
+
reason: [],
|
|
726
|
+
};
|
|
727
|
+
if (Object.keys(schema).length === 0) {
|
|
728
|
+
return paramResult;
|
|
729
|
+
}
|
|
730
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
731
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
732
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
733
|
+
paramResult.isValid = false;
|
|
734
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
735
|
+
return paramResult;
|
|
736
|
+
}
|
|
737
|
+
if (schema.type === "string" ||
|
|
738
|
+
schema.type === "integer" ||
|
|
739
|
+
schema.type === "boolean" ||
|
|
740
|
+
schema.type === "number") {
|
|
741
|
+
if (isRequiredWithoutDefault) {
|
|
742
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
else if (schema.type === "object") {
|
|
749
|
+
const { properties } = schema;
|
|
750
|
+
for (const property in properties) {
|
|
751
|
+
let isRequired = false;
|
|
752
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
753
|
+
isRequired = true;
|
|
754
|
+
}
|
|
755
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
756
|
+
paramResult.requiredNum += result.requiredNum;
|
|
757
|
+
paramResult.optionalNum += result.optionalNum;
|
|
758
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
759
|
+
paramResult.reason.push(...result.reason);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
764
|
+
paramResult.isValid = false;
|
|
765
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return paramResult;
|
|
769
|
+
}
|
|
770
|
+
checkParamSchema(paramObject) {
|
|
771
|
+
const paramResult = {
|
|
772
|
+
requiredNum: 0,
|
|
773
|
+
optionalNum: 0,
|
|
774
|
+
isValid: true,
|
|
775
|
+
reason: [],
|
|
776
|
+
};
|
|
777
|
+
if (!paramObject) {
|
|
778
|
+
return paramResult;
|
|
779
|
+
}
|
|
780
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
781
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
782
|
+
const param = paramObject[i];
|
|
783
|
+
const schema = param.schema;
|
|
784
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
785
|
+
paramResult.isValid = false;
|
|
786
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
790
|
+
if (isCopilot) {
|
|
791
|
+
if (isRequiredWithoutDefault) {
|
|
792
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
796
|
+
}
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
800
|
+
if (isRequiredWithoutDefault) {
|
|
801
|
+
paramResult.isValid = false;
|
|
802
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
803
|
+
}
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (schema.type !== "boolean" &&
|
|
807
|
+
schema.type !== "string" &&
|
|
808
|
+
schema.type !== "number" &&
|
|
809
|
+
schema.type !== "integer") {
|
|
810
|
+
if (isRequiredWithoutDefault) {
|
|
811
|
+
paramResult.isValid = false;
|
|
812
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
813
|
+
}
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (param.in === "query" || param.in === "path") {
|
|
817
|
+
if (isRequiredWithoutDefault) {
|
|
818
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return paramResult;
|
|
826
|
+
}
|
|
827
|
+
hasNestedObjectInSchema(schema) {
|
|
828
|
+
if (schema.type === "object") {
|
|
829
|
+
for (const property in schema.properties) {
|
|
830
|
+
const nestedSchema = schema.properties[property];
|
|
831
|
+
if (nestedSchema.type === "object") {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Copyright (c) Microsoft Corporation.
|
|
841
|
+
class CopilotValidator extends Validator {
|
|
842
|
+
constructor(spec, options) {
|
|
843
|
+
super();
|
|
844
|
+
this.projectType = ProjectType.Copilot;
|
|
845
|
+
this.options = options;
|
|
846
|
+
this.spec = spec;
|
|
847
|
+
}
|
|
848
|
+
validateSpec() {
|
|
849
|
+
const result = { errors: [], warnings: [] };
|
|
850
|
+
// validate spec version
|
|
851
|
+
let validationResult = this.validateSpecVersion();
|
|
852
|
+
result.errors.push(...validationResult.errors);
|
|
853
|
+
// validate spec server
|
|
854
|
+
validationResult = this.validateSpecServer();
|
|
855
|
+
result.errors.push(...validationResult.errors);
|
|
856
|
+
// validate no supported API
|
|
857
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
858
|
+
result.errors.push(...validationResult.errors);
|
|
859
|
+
// validate operationId missing
|
|
860
|
+
validationResult = this.validateSpecOperationId();
|
|
861
|
+
result.warnings.push(...validationResult.warnings);
|
|
862
|
+
return result;
|
|
863
|
+
}
|
|
864
|
+
validateAPI(method, path) {
|
|
865
|
+
const result = { isValid: true, reason: [] };
|
|
866
|
+
method = method.toLocaleLowerCase();
|
|
867
|
+
// validate method and path
|
|
868
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
869
|
+
if (!methodAndPathResult.isValid) {
|
|
870
|
+
return methodAndPathResult;
|
|
871
|
+
}
|
|
872
|
+
const operationObject = this.spec.paths[path][method];
|
|
873
|
+
// validate auth
|
|
874
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
875
|
+
result.reason.push(...authCheckResult.reason);
|
|
876
|
+
// validate operationId
|
|
877
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
878
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
879
|
+
}
|
|
880
|
+
// validate server
|
|
881
|
+
const validateServerResult = this.validateServer(method, path);
|
|
882
|
+
result.reason.push(...validateServerResult.reason);
|
|
883
|
+
// validate response
|
|
884
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
885
|
+
result.reason.push(...validateResponseResult.reason);
|
|
886
|
+
// validate requestBody
|
|
887
|
+
const requestBody = operationObject.requestBody;
|
|
888
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
889
|
+
if (requestJsonBody) {
|
|
890
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
891
|
+
if (requestBodySchema.type !== "object") {
|
|
892
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
893
|
+
}
|
|
894
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
895
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
896
|
+
}
|
|
897
|
+
// validate parameters
|
|
898
|
+
const paramObject = operationObject.parameters;
|
|
899
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
900
|
+
result.reason.push(...paramResult.reason);
|
|
901
|
+
if (result.reason.length > 0) {
|
|
902
|
+
result.isValid = false;
|
|
903
|
+
}
|
|
904
|
+
return result;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Copyright (c) Microsoft Corporation.
|
|
909
|
+
class SMEValidator extends Validator {
|
|
910
|
+
constructor(spec, options) {
|
|
911
|
+
super();
|
|
912
|
+
this.projectType = ProjectType.SME;
|
|
913
|
+
this.options = options;
|
|
914
|
+
this.spec = spec;
|
|
915
|
+
}
|
|
916
|
+
validateSpec() {
|
|
917
|
+
const result = { errors: [], warnings: [] };
|
|
918
|
+
// validate spec version
|
|
919
|
+
let validationResult = this.validateSpecVersion();
|
|
920
|
+
result.errors.push(...validationResult.errors);
|
|
921
|
+
// validate spec server
|
|
922
|
+
validationResult = this.validateSpecServer();
|
|
923
|
+
result.errors.push(...validationResult.errors);
|
|
924
|
+
// validate no supported API
|
|
925
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
926
|
+
result.errors.push(...validationResult.errors);
|
|
927
|
+
// validate operationId missing
|
|
928
|
+
if (this.options.allowMissingId) {
|
|
929
|
+
validationResult = this.validateSpecOperationId();
|
|
930
|
+
result.warnings.push(...validationResult.warnings);
|
|
931
|
+
}
|
|
932
|
+
return result;
|
|
933
|
+
}
|
|
934
|
+
validateAPI(method, path) {
|
|
935
|
+
const result = { isValid: true, reason: [] };
|
|
936
|
+
method = method.toLocaleLowerCase();
|
|
937
|
+
// validate method and path
|
|
938
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
939
|
+
if (!methodAndPathResult.isValid) {
|
|
940
|
+
return methodAndPathResult;
|
|
941
|
+
}
|
|
942
|
+
const operationObject = this.spec.paths[path][method];
|
|
943
|
+
// validate auth
|
|
944
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
945
|
+
result.reason.push(...authCheckResult.reason);
|
|
946
|
+
// validate operationId
|
|
947
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
948
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
949
|
+
}
|
|
950
|
+
// validate server
|
|
951
|
+
const validateServerResult = this.validateServer(method, path);
|
|
952
|
+
result.reason.push(...validateServerResult.reason);
|
|
953
|
+
// validate response
|
|
954
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
955
|
+
result.reason.push(...validateResponseResult.reason);
|
|
956
|
+
let postBodyResult = {
|
|
957
|
+
requiredNum: 0,
|
|
958
|
+
optionalNum: 0,
|
|
959
|
+
isValid: true,
|
|
960
|
+
reason: [],
|
|
961
|
+
};
|
|
962
|
+
// validate requestBody
|
|
963
|
+
const requestBody = operationObject.requestBody;
|
|
964
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
965
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
966
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
967
|
+
}
|
|
968
|
+
if (requestJsonBody) {
|
|
969
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
970
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
971
|
+
result.reason.push(...postBodyResult.reason);
|
|
972
|
+
}
|
|
973
|
+
// validate parameters
|
|
974
|
+
const paramObject = operationObject.parameters;
|
|
975
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
976
|
+
result.reason.push(...paramResult.reason);
|
|
977
|
+
// validate total parameters count
|
|
978
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
979
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
980
|
+
result.reason.push(...paramCountResult.reason);
|
|
981
|
+
}
|
|
982
|
+
if (result.reason.length > 0) {
|
|
983
|
+
result.isValid = false;
|
|
984
|
+
}
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
988
|
+
const result = { isValid: true, reason: [] };
|
|
989
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
990
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
991
|
+
if (totalRequiredParams > 1) {
|
|
992
|
+
if (!this.options.allowMultipleParameters ||
|
|
993
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
994
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
else if (totalParams === 0) {
|
|
998
|
+
result.reason.push(ErrorType.NoParameter);
|
|
999
|
+
}
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
1004
|
+
|
|
1005
|
+
// Copyright (c) Microsoft Corporation.
|
|
1006
|
+
class TeamsAIValidator extends Validator {
|
|
1007
|
+
constructor(spec, options) {
|
|
1008
|
+
super();
|
|
1009
|
+
this.projectType = ProjectType.TeamsAi;
|
|
1010
|
+
this.options = options;
|
|
1011
|
+
this.spec = spec;
|
|
1012
|
+
}
|
|
1013
|
+
validateSpec() {
|
|
1014
|
+
const result = { errors: [], warnings: [] };
|
|
1015
|
+
// validate spec server
|
|
1016
|
+
let validationResult = this.validateSpecServer();
|
|
1017
|
+
result.errors.push(...validationResult.errors);
|
|
1018
|
+
// validate no supported API
|
|
1019
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1020
|
+
result.errors.push(...validationResult.errors);
|
|
1021
|
+
return result;
|
|
1022
|
+
}
|
|
1023
|
+
validateAPI(method, path) {
|
|
1024
|
+
const result = { isValid: true, reason: [] };
|
|
1025
|
+
method = method.toLocaleLowerCase();
|
|
1026
|
+
// validate method and path
|
|
1027
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1028
|
+
if (!methodAndPathResult.isValid) {
|
|
1029
|
+
return methodAndPathResult;
|
|
1030
|
+
}
|
|
1031
|
+
const operationObject = this.spec.paths[path][method];
|
|
1032
|
+
// validate operationId
|
|
1033
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1034
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1035
|
+
}
|
|
1036
|
+
// validate server
|
|
1037
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1038
|
+
result.reason.push(...validateServerResult.reason);
|
|
1039
|
+
if (result.reason.length > 0) {
|
|
1040
|
+
result.isValid = false;
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
class ValidatorFactory {
|
|
1047
|
+
static create(spec, options) {
|
|
1048
|
+
var _a;
|
|
1049
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1050
|
+
switch (type) {
|
|
1051
|
+
case ProjectType.SME:
|
|
1052
|
+
return new SMEValidator(spec, options);
|
|
1053
|
+
case ProjectType.Copilot:
|
|
1054
|
+
return new CopilotValidator(spec, options);
|
|
1055
|
+
case ProjectType.TeamsAi:
|
|
1056
|
+
return new TeamsAIValidator(spec, options);
|
|
1057
|
+
default:
|
|
1058
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1059
|
+
}
|
|
814
1060
|
}
|
|
815
1061
|
}
|
|
816
1062
|
|
|
@@ -833,7 +1079,11 @@ class SpecParser {
|
|
|
833
1079
|
allowBearerTokenAuth: false,
|
|
834
1080
|
allowOauth2: false,
|
|
835
1081
|
allowMethods: ["get", "post"],
|
|
1082
|
+
allowConversationStarters: false,
|
|
1083
|
+
allowResponseSemantics: false,
|
|
1084
|
+
allowConfirmation: false,
|
|
836
1085
|
projectType: ProjectType.SME,
|
|
1086
|
+
isGptPlugin: false,
|
|
837
1087
|
};
|
|
838
1088
|
this.pathOrSpec = pathOrDoc;
|
|
839
1089
|
this.parser = new SwaggerParser();
|
|
@@ -849,11 +1099,7 @@ class SpecParser {
|
|
|
849
1099
|
try {
|
|
850
1100
|
try {
|
|
851
1101
|
yield this.loadSpec();
|
|
852
|
-
yield this.parser.validate(this.spec
|
|
853
|
-
validate: {
|
|
854
|
-
schema: false,
|
|
855
|
-
},
|
|
856
|
-
});
|
|
1102
|
+
yield this.parser.validate(this.spec);
|
|
857
1103
|
}
|
|
858
1104
|
catch (e) {
|
|
859
1105
|
return {
|
|
@@ -862,16 +1108,46 @@ class SpecParser {
|
|
|
862
1108
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
863
1109
|
};
|
|
864
1110
|
}
|
|
1111
|
+
const errors = [];
|
|
1112
|
+
const warnings = [];
|
|
865
1113
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
866
1114
|
return {
|
|
867
1115
|
status: ValidationStatus.Error,
|
|
868
1116
|
warnings: [],
|
|
869
1117
|
errors: [
|
|
870
|
-
{
|
|
1118
|
+
{
|
|
1119
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1120
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1121
|
+
},
|
|
871
1122
|
],
|
|
872
1123
|
};
|
|
873
1124
|
}
|
|
874
|
-
|
|
1125
|
+
// Remote reference not supported
|
|
1126
|
+
const refPaths = this.parser.$refs.paths();
|
|
1127
|
+
// refPaths [0] is the current spec file path
|
|
1128
|
+
if (refPaths.length > 1) {
|
|
1129
|
+
errors.push({
|
|
1130
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1131
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1132
|
+
data: refPaths,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
const validator = this.getValidator(this.spec);
|
|
1136
|
+
const validationResult = validator.validateSpec();
|
|
1137
|
+
warnings.push(...validationResult.warnings);
|
|
1138
|
+
errors.push(...validationResult.errors);
|
|
1139
|
+
let status = ValidationStatus.Valid;
|
|
1140
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1141
|
+
status = ValidationStatus.Warning;
|
|
1142
|
+
}
|
|
1143
|
+
else if (errors.length > 0) {
|
|
1144
|
+
status = ValidationStatus.Error;
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
status: status,
|
|
1148
|
+
warnings: warnings,
|
|
1149
|
+
errors: errors,
|
|
1150
|
+
};
|
|
875
1151
|
}
|
|
876
1152
|
catch (err) {
|
|
877
1153
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -882,17 +1158,20 @@ class SpecParser {
|
|
|
882
1158
|
return __awaiter(this, void 0, void 0, function* () {
|
|
883
1159
|
try {
|
|
884
1160
|
yield this.loadSpec();
|
|
885
|
-
const apiMap = this.
|
|
1161
|
+
const apiMap = this.getAPIs(this.spec);
|
|
886
1162
|
const apiInfos = [];
|
|
887
1163
|
for (const key in apiMap) {
|
|
888
|
-
const
|
|
1164
|
+
const { operation, isValid } = apiMap[key];
|
|
1165
|
+
if (!isValid) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
889
1168
|
const [method, path] = key.split(" ");
|
|
890
|
-
const operationId =
|
|
1169
|
+
const operationId = operation.operationId;
|
|
891
1170
|
// In Browser environment, this api is by default not support api without operationId
|
|
892
1171
|
if (!operationId) {
|
|
893
1172
|
continue;
|
|
894
1173
|
}
|
|
895
|
-
const
|
|
1174
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
896
1175
|
const apiInfo = {
|
|
897
1176
|
method: method,
|
|
898
1177
|
path: path,
|
|
@@ -901,9 +1180,6 @@ class SpecParser {
|
|
|
901
1180
|
parameters: command.parameters,
|
|
902
1181
|
description: command.description,
|
|
903
1182
|
};
|
|
904
|
-
if (warning) {
|
|
905
|
-
apiInfo.warning = warning;
|
|
906
|
-
}
|
|
907
1183
|
apiInfos.push(apiInfo);
|
|
908
1184
|
}
|
|
909
1185
|
return apiInfos;
|
|
@@ -973,13 +1249,18 @@ class SpecParser {
|
|
|
973
1249
|
}
|
|
974
1250
|
});
|
|
975
1251
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1252
|
+
getAPIs(spec) {
|
|
1253
|
+
const validator = this.getValidator(spec);
|
|
1254
|
+
const apiMap = validator.listAPIs();
|
|
1255
|
+
return apiMap;
|
|
1256
|
+
}
|
|
1257
|
+
getValidator(spec) {
|
|
1258
|
+
if (this.validator) {
|
|
1259
|
+
return this.validator;
|
|
979
1260
|
}
|
|
980
|
-
const
|
|
981
|
-
this.
|
|
982
|
-
return
|
|
1261
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1262
|
+
this.validator = validator;
|
|
1263
|
+
return validator;
|
|
983
1264
|
}
|
|
984
1265
|
}
|
|
985
1266
|
|
|
@@ -987,7 +1268,7 @@ class SpecParser {
|
|
|
987
1268
|
class AdaptiveCardGenerator {
|
|
988
1269
|
static generateAdaptiveCard(operationItem) {
|
|
989
1270
|
try {
|
|
990
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1271
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
991
1272
|
let cardBody = [];
|
|
992
1273
|
let schema = json.schema;
|
|
993
1274
|
let jsonPath = "$";
|