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