@microsoft/m365-spec-parser 0.1.1-alpha.f04ac4ba4.0 → 0.1.1-alpha.f2bd7316b.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 +635 -334
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1076 -631
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +635 -334
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1089 -640
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +9 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +83 -20
- package/dist/src/manifestUpdater.d.ts +9 -6
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +17 -33
- 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 = {}));
|
|
@@ -71,7 +87,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
71
87
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
72
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.";
|
|
73
89
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
74
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
90
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
75
91
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
76
92
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
77
93
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -79,7 +95,8 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
79
95
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
80
96
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
81
97
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
82
|
-
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.";
|
|
83
100
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
84
101
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
85
102
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
@@ -90,8 +107,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
90
107
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
91
108
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
92
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
110
|
+
ConstantString.ImageType = "Image";
|
|
93
111
|
ConstantString.ContainerType = "Container";
|
|
94
|
-
ConstantString.RegistrationIdPostfix =
|
|
112
|
+
ConstantString.RegistrationIdPostfix = {
|
|
113
|
+
apiKey: "REGISTRATION_ID",
|
|
114
|
+
oauth2: "CONFIGURATION_ID",
|
|
115
|
+
http: "REGISTRATION_ID",
|
|
116
|
+
openIdConnect: "REGISTRATION_ID",
|
|
117
|
+
};
|
|
95
118
|
ConstantString.ResponseCodeFor20X = [
|
|
96
119
|
"200",
|
|
97
120
|
"201",
|
|
@@ -150,9 +173,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
150
173
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
151
174
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
152
175
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
176
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
153
177
|
ConstantString.CommandTitleMaxLens = 32;
|
|
154
178
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
155
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
179
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
180
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
156
181
|
|
|
157
182
|
// Copyright (c) Microsoft Corporation.
|
|
158
183
|
class Utils {
|
|
@@ -167,242 +192,33 @@ class Utils {
|
|
|
167
192
|
}
|
|
168
193
|
return false;
|
|
169
194
|
}
|
|
170
|
-
static
|
|
171
|
-
|
|
172
|
-
requiredNum: 0,
|
|
173
|
-
optionalNum: 0,
|
|
174
|
-
isValid: true,
|
|
175
|
-
};
|
|
176
|
-
if (!paramObject) {
|
|
177
|
-
return paramResult;
|
|
178
|
-
}
|
|
179
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
180
|
-
const param = paramObject[i];
|
|
181
|
-
const schema = param.schema;
|
|
182
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
183
|
-
paramResult.isValid = false;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
187
|
-
if (isCopilot) {
|
|
188
|
-
if (isRequiredWithoutDefault) {
|
|
189
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
193
|
-
}
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
197
|
-
if (isRequiredWithoutDefault) {
|
|
198
|
-
paramResult.isValid = false;
|
|
199
|
-
}
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
if (schema.type !== "boolean" &&
|
|
203
|
-
schema.type !== "string" &&
|
|
204
|
-
schema.type !== "number" &&
|
|
205
|
-
schema.type !== "integer") {
|
|
206
|
-
if (isRequiredWithoutDefault) {
|
|
207
|
-
paramResult.isValid = false;
|
|
208
|
-
}
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (param.in === "query" || param.in === "path") {
|
|
212
|
-
if (isRequiredWithoutDefault) {
|
|
213
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return paramResult;
|
|
195
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
196
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
221
197
|
}
|
|
222
|
-
static
|
|
223
|
-
|
|
224
|
-
const paramResult = {
|
|
225
|
-
requiredNum: 0,
|
|
226
|
-
optionalNum: 0,
|
|
227
|
-
isValid: true,
|
|
228
|
-
};
|
|
229
|
-
if (Object.keys(schema).length === 0) {
|
|
230
|
-
return paramResult;
|
|
231
|
-
}
|
|
232
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
233
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
234
|
-
paramResult.isValid = false;
|
|
235
|
-
return paramResult;
|
|
236
|
-
}
|
|
237
|
-
if (schema.type === "string" ||
|
|
238
|
-
schema.type === "integer" ||
|
|
239
|
-
schema.type === "boolean" ||
|
|
240
|
-
schema.type === "number") {
|
|
241
|
-
if (isRequiredWithoutDefault) {
|
|
242
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else if (schema.type === "object") {
|
|
249
|
-
const { properties } = schema;
|
|
250
|
-
for (const property in properties) {
|
|
251
|
-
let isRequired = false;
|
|
252
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
253
|
-
isRequired = true;
|
|
254
|
-
}
|
|
255
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
256
|
-
paramResult.requiredNum += result.requiredNum;
|
|
257
|
-
paramResult.optionalNum += result.optionalNum;
|
|
258
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
263
|
-
paramResult.isValid = false;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return paramResult;
|
|
198
|
+
static isBearerTokenAuth(authScheme) {
|
|
199
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
267
200
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* @param {string} method - The HTTP method of the API.
|
|
271
|
-
* @param {string} path - The path of the API.
|
|
272
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
273
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
274
|
-
* @description The following APIs are supported:
|
|
275
|
-
* 1. only support Get/Post operation without auth property
|
|
276
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
277
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
278
|
-
* 4. request body + required parameters <= 1
|
|
279
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
280
|
-
* 6. only support request body with “application/json” content type
|
|
281
|
-
*/
|
|
282
|
-
static isSupportedApi(method, path, spec, options) {
|
|
283
|
-
var _a;
|
|
284
|
-
const pathObj = spec.paths[path];
|
|
285
|
-
method = method.toLocaleLowerCase();
|
|
286
|
-
if (pathObj) {
|
|
287
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
288
|
-
const securities = pathObj[method].security;
|
|
289
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
290
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
const operationObject = pathObj[method];
|
|
294
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
const paramObject = operationObject.parameters;
|
|
298
|
-
const requestBody = operationObject.requestBody;
|
|
299
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
300
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
301
|
-
if (mediaTypesCount > 1) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
305
|
-
if (Object.keys(responseJson).length === 0) {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
let requestBodyParamResult = {
|
|
309
|
-
requiredNum: 0,
|
|
310
|
-
optionalNum: 0,
|
|
311
|
-
isValid: true,
|
|
312
|
-
};
|
|
313
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
314
|
-
if (requestJsonBody) {
|
|
315
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
316
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
320
|
-
}
|
|
321
|
-
if (!requestBodyParamResult.isValid) {
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
325
|
-
if (!paramResult.isValid) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
// Copilot support arbitrary parameters
|
|
329
|
-
if (isCopilot) {
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
333
|
-
if (options.allowMultipleParameters &&
|
|
334
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
335
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
else if (requestBodyParamResult.requiredNum +
|
|
341
|
-
requestBodyParamResult.optionalNum +
|
|
342
|
-
paramResult.requiredNum +
|
|
343
|
-
paramResult.optionalNum ===
|
|
344
|
-
0) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
static isSupportedAuth(authSchemaArray, options) {
|
|
355
|
-
if (authSchemaArray.length === 0) {
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
if (options.allowAPIKeyAuth || options.allowOauth2) {
|
|
359
|
-
// Currently we don't support multiple auth in one operation
|
|
360
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
for (const auths of authSchemaArray) {
|
|
364
|
-
if (auths.length === 1) {
|
|
365
|
-
if (!options.allowOauth2 &&
|
|
366
|
-
options.allowAPIKeyAuth &&
|
|
367
|
-
Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
368
|
-
return true;
|
|
369
|
-
}
|
|
370
|
-
else if (!options.allowAPIKeyAuth &&
|
|
371
|
-
options.allowOauth2 &&
|
|
372
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
else if (options.allowAPIKeyAuth &&
|
|
376
|
-
options.allowOauth2 &&
|
|
377
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
378
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
static isAPIKeyAuth(authSchema) {
|
|
387
|
-
return authSchema.type === "apiKey";
|
|
201
|
+
static isAPIKeyAuth(authScheme) {
|
|
202
|
+
return authScheme.type === "apiKey";
|
|
388
203
|
}
|
|
389
|
-
static
|
|
390
|
-
return (
|
|
391
|
-
|
|
392
|
-
|
|
204
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
205
|
+
return !!(authScheme.type === "oauth2" &&
|
|
206
|
+
authScheme.flows &&
|
|
207
|
+
authScheme.flows.authorizationCode);
|
|
393
208
|
}
|
|
394
209
|
static getAuthArray(securities, spec) {
|
|
395
210
|
var _a;
|
|
396
211
|
const result = [];
|
|
397
212
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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];
|
|
401
217
|
const authArray = [];
|
|
402
218
|
for (const name in security) {
|
|
403
219
|
const auth = securitySchemas[name];
|
|
404
220
|
authArray.push({
|
|
405
|
-
|
|
221
|
+
authScheme: auth,
|
|
406
222
|
name: name,
|
|
407
223
|
});
|
|
408
224
|
}
|
|
@@ -414,24 +230,47 @@ class Utils {
|
|
|
414
230
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
415
231
|
return result;
|
|
416
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
|
+
}
|
|
417
252
|
static updateFirstLetter(str) {
|
|
418
253
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
419
254
|
}
|
|
420
255
|
static getResponseJson(operationObject) {
|
|
421
256
|
var _a, _b;
|
|
422
257
|
let json = {};
|
|
258
|
+
let multipleMediaType = false;
|
|
423
259
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
424
260
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
425
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
426
|
-
if (mediaTypesCount > 1) {
|
|
427
|
-
return {};
|
|
428
|
-
}
|
|
429
261
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
262
|
+
multipleMediaType = false;
|
|
430
263
|
json = responseObject.content["application/json"];
|
|
431
|
-
|
|
264
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
265
|
+
multipleMediaType = true;
|
|
266
|
+
json = {};
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
432
271
|
}
|
|
433
272
|
}
|
|
434
|
-
return json;
|
|
273
|
+
return { json, multipleMediaType };
|
|
435
274
|
}
|
|
436
275
|
static convertPathToCamelCase(path) {
|
|
437
276
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -451,10 +290,10 @@ class Utils {
|
|
|
451
290
|
return undefined;
|
|
452
291
|
}
|
|
453
292
|
}
|
|
454
|
-
static
|
|
293
|
+
static resolveEnv(str) {
|
|
455
294
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
456
|
-
let matches = placeHolderReg.exec(
|
|
457
|
-
let
|
|
295
|
+
let matches = placeHolderReg.exec(str);
|
|
296
|
+
let newStr = str;
|
|
458
297
|
while (matches != null) {
|
|
459
298
|
const envVar = matches[1];
|
|
460
299
|
const envVal = process.env[envVar];
|
|
@@ -462,17 +301,17 @@ class Utils {
|
|
|
462
301
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
463
302
|
}
|
|
464
303
|
else {
|
|
465
|
-
|
|
304
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
466
305
|
}
|
|
467
|
-
matches = placeHolderReg.exec(
|
|
306
|
+
matches = placeHolderReg.exec(str);
|
|
468
307
|
}
|
|
469
|
-
return
|
|
308
|
+
return newStr;
|
|
470
309
|
}
|
|
471
310
|
static checkServerUrl(servers) {
|
|
472
311
|
const errors = [];
|
|
473
312
|
let serverUrl;
|
|
474
313
|
try {
|
|
475
|
-
serverUrl = Utils.
|
|
314
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
476
315
|
}
|
|
477
316
|
catch (err) {
|
|
478
317
|
errors.push({
|
|
@@ -503,6 +342,7 @@ class Utils {
|
|
|
503
342
|
return errors;
|
|
504
343
|
}
|
|
505
344
|
static validateServer(spec, options) {
|
|
345
|
+
var _a;
|
|
506
346
|
const errors = [];
|
|
507
347
|
let hasTopLevelServers = false;
|
|
508
348
|
let hasPathLevelServers = false;
|
|
@@ -523,7 +363,7 @@ class Utils {
|
|
|
523
363
|
}
|
|
524
364
|
for (const method in methods) {
|
|
525
365
|
const operationObject = methods[method];
|
|
526
|
-
if (
|
|
366
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
527
367
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
528
368
|
hasOperationLevelServers = true;
|
|
529
369
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -566,6 +406,7 @@ class Utils {
|
|
|
566
406
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
567
407
|
}
|
|
568
408
|
if (isRequired && schema.default === undefined) {
|
|
409
|
+
parameter.isRequired = true;
|
|
569
410
|
requiredParams.push(parameter);
|
|
570
411
|
}
|
|
571
412
|
else {
|
|
@@ -629,6 +470,7 @@ class Utils {
|
|
|
629
470
|
}
|
|
630
471
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
631
472
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
473
|
+
parameter.isRequired = true;
|
|
632
474
|
requiredParams.push(parameter);
|
|
633
475
|
}
|
|
634
476
|
else {
|
|
@@ -648,13 +490,7 @@ class Utils {
|
|
|
648
490
|
}
|
|
649
491
|
}
|
|
650
492
|
const operationId = operationItem.operationId;
|
|
651
|
-
const parameters = [];
|
|
652
|
-
if (requiredParams.length !== 0) {
|
|
653
|
-
parameters.push(...requiredParams);
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
parameters.push(optionalParams[0]);
|
|
657
|
-
}
|
|
493
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
658
494
|
const command = {
|
|
659
495
|
context: ["compose"],
|
|
660
496
|
type: "query",
|
|
@@ -663,105 +499,534 @@ class Utils {
|
|
|
663
499
|
parameters: parameters,
|
|
664
500
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
665
501
|
};
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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 "";
|
|
673
514
|
}
|
|
674
|
-
|
|
515
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
516
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
517
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
518
|
+
}
|
|
519
|
+
return safeRegistrationIdEnvName;
|
|
675
520
|
}
|
|
676
|
-
static
|
|
677
|
-
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;
|
|
678
540
|
const result = {};
|
|
679
541
|
for (const path in paths) {
|
|
680
542
|
const methods = paths[path];
|
|
681
543
|
for (const method in methods) {
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
const
|
|
685
|
-
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
|
+
};
|
|
686
552
|
}
|
|
687
553
|
}
|
|
688
554
|
}
|
|
555
|
+
this.apiMap = result;
|
|
689
556
|
return result;
|
|
690
557
|
}
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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,
|
|
698
565
|
});
|
|
699
566
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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({
|
|
717
587
|
type: ErrorType.NoSupportedApi,
|
|
718
588
|
content: ConstantString.NoSupportedApi,
|
|
589
|
+
data,
|
|
719
590
|
});
|
|
720
591
|
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecOperationId() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
721
597
|
// OperationId missing
|
|
722
598
|
const apisMissingOperationId = [];
|
|
723
599
|
for (const key in apiMap) {
|
|
724
|
-
const
|
|
725
|
-
if (!
|
|
600
|
+
const { operation } = apiMap[key];
|
|
601
|
+
if (!operation.operationId) {
|
|
726
602
|
apisMissingOperationId.push(key);
|
|
727
603
|
}
|
|
728
604
|
}
|
|
729
605
|
if (apisMissingOperationId.length > 0) {
|
|
730
|
-
warnings.push({
|
|
606
|
+
result.warnings.push({
|
|
731
607
|
type: WarningType.OperationIdMissing,
|
|
732
608
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
733
609
|
data: apisMissingOperationId,
|
|
734
610
|
});
|
|
735
611
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
+
}
|
|
739
642
|
}
|
|
740
|
-
|
|
741
|
-
|
|
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);
|
|
742
651
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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));
|
|
656
|
+
}
|
|
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
|
+
}
|
|
686
|
+
}
|
|
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: [],
|
|
747
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;
|
|
748
739
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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;
|
|
755
796
|
}
|
|
756
|
-
|
|
757
|
-
if (
|
|
758
|
-
|
|
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
|
+
}
|
|
759
805
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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}`);
|
|
763
1029
|
}
|
|
764
|
-
return safeRegistrationIdEnvName;
|
|
765
1030
|
}
|
|
766
1031
|
}
|
|
767
1032
|
|
|
@@ -781,9 +1046,14 @@ class SpecParser {
|
|
|
781
1046
|
allowSwagger: false,
|
|
782
1047
|
allowAPIKeyAuth: false,
|
|
783
1048
|
allowMultipleParameters: false,
|
|
1049
|
+
allowBearerTokenAuth: false,
|
|
784
1050
|
allowOauth2: false,
|
|
785
1051
|
allowMethods: ["get", "post"],
|
|
1052
|
+
allowConversationStarters: false,
|
|
1053
|
+
allowResponseSemantics: false,
|
|
1054
|
+
allowConfirmation: false,
|
|
786
1055
|
projectType: ProjectType.SME,
|
|
1056
|
+
isGptPlugin: false,
|
|
787
1057
|
};
|
|
788
1058
|
this.pathOrSpec = pathOrDoc;
|
|
789
1059
|
this.parser = new SwaggerParser();
|
|
@@ -798,11 +1068,7 @@ class SpecParser {
|
|
|
798
1068
|
try {
|
|
799
1069
|
try {
|
|
800
1070
|
await this.loadSpec();
|
|
801
|
-
await this.parser.validate(this.spec
|
|
802
|
-
validate: {
|
|
803
|
-
schema: false,
|
|
804
|
-
},
|
|
805
|
-
});
|
|
1071
|
+
await this.parser.validate(this.spec);
|
|
806
1072
|
}
|
|
807
1073
|
catch (e) {
|
|
808
1074
|
return {
|
|
@@ -811,16 +1077,46 @@ class SpecParser {
|
|
|
811
1077
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
812
1078
|
};
|
|
813
1079
|
}
|
|
1080
|
+
const errors = [];
|
|
1081
|
+
const warnings = [];
|
|
814
1082
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
815
1083
|
return {
|
|
816
1084
|
status: ValidationStatus.Error,
|
|
817
1085
|
warnings: [],
|
|
818
1086
|
errors: [
|
|
819
|
-
{
|
|
1087
|
+
{
|
|
1088
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1089
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1090
|
+
},
|
|
820
1091
|
],
|
|
821
1092
|
};
|
|
822
1093
|
}
|
|
823
|
-
|
|
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
|
+
};
|
|
824
1120
|
}
|
|
825
1121
|
catch (err) {
|
|
826
1122
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -829,17 +1125,20 @@ class SpecParser {
|
|
|
829
1125
|
async listSupportedAPIInfo() {
|
|
830
1126
|
try {
|
|
831
1127
|
await this.loadSpec();
|
|
832
|
-
const apiMap = this.
|
|
1128
|
+
const apiMap = this.getAPIs(this.spec);
|
|
833
1129
|
const apiInfos = [];
|
|
834
1130
|
for (const key in apiMap) {
|
|
835
|
-
const
|
|
1131
|
+
const { operation, isValid } = apiMap[key];
|
|
1132
|
+
if (!isValid) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
836
1135
|
const [method, path] = key.split(" ");
|
|
837
|
-
const operationId =
|
|
1136
|
+
const operationId = operation.operationId;
|
|
838
1137
|
// In Browser environment, this api is by default not support api without operationId
|
|
839
1138
|
if (!operationId) {
|
|
840
1139
|
continue;
|
|
841
1140
|
}
|
|
842
|
-
const
|
|
1141
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
843
1142
|
const apiInfo = {
|
|
844
1143
|
method: method,
|
|
845
1144
|
path: path,
|
|
@@ -848,9 +1147,6 @@ class SpecParser {
|
|
|
848
1147
|
parameters: command.parameters,
|
|
849
1148
|
description: command.description,
|
|
850
1149
|
};
|
|
851
|
-
if (warning) {
|
|
852
|
-
apiInfo.warning = warning;
|
|
853
|
-
}
|
|
854
1150
|
apiInfos.push(apiInfo);
|
|
855
1151
|
}
|
|
856
1152
|
return apiInfos;
|
|
@@ -909,13 +1205,18 @@ class SpecParser {
|
|
|
909
1205
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
910
1206
|
}
|
|
911
1207
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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;
|
|
915
1216
|
}
|
|
916
|
-
const
|
|
917
|
-
this.
|
|
918
|
-
return
|
|
1217
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1218
|
+
this.validator = validator;
|
|
1219
|
+
return validator;
|
|
919
1220
|
}
|
|
920
1221
|
}
|
|
921
1222
|
|
|
@@ -923,7 +1224,7 @@ class SpecParser {
|
|
|
923
1224
|
class AdaptiveCardGenerator {
|
|
924
1225
|
static generateAdaptiveCard(operationItem) {
|
|
925
1226
|
try {
|
|
926
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1227
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
927
1228
|
let cardBody = [];
|
|
928
1229
|
let schema = json.schema;
|
|
929
1230
|
let jsonPath = "$";
|