@microsoft/m365-spec-parser 0.1.1-alpha.8d8f5a0bb.0 → 0.1.1-alpha.8da24c130.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 +650 -337
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1097 -633
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +650 -337
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1109 -641
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +10 -3
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +110 -19
- package/dist/src/manifestUpdater.d.ts +10 -7
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +4 -3
- package/dist/src/specParser.d.ts +6 -5
- package/dist/src/utils.d.ts +18 -34
- package/package.json +3 -3
package/dist/index.esm5.js
CHANGED
|
@@ -45,7 +45,8 @@ var ErrorType;
|
|
|
45
45
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
46
46
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
47
47
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
48
|
-
ErrorType["
|
|
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 = {}));
|
|
@@ -76,7 +92,13 @@ var ValidationStatus;
|
|
|
76
92
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
77
93
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
78
94
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
79
|
-
})(ValidationStatus || (ValidationStatus = {}));
|
|
95
|
+
})(ValidationStatus || (ValidationStatus = {}));
|
|
96
|
+
var ProjectType;
|
|
97
|
+
(function (ProjectType) {
|
|
98
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
99
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
100
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
101
|
+
})(ProjectType || (ProjectType = {}));
|
|
80
102
|
|
|
81
103
|
// Copyright (c) Microsoft Corporation.
|
|
82
104
|
class SpecParserError extends Error {
|
|
@@ -95,7 +117,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
95
117
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
96
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.";
|
|
97
119
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
98
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
120
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
99
121
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
100
122
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
101
123
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -103,7 +125,8 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
103
125
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
104
126
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
105
127
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
106
|
-
ConstantString.
|
|
128
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
129
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
107
130
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
108
131
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
109
132
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
@@ -114,8 +137,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
114
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
115
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
116
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
117
141
|
ConstantString.ContainerType = "Container";
|
|
118
|
-
ConstantString.RegistrationIdPostfix =
|
|
142
|
+
ConstantString.RegistrationIdPostfix = {
|
|
143
|
+
apiKey: "REGISTRATION_ID",
|
|
144
|
+
oauth2: "CONFIGURATION_ID",
|
|
145
|
+
http: "REGISTRATION_ID",
|
|
146
|
+
openIdConnect: "REGISTRATION_ID",
|
|
147
|
+
};
|
|
119
148
|
ConstantString.ResponseCodeFor20X = [
|
|
120
149
|
"200",
|
|
121
150
|
"201",
|
|
@@ -174,8 +203,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
174
203
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
175
204
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
176
205
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
206
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
177
207
|
ConstantString.CommandTitleMaxLens = 32;
|
|
178
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
208
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
209
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
210
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
179
211
|
|
|
180
212
|
// Copyright (c) Microsoft Corporation.
|
|
181
213
|
class Utils {
|
|
@@ -190,238 +222,33 @@ class Utils {
|
|
|
190
222
|
}
|
|
191
223
|
return false;
|
|
192
224
|
}
|
|
193
|
-
static
|
|
194
|
-
|
|
195
|
-
requiredNum: 0,
|
|
196
|
-
optionalNum: 0,
|
|
197
|
-
isValid: true,
|
|
198
|
-
};
|
|
199
|
-
if (!paramObject) {
|
|
200
|
-
return paramResult;
|
|
201
|
-
}
|
|
202
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
203
|
-
const param = paramObject[i];
|
|
204
|
-
const schema = param.schema;
|
|
205
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
206
|
-
paramResult.isValid = false;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
210
|
-
if (isCopilot) {
|
|
211
|
-
if (isRequiredWithoutDefault) {
|
|
212
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
216
|
-
}
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
220
|
-
if (isRequiredWithoutDefault) {
|
|
221
|
-
paramResult.isValid = false;
|
|
222
|
-
}
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (schema.type !== "boolean" &&
|
|
226
|
-
schema.type !== "string" &&
|
|
227
|
-
schema.type !== "number" &&
|
|
228
|
-
schema.type !== "integer") {
|
|
229
|
-
if (isRequiredWithoutDefault) {
|
|
230
|
-
paramResult.isValid = false;
|
|
231
|
-
}
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (param.in === "query" || param.in === "path") {
|
|
235
|
-
if (isRequiredWithoutDefault) {
|
|
236
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return paramResult;
|
|
225
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
226
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
244
227
|
}
|
|
245
|
-
static
|
|
246
|
-
|
|
247
|
-
const paramResult = {
|
|
248
|
-
requiredNum: 0,
|
|
249
|
-
optionalNum: 0,
|
|
250
|
-
isValid: true,
|
|
251
|
-
};
|
|
252
|
-
if (Object.keys(schema).length === 0) {
|
|
253
|
-
return paramResult;
|
|
254
|
-
}
|
|
255
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
256
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
257
|
-
paramResult.isValid = false;
|
|
258
|
-
return paramResult;
|
|
259
|
-
}
|
|
260
|
-
if (schema.type === "string" ||
|
|
261
|
-
schema.type === "integer" ||
|
|
262
|
-
schema.type === "boolean" ||
|
|
263
|
-
schema.type === "number") {
|
|
264
|
-
if (isRequiredWithoutDefault) {
|
|
265
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
else if (schema.type === "object") {
|
|
272
|
-
const { properties } = schema;
|
|
273
|
-
for (const property in properties) {
|
|
274
|
-
let isRequired = false;
|
|
275
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
276
|
-
isRequired = true;
|
|
277
|
-
}
|
|
278
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
279
|
-
paramResult.requiredNum += result.requiredNum;
|
|
280
|
-
paramResult.optionalNum += result.optionalNum;
|
|
281
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
286
|
-
paramResult.isValid = false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return paramResult;
|
|
228
|
+
static isBearerTokenAuth(authScheme) {
|
|
229
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
290
230
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
* @param {string} method - The HTTP method of the API.
|
|
294
|
-
* @param {string} path - The path of the API.
|
|
295
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
296
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
297
|
-
* @description The following APIs are supported:
|
|
298
|
-
* 1. only support Get/Post operation without auth property
|
|
299
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
300
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
301
|
-
* 4. request body + required parameters <= 1
|
|
302
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
303
|
-
* 6. only support request body with “application/json” content type
|
|
304
|
-
*/
|
|
305
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot) {
|
|
306
|
-
const pathObj = spec.paths[path];
|
|
307
|
-
method = method.toLocaleLowerCase();
|
|
308
|
-
if (pathObj) {
|
|
309
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
310
|
-
pathObj[method]) {
|
|
311
|
-
const securities = pathObj[method].security;
|
|
312
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
313
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
const operationObject = pathObj[method];
|
|
317
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
const paramObject = operationObject.parameters;
|
|
321
|
-
const requestBody = operationObject.requestBody;
|
|
322
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
323
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
324
|
-
if (mediaTypesCount > 1) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
328
|
-
if (Object.keys(responseJson).length === 0) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
let requestBodyParamResult = {
|
|
332
|
-
requiredNum: 0,
|
|
333
|
-
optionalNum: 0,
|
|
334
|
-
isValid: true,
|
|
335
|
-
};
|
|
336
|
-
if (requestJsonBody) {
|
|
337
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
338
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
342
|
-
}
|
|
343
|
-
if (!requestBodyParamResult.isValid) {
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
347
|
-
if (!paramResult.isValid) {
|
|
348
|
-
return false;
|
|
349
|
-
}
|
|
350
|
-
// Copilot support arbitrary parameters
|
|
351
|
-
if (isCopilot) {
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
355
|
-
if (allowMultipleParameters &&
|
|
356
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
else if (requestBodyParamResult.requiredNum +
|
|
362
|
-
requestBodyParamResult.optionalNum +
|
|
363
|
-
paramResult.requiredNum +
|
|
364
|
-
paramResult.optionalNum ===
|
|
365
|
-
0) {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return false;
|
|
231
|
+
static isAPIKeyAuth(authScheme) {
|
|
232
|
+
return authScheme.type === "apiKey";
|
|
374
233
|
}
|
|
375
|
-
static
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (allowAPIKeyAuth || allowOauth2) {
|
|
380
|
-
// Currently we don't support multiple auth in one operation
|
|
381
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
for (const auths of authSchemaArray) {
|
|
385
|
-
if (auths.length === 1) {
|
|
386
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
else if (!allowAPIKeyAuth &&
|
|
390
|
-
allowOauth2 &&
|
|
391
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
392
|
-
return true;
|
|
393
|
-
}
|
|
394
|
-
else if (allowAPIKeyAuth &&
|
|
395
|
-
allowOauth2 &&
|
|
396
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
397
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
static isAPIKeyAuth(authSchema) {
|
|
406
|
-
return authSchema.type === "apiKey";
|
|
407
|
-
}
|
|
408
|
-
static isBearerTokenAuth(authSchema) {
|
|
409
|
-
return (authSchema.type === "oauth2" ||
|
|
410
|
-
authSchema.type === "openIdConnect" ||
|
|
411
|
-
(authSchema.type === "http" && authSchema.scheme === "bearer"));
|
|
234
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
235
|
+
return !!(authScheme.type === "oauth2" &&
|
|
236
|
+
authScheme.flows &&
|
|
237
|
+
authScheme.flows.authorizationCode);
|
|
412
238
|
}
|
|
413
239
|
static getAuthArray(securities, spec) {
|
|
414
240
|
var _a;
|
|
415
241
|
const result = [];
|
|
416
242
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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];
|
|
420
247
|
const authArray = [];
|
|
421
248
|
for (const name in security) {
|
|
422
249
|
const auth = securitySchemas[name];
|
|
423
250
|
authArray.push({
|
|
424
|
-
|
|
251
|
+
authScheme: auth,
|
|
425
252
|
name: name,
|
|
426
253
|
});
|
|
427
254
|
}
|
|
@@ -433,24 +260,47 @@ class Utils {
|
|
|
433
260
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
434
261
|
return result;
|
|
435
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
|
+
}
|
|
436
282
|
static updateFirstLetter(str) {
|
|
437
283
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
438
284
|
}
|
|
439
285
|
static getResponseJson(operationObject) {
|
|
440
286
|
var _a, _b;
|
|
441
287
|
let json = {};
|
|
288
|
+
let multipleMediaType = false;
|
|
442
289
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
443
290
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
444
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
445
|
-
if (mediaTypesCount > 1) {
|
|
446
|
-
return {};
|
|
447
|
-
}
|
|
448
291
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
292
|
+
multipleMediaType = false;
|
|
449
293
|
json = responseObject.content["application/json"];
|
|
450
|
-
|
|
294
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
295
|
+
multipleMediaType = true;
|
|
296
|
+
json = {};
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
451
301
|
}
|
|
452
302
|
}
|
|
453
|
-
return json;
|
|
303
|
+
return { json, multipleMediaType };
|
|
454
304
|
}
|
|
455
305
|
static convertPathToCamelCase(path) {
|
|
456
306
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -470,10 +320,10 @@ class Utils {
|
|
|
470
320
|
return undefined;
|
|
471
321
|
}
|
|
472
322
|
}
|
|
473
|
-
static
|
|
323
|
+
static resolveEnv(str) {
|
|
474
324
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
475
|
-
let matches = placeHolderReg.exec(
|
|
476
|
-
let
|
|
325
|
+
let matches = placeHolderReg.exec(str);
|
|
326
|
+
let newStr = str;
|
|
477
327
|
while (matches != null) {
|
|
478
328
|
const envVar = matches[1];
|
|
479
329
|
const envVal = process.env[envVar];
|
|
@@ -481,17 +331,17 @@ class Utils {
|
|
|
481
331
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
482
332
|
}
|
|
483
333
|
else {
|
|
484
|
-
|
|
334
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
485
335
|
}
|
|
486
|
-
matches = placeHolderReg.exec(
|
|
336
|
+
matches = placeHolderReg.exec(str);
|
|
487
337
|
}
|
|
488
|
-
return
|
|
338
|
+
return newStr;
|
|
489
339
|
}
|
|
490
340
|
static checkServerUrl(servers) {
|
|
491
341
|
const errors = [];
|
|
492
342
|
let serverUrl;
|
|
493
343
|
try {
|
|
494
|
-
serverUrl = Utils.
|
|
344
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
495
345
|
}
|
|
496
346
|
catch (err) {
|
|
497
347
|
errors.push({
|
|
@@ -521,7 +371,8 @@ class Utils {
|
|
|
521
371
|
}
|
|
522
372
|
return errors;
|
|
523
373
|
}
|
|
524
|
-
static validateServer(spec,
|
|
374
|
+
static validateServer(spec, options) {
|
|
375
|
+
var _a;
|
|
525
376
|
const errors = [];
|
|
526
377
|
let hasTopLevelServers = false;
|
|
527
378
|
let hasPathLevelServers = false;
|
|
@@ -542,7 +393,7 @@ class Utils {
|
|
|
542
393
|
}
|
|
543
394
|
for (const method in methods) {
|
|
544
395
|
const operationObject = methods[method];
|
|
545
|
-
if (
|
|
396
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
546
397
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
547
398
|
hasOperationLevelServers = true;
|
|
548
399
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -585,6 +436,7 @@ class Utils {
|
|
|
585
436
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
586
437
|
}
|
|
587
438
|
if (isRequired && schema.default === undefined) {
|
|
439
|
+
parameter.isRequired = true;
|
|
588
440
|
requiredParams.push(parameter);
|
|
589
441
|
}
|
|
590
442
|
else {
|
|
@@ -629,7 +481,7 @@ class Utils {
|
|
|
629
481
|
param.value = schema.default;
|
|
630
482
|
}
|
|
631
483
|
}
|
|
632
|
-
static parseApiInfo(operationItem,
|
|
484
|
+
static parseApiInfo(operationItem, options) {
|
|
633
485
|
var _a, _b;
|
|
634
486
|
const requiredParams = [];
|
|
635
487
|
const optionalParams = [];
|
|
@@ -643,11 +495,12 @@ class Utils {
|
|
|
643
495
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
644
496
|
};
|
|
645
497
|
const schema = param.schema;
|
|
646
|
-
if (allowMultipleParameters && schema) {
|
|
498
|
+
if (options.allowMultipleParameters && schema) {
|
|
647
499
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
648
500
|
}
|
|
649
501
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
650
502
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
503
|
+
parameter.isRequired = true;
|
|
651
504
|
requiredParams.push(parameter);
|
|
652
505
|
}
|
|
653
506
|
else {
|
|
@@ -661,19 +514,13 @@ class Utils {
|
|
|
661
514
|
const requestJson = requestBody.content["application/json"];
|
|
662
515
|
if (Object.keys(requestJson).length !== 0) {
|
|
663
516
|
const schema = requestJson.schema;
|
|
664
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
517
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
665
518
|
requiredParams.push(...requiredP);
|
|
666
519
|
optionalParams.push(...optionalP);
|
|
667
520
|
}
|
|
668
521
|
}
|
|
669
522
|
const operationId = operationItem.operationId;
|
|
670
|
-
const parameters = [];
|
|
671
|
-
if (requiredParams.length !== 0) {
|
|
672
|
-
parameters.push(...requiredParams);
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
parameters.push(optionalParams[0]);
|
|
676
|
-
}
|
|
523
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
677
524
|
const command = {
|
|
678
525
|
context: ["compose"],
|
|
679
526
|
type: "query",
|
|
@@ -682,105 +529,534 @@ class Utils {
|
|
|
682
529
|
parameters: parameters,
|
|
683
530
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
684
531
|
};
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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;
|
|
692
548
|
}
|
|
693
|
-
return
|
|
549
|
+
return safeRegistrationIdEnvName;
|
|
694
550
|
}
|
|
695
|
-
static
|
|
696
|
-
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;
|
|
697
570
|
const result = {};
|
|
698
571
|
for (const path in paths) {
|
|
699
572
|
const methods = paths[path];
|
|
700
573
|
for (const method in methods) {
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
const
|
|
704
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
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
|
+
};
|
|
705
582
|
}
|
|
706
583
|
}
|
|
707
584
|
}
|
|
585
|
+
this.apiMap = result;
|
|
708
586
|
return result;
|
|
709
587
|
}
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
// Server validation
|
|
720
|
-
const serverErrors = Utils.validateServer(spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2, isCopilot);
|
|
721
|
-
errors.push(...serverErrors);
|
|
722
|
-
// Remote reference not supported
|
|
723
|
-
const refPaths = parser.$refs.paths();
|
|
724
|
-
// refPaths [0] is the current spec file path
|
|
725
|
-
if (refPaths.length > 1) {
|
|
726
|
-
errors.push({
|
|
727
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
728
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
729
|
-
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,
|
|
730
595
|
});
|
|
731
596
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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({
|
|
736
617
|
type: ErrorType.NoSupportedApi,
|
|
737
618
|
content: ConstantString.NoSupportedApi,
|
|
619
|
+
data,
|
|
738
620
|
});
|
|
739
621
|
}
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
validateSpecOperationId() {
|
|
625
|
+
const result = { errors: [], warnings: [] };
|
|
626
|
+
const apiMap = this.listAPIs();
|
|
740
627
|
// OperationId missing
|
|
741
628
|
const apisMissingOperationId = [];
|
|
742
629
|
for (const key in apiMap) {
|
|
743
|
-
const
|
|
744
|
-
if (!
|
|
630
|
+
const { operation } = apiMap[key];
|
|
631
|
+
if (!operation.operationId) {
|
|
745
632
|
apisMissingOperationId.push(key);
|
|
746
633
|
}
|
|
747
634
|
}
|
|
748
635
|
if (apisMissingOperationId.length > 0) {
|
|
749
|
-
warnings.push({
|
|
636
|
+
result.warnings.push({
|
|
750
637
|
type: WarningType.OperationIdMissing,
|
|
751
638
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
752
639
|
data: apisMissingOperationId,
|
|
753
640
|
});
|
|
754
641
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
+
}
|
|
758
672
|
}
|
|
759
|
-
|
|
760
|
-
|
|
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);
|
|
761
681
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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: [] };
|
|
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
|
+
}
|
|
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: [],
|
|
766
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;
|
|
767
769
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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;
|
|
774
826
|
}
|
|
775
|
-
|
|
776
|
-
if (
|
|
777
|
-
|
|
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
|
+
}
|
|
778
835
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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}`);
|
|
782
1059
|
}
|
|
783
|
-
return safeRegistrationIdEnvName;
|
|
784
1060
|
}
|
|
785
1061
|
}
|
|
786
1062
|
|
|
@@ -800,8 +1076,14 @@ class SpecParser {
|
|
|
800
1076
|
allowSwagger: false,
|
|
801
1077
|
allowAPIKeyAuth: false,
|
|
802
1078
|
allowMultipleParameters: false,
|
|
1079
|
+
allowBearerTokenAuth: false,
|
|
803
1080
|
allowOauth2: false,
|
|
804
|
-
|
|
1081
|
+
allowMethods: ["get", "post"],
|
|
1082
|
+
allowConversationStarters: false,
|
|
1083
|
+
allowResponseSemantics: false,
|
|
1084
|
+
allowConfirmation: false,
|
|
1085
|
+
projectType: ProjectType.SME,
|
|
1086
|
+
isGptPlugin: false,
|
|
805
1087
|
};
|
|
806
1088
|
this.pathOrSpec = pathOrDoc;
|
|
807
1089
|
this.parser = new SwaggerParser();
|
|
@@ -817,11 +1099,7 @@ class SpecParser {
|
|
|
817
1099
|
try {
|
|
818
1100
|
try {
|
|
819
1101
|
yield this.loadSpec();
|
|
820
|
-
yield this.parser.validate(this.spec
|
|
821
|
-
validate: {
|
|
822
|
-
schema: false,
|
|
823
|
-
},
|
|
824
|
-
});
|
|
1102
|
+
yield this.parser.validate(this.spec);
|
|
825
1103
|
}
|
|
826
1104
|
catch (e) {
|
|
827
1105
|
return {
|
|
@@ -830,16 +1108,46 @@ class SpecParser {
|
|
|
830
1108
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
831
1109
|
};
|
|
832
1110
|
}
|
|
1111
|
+
const errors = [];
|
|
1112
|
+
const warnings = [];
|
|
833
1113
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
834
1114
|
return {
|
|
835
1115
|
status: ValidationStatus.Error,
|
|
836
1116
|
warnings: [],
|
|
837
1117
|
errors: [
|
|
838
|
-
{
|
|
1118
|
+
{
|
|
1119
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1120
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1121
|
+
},
|
|
839
1122
|
],
|
|
840
1123
|
};
|
|
841
1124
|
}
|
|
842
|
-
|
|
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
|
+
};
|
|
843
1151
|
}
|
|
844
1152
|
catch (err) {
|
|
845
1153
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -850,17 +1158,20 @@ class SpecParser {
|
|
|
850
1158
|
return __awaiter(this, void 0, void 0, function* () {
|
|
851
1159
|
try {
|
|
852
1160
|
yield this.loadSpec();
|
|
853
|
-
const apiMap = this.
|
|
1161
|
+
const apiMap = this.getAPIs(this.spec);
|
|
854
1162
|
const apiInfos = [];
|
|
855
1163
|
for (const key in apiMap) {
|
|
856
|
-
const
|
|
1164
|
+
const { operation, isValid } = apiMap[key];
|
|
1165
|
+
if (!isValid) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
857
1168
|
const [method, path] = key.split(" ");
|
|
858
|
-
const operationId =
|
|
1169
|
+
const operationId = operation.operationId;
|
|
859
1170
|
// In Browser environment, this api is by default not support api without operationId
|
|
860
1171
|
if (!operationId) {
|
|
861
1172
|
continue;
|
|
862
1173
|
}
|
|
863
|
-
const
|
|
1174
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
864
1175
|
const apiInfo = {
|
|
865
1176
|
method: method,
|
|
866
1177
|
path: path,
|
|
@@ -869,9 +1180,6 @@ class SpecParser {
|
|
|
869
1180
|
parameters: command.parameters,
|
|
870
1181
|
description: command.description,
|
|
871
1182
|
};
|
|
872
|
-
if (warning) {
|
|
873
|
-
apiInfo.warning = warning;
|
|
874
|
-
}
|
|
875
1183
|
apiInfos.push(apiInfo);
|
|
876
1184
|
}
|
|
877
1185
|
return apiInfos;
|
|
@@ -924,7 +1232,7 @@ class SpecParser {
|
|
|
924
1232
|
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
925
1233
|
*/
|
|
926
1234
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
927
|
-
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal
|
|
1235
|
+
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
928
1236
|
return __awaiter(this, void 0, void 0, function* () {
|
|
929
1237
|
throw new Error("Method not implemented.");
|
|
930
1238
|
});
|
|
@@ -941,13 +1249,18 @@ class SpecParser {
|
|
|
941
1249
|
}
|
|
942
1250
|
});
|
|
943
1251
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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;
|
|
947
1260
|
}
|
|
948
|
-
const
|
|
949
|
-
this.
|
|
950
|
-
return
|
|
1261
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1262
|
+
this.validator = validator;
|
|
1263
|
+
return validator;
|
|
951
1264
|
}
|
|
952
1265
|
}
|
|
953
1266
|
|
|
@@ -955,7 +1268,7 @@ class SpecParser {
|
|
|
955
1268
|
class AdaptiveCardGenerator {
|
|
956
1269
|
static generateAdaptiveCard(operationItem) {
|
|
957
1270
|
try {
|
|
958
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1271
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
959
1272
|
let cardBody = [];
|
|
960
1273
|
let schema = json.schema;
|
|
961
1274
|
let jsonPath = "$";
|