@microsoft/m365-spec-parser 0.1.1-alpha.e17ffd4d1.0 → 0.1.1-alpha.e1b11d5b2.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 +627 -338
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1067 -636
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +627 -338
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1079 -644
- 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 +79 -16
- package/dist/src/manifestUpdater.d.ts +7 -4
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +15 -32
- 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,253 +222,33 @@ 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
|
-
|
|
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(authSchemaArray, options) {
|
|
396
|
-
if (authSchemaArray.length === 0) {
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
400
|
-
// Currently we don't support multiple auth in one operation
|
|
401
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
for (const auths of authSchemaArray) {
|
|
405
|
-
if (auths.length === 1) {
|
|
406
|
-
if (!options.allowOauth2 &&
|
|
407
|
-
options.allowAPIKeyAuth &&
|
|
408
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
else if (!options.allowAPIKeyAuth &&
|
|
412
|
-
options.allowOauth2 &&
|
|
413
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema)) {
|
|
414
|
-
return true;
|
|
415
|
-
}
|
|
416
|
-
else if (options.allowAPIKeyAuth &&
|
|
417
|
-
options.allowOauth2 &&
|
|
418
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
419
|
-
Utils.isOAuthWithAuthCodeFlow(auths[0].authSchema))) {
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return false;
|
|
228
|
+
static isBearerTokenAuth(authScheme) {
|
|
229
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
426
230
|
}
|
|
427
|
-
static isAPIKeyAuth(
|
|
428
|
-
return
|
|
231
|
+
static isAPIKeyAuth(authScheme) {
|
|
232
|
+
return authScheme.type === "apiKey";
|
|
429
233
|
}
|
|
430
|
-
static isOAuthWithAuthCodeFlow(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return false;
|
|
234
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
235
|
+
return !!(authScheme.type === "oauth2" &&
|
|
236
|
+
authScheme.flows &&
|
|
237
|
+
authScheme.flows.authorizationCode);
|
|
435
238
|
}
|
|
436
239
|
static getAuthArray(securities, spec) {
|
|
437
240
|
var _a;
|
|
438
241
|
const result = [];
|
|
439
242
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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];
|
|
443
247
|
const authArray = [];
|
|
444
248
|
for (const name in security) {
|
|
445
249
|
const auth = securitySchemas[name];
|
|
446
250
|
authArray.push({
|
|
447
|
-
|
|
251
|
+
authScheme: auth,
|
|
448
252
|
name: name,
|
|
449
253
|
});
|
|
450
254
|
}
|
|
@@ -456,17 +260,39 @@ class Utils {
|
|
|
456
260
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
457
261
|
return result;
|
|
458
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
|
+
}
|
|
459
282
|
static updateFirstLetter(str) {
|
|
460
283
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
461
284
|
}
|
|
462
|
-
static getResponseJson(operationObject
|
|
285
|
+
static getResponseJson(operationObject) {
|
|
463
286
|
var _a, _b;
|
|
464
287
|
let json = {};
|
|
288
|
+
let multipleMediaType = false;
|
|
465
289
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
466
290
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
467
291
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
292
|
+
multipleMediaType = false;
|
|
468
293
|
json = responseObject.content["application/json"];
|
|
469
|
-
if (
|
|
294
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
295
|
+
multipleMediaType = true;
|
|
470
296
|
json = {};
|
|
471
297
|
}
|
|
472
298
|
else {
|
|
@@ -474,7 +300,7 @@ class Utils {
|
|
|
474
300
|
}
|
|
475
301
|
}
|
|
476
302
|
}
|
|
477
|
-
return json;
|
|
303
|
+
return { json, multipleMediaType };
|
|
478
304
|
}
|
|
479
305
|
static convertPathToCamelCase(path) {
|
|
480
306
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -494,10 +320,10 @@ class Utils {
|
|
|
494
320
|
return undefined;
|
|
495
321
|
}
|
|
496
322
|
}
|
|
497
|
-
static
|
|
323
|
+
static resolveEnv(str) {
|
|
498
324
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
499
|
-
let matches = placeHolderReg.exec(
|
|
500
|
-
let
|
|
325
|
+
let matches = placeHolderReg.exec(str);
|
|
326
|
+
let newStr = str;
|
|
501
327
|
while (matches != null) {
|
|
502
328
|
const envVar = matches[1];
|
|
503
329
|
const envVal = process.env[envVar];
|
|
@@ -505,17 +331,17 @@ class Utils {
|
|
|
505
331
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
506
332
|
}
|
|
507
333
|
else {
|
|
508
|
-
|
|
334
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
509
335
|
}
|
|
510
|
-
matches = placeHolderReg.exec(
|
|
336
|
+
matches = placeHolderReg.exec(str);
|
|
511
337
|
}
|
|
512
|
-
return
|
|
338
|
+
return newStr;
|
|
513
339
|
}
|
|
514
340
|
static checkServerUrl(servers) {
|
|
515
341
|
const errors = [];
|
|
516
342
|
let serverUrl;
|
|
517
343
|
try {
|
|
518
|
-
serverUrl = Utils.
|
|
344
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
519
345
|
}
|
|
520
346
|
catch (err) {
|
|
521
347
|
errors.push({
|
|
@@ -546,6 +372,7 @@ class Utils {
|
|
|
546
372
|
return errors;
|
|
547
373
|
}
|
|
548
374
|
static validateServer(spec, options) {
|
|
375
|
+
var _a;
|
|
549
376
|
const errors = [];
|
|
550
377
|
let hasTopLevelServers = false;
|
|
551
378
|
let hasPathLevelServers = false;
|
|
@@ -566,7 +393,7 @@ class Utils {
|
|
|
566
393
|
}
|
|
567
394
|
for (const method in methods) {
|
|
568
395
|
const operationObject = methods[method];
|
|
569
|
-
if (
|
|
396
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
570
397
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
571
398
|
hasOperationLevelServers = true;
|
|
572
399
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -609,6 +436,7 @@ class Utils {
|
|
|
609
436
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
610
437
|
}
|
|
611
438
|
if (isRequired && schema.default === undefined) {
|
|
439
|
+
parameter.isRequired = true;
|
|
612
440
|
requiredParams.push(parameter);
|
|
613
441
|
}
|
|
614
442
|
else {
|
|
@@ -672,6 +500,7 @@ class Utils {
|
|
|
672
500
|
}
|
|
673
501
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
674
502
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
503
|
+
parameter.isRequired = true;
|
|
675
504
|
requiredParams.push(parameter);
|
|
676
505
|
}
|
|
677
506
|
else {
|
|
@@ -691,13 +520,7 @@ class Utils {
|
|
|
691
520
|
}
|
|
692
521
|
}
|
|
693
522
|
const operationId = operationItem.operationId;
|
|
694
|
-
const parameters = [];
|
|
695
|
-
if (requiredParams.length !== 0) {
|
|
696
|
-
parameters.push(...requiredParams);
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
parameters.push(optionalParams[0]);
|
|
700
|
-
}
|
|
523
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
701
524
|
const command = {
|
|
702
525
|
context: ["compose"],
|
|
703
526
|
type: "query",
|
|
@@ -706,104 +529,534 @@ class Utils {
|
|
|
706
529
|
parameters: parameters,
|
|
707
530
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
708
531
|
};
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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 "";
|
|
716
544
|
}
|
|
717
|
-
|
|
545
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
546
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
547
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
548
|
+
}
|
|
549
|
+
return safeRegistrationIdEnvName;
|
|
718
550
|
}
|
|
719
|
-
static
|
|
720
|
-
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;
|
|
721
570
|
const result = {};
|
|
722
571
|
for (const path in paths) {
|
|
723
572
|
const methods = paths[path];
|
|
724
573
|
for (const method in methods) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
+
};
|
|
728
582
|
}
|
|
729
583
|
}
|
|
730
584
|
}
|
|
585
|
+
this.apiMap = result;
|
|
731
586
|
return result;
|
|
732
587
|
}
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
// Server validation
|
|
743
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
744
|
-
errors.push(...serverErrors);
|
|
745
|
-
// Remote reference not supported
|
|
746
|
-
const refPaths = parser.$refs.paths();
|
|
747
|
-
// refPaths [0] is the current spec file path
|
|
748
|
-
if (refPaths.length > 1) {
|
|
749
|
-
errors.push({
|
|
750
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
751
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
752
|
-
data: refPaths,
|
|
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,
|
|
753
595
|
});
|
|
754
596
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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({
|
|
759
617
|
type: ErrorType.NoSupportedApi,
|
|
760
618
|
content: ConstantString.NoSupportedApi,
|
|
619
|
+
data,
|
|
761
620
|
});
|
|
762
621
|
}
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
validateSpecOperationId() {
|
|
625
|
+
const result = { errors: [], warnings: [] };
|
|
626
|
+
const apiMap = this.listAPIs();
|
|
763
627
|
// OperationId missing
|
|
764
628
|
const apisMissingOperationId = [];
|
|
765
629
|
for (const key in apiMap) {
|
|
766
|
-
const
|
|
767
|
-
if (!
|
|
630
|
+
const { operation } = apiMap[key];
|
|
631
|
+
if (!operation.operationId) {
|
|
768
632
|
apisMissingOperationId.push(key);
|
|
769
633
|
}
|
|
770
634
|
}
|
|
771
635
|
if (apisMissingOperationId.length > 0) {
|
|
772
|
-
warnings.push({
|
|
636
|
+
result.warnings.push({
|
|
773
637
|
type: WarningType.OperationIdMissing,
|
|
774
638
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
775
639
|
data: apisMissingOperationId,
|
|
776
640
|
});
|
|
777
641
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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;
|
|
650
|
+
}
|
|
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;
|
|
656
|
+
}
|
|
657
|
+
return result;
|
|
658
|
+
}
|
|
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;
|
|
674
|
+
}
|
|
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);
|
|
781
681
|
}
|
|
782
|
-
else
|
|
783
|
-
|
|
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));
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
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: [] };
|
|
784
696
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
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: [],
|
|
789
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;
|
|
790
769
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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;
|
|
797
826
|
}
|
|
798
|
-
|
|
799
|
-
if (
|
|
800
|
-
|
|
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
|
+
}
|
|
801
835
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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}`);
|
|
805
1059
|
}
|
|
806
|
-
return safeRegistrationIdEnvName;
|
|
807
1060
|
}
|
|
808
1061
|
}
|
|
809
1062
|
|
|
@@ -823,9 +1076,14 @@ class SpecParser {
|
|
|
823
1076
|
allowSwagger: false,
|
|
824
1077
|
allowAPIKeyAuth: false,
|
|
825
1078
|
allowMultipleParameters: false,
|
|
1079
|
+
allowBearerTokenAuth: false,
|
|
826
1080
|
allowOauth2: false,
|
|
827
1081
|
allowMethods: ["get", "post"],
|
|
1082
|
+
allowConversationStarters: false,
|
|
1083
|
+
allowResponseSemantics: false,
|
|
1084
|
+
allowConfirmation: false,
|
|
828
1085
|
projectType: ProjectType.SME,
|
|
1086
|
+
isGptPlugin: false,
|
|
829
1087
|
};
|
|
830
1088
|
this.pathOrSpec = pathOrDoc;
|
|
831
1089
|
this.parser = new SwaggerParser();
|
|
@@ -841,11 +1099,7 @@ class SpecParser {
|
|
|
841
1099
|
try {
|
|
842
1100
|
try {
|
|
843
1101
|
yield this.loadSpec();
|
|
844
|
-
yield this.parser.validate(this.spec
|
|
845
|
-
validate: {
|
|
846
|
-
schema: false,
|
|
847
|
-
},
|
|
848
|
-
});
|
|
1102
|
+
yield this.parser.validate(this.spec);
|
|
849
1103
|
}
|
|
850
1104
|
catch (e) {
|
|
851
1105
|
return {
|
|
@@ -854,16 +1108,46 @@ class SpecParser {
|
|
|
854
1108
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
855
1109
|
};
|
|
856
1110
|
}
|
|
1111
|
+
const errors = [];
|
|
1112
|
+
const warnings = [];
|
|
857
1113
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
858
1114
|
return {
|
|
859
1115
|
status: ValidationStatus.Error,
|
|
860
1116
|
warnings: [],
|
|
861
1117
|
errors: [
|
|
862
|
-
{
|
|
1118
|
+
{
|
|
1119
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1120
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1121
|
+
},
|
|
863
1122
|
],
|
|
864
1123
|
};
|
|
865
1124
|
}
|
|
866
|
-
|
|
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
|
+
};
|
|
867
1151
|
}
|
|
868
1152
|
catch (err) {
|
|
869
1153
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -874,17 +1158,20 @@ class SpecParser {
|
|
|
874
1158
|
return __awaiter(this, void 0, void 0, function* () {
|
|
875
1159
|
try {
|
|
876
1160
|
yield this.loadSpec();
|
|
877
|
-
const apiMap = this.
|
|
1161
|
+
const apiMap = this.getAPIs(this.spec);
|
|
878
1162
|
const apiInfos = [];
|
|
879
1163
|
for (const key in apiMap) {
|
|
880
|
-
const
|
|
1164
|
+
const { operation, isValid } = apiMap[key];
|
|
1165
|
+
if (!isValid) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
881
1168
|
const [method, path] = key.split(" ");
|
|
882
|
-
const operationId =
|
|
1169
|
+
const operationId = operation.operationId;
|
|
883
1170
|
// In Browser environment, this api is by default not support api without operationId
|
|
884
1171
|
if (!operationId) {
|
|
885
1172
|
continue;
|
|
886
1173
|
}
|
|
887
|
-
const
|
|
1174
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
888
1175
|
const apiInfo = {
|
|
889
1176
|
method: method,
|
|
890
1177
|
path: path,
|
|
@@ -893,9 +1180,6 @@ class SpecParser {
|
|
|
893
1180
|
parameters: command.parameters,
|
|
894
1181
|
description: command.description,
|
|
895
1182
|
};
|
|
896
|
-
if (warning) {
|
|
897
|
-
apiInfo.warning = warning;
|
|
898
|
-
}
|
|
899
1183
|
apiInfos.push(apiInfo);
|
|
900
1184
|
}
|
|
901
1185
|
return apiInfos;
|
|
@@ -965,13 +1249,18 @@ class SpecParser {
|
|
|
965
1249
|
}
|
|
966
1250
|
});
|
|
967
1251
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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;
|
|
971
1260
|
}
|
|
972
|
-
const
|
|
973
|
-
this.
|
|
974
|
-
return
|
|
1261
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1262
|
+
this.validator = validator;
|
|
1263
|
+
return validator;
|
|
975
1264
|
}
|
|
976
1265
|
}
|
|
977
1266
|
|
|
@@ -979,7 +1268,7 @@ class SpecParser {
|
|
|
979
1268
|
class AdaptiveCardGenerator {
|
|
980
1269
|
static generateAdaptiveCard(operationItem) {
|
|
981
1270
|
try {
|
|
982
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1271
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
983
1272
|
let cardBody = [];
|
|
984
1273
|
let schema = json.schema;
|
|
985
1274
|
let jsonPath = "$";
|