@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.3bb446445.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm2017.js +615 -334
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1053 -635
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +615 -334
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1062 -640
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +8 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +66 -1
- package/dist/src/manifestUpdater.d.ts +7 -4
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +4 -2
- package/dist/src/utils.d.ts +9 -28
- package/package.json +3 -4
package/dist/index.esm2017.js
CHANGED
|
@@ -16,6 +16,7 @@ var ErrorType;
|
|
|
16
16
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
17
17
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
18
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,6 +95,7 @@ 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.";
|
|
98
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
82
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";
|
|
@@ -90,9 +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 =
|
|
95
|
-
|
|
112
|
+
ConstantString.RegistrationIdPostfix = {
|
|
113
|
+
apiKey: "REGISTRATION_ID",
|
|
114
|
+
oauth2: "CONFIGURATION_ID",
|
|
115
|
+
http: "REGISTRATION_ID",
|
|
116
|
+
openIdConnect: "REGISTRATION_ID",
|
|
117
|
+
};
|
|
96
118
|
ConstantString.ResponseCodeFor20X = [
|
|
97
119
|
"200",
|
|
98
120
|
"201",
|
|
@@ -151,9 +173,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
151
173
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
152
174
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
153
175
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
176
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
154
177
|
ConstantString.CommandTitleMaxLens = 32;
|
|
155
178
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
156
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
179
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
180
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
157
181
|
|
|
158
182
|
// Copyright (c) Microsoft Corporation.
|
|
159
183
|
class Utils {
|
|
@@ -168,221 +192,9 @@ class Utils {
|
|
|
168
192
|
}
|
|
169
193
|
return false;
|
|
170
194
|
}
|
|
171
|
-
static checkParameters(paramObject, isCopilot) {
|
|
172
|
-
const paramResult = {
|
|
173
|
-
requiredNum: 0,
|
|
174
|
-
optionalNum: 0,
|
|
175
|
-
isValid: true,
|
|
176
|
-
};
|
|
177
|
-
if (!paramObject) {
|
|
178
|
-
return paramResult;
|
|
179
|
-
}
|
|
180
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
181
|
-
const param = paramObject[i];
|
|
182
|
-
const schema = param.schema;
|
|
183
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
184
|
-
paramResult.isValid = false;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
188
|
-
if (isCopilot) {
|
|
189
|
-
if (isRequiredWithoutDefault) {
|
|
190
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
194
|
-
}
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
198
|
-
if (isRequiredWithoutDefault) {
|
|
199
|
-
paramResult.isValid = false;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (schema.type !== "boolean" &&
|
|
204
|
-
schema.type !== "string" &&
|
|
205
|
-
schema.type !== "number" &&
|
|
206
|
-
schema.type !== "integer") {
|
|
207
|
-
if (isRequiredWithoutDefault) {
|
|
208
|
-
paramResult.isValid = false;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (param.in === "query" || param.in === "path") {
|
|
213
|
-
if (isRequiredWithoutDefault) {
|
|
214
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return paramResult;
|
|
222
|
-
}
|
|
223
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
224
|
-
var _a;
|
|
225
|
-
const paramResult = {
|
|
226
|
-
requiredNum: 0,
|
|
227
|
-
optionalNum: 0,
|
|
228
|
-
isValid: true,
|
|
229
|
-
};
|
|
230
|
-
if (Object.keys(schema).length === 0) {
|
|
231
|
-
return paramResult;
|
|
232
|
-
}
|
|
233
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
234
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
235
|
-
paramResult.isValid = false;
|
|
236
|
-
return paramResult;
|
|
237
|
-
}
|
|
238
|
-
if (schema.type === "string" ||
|
|
239
|
-
schema.type === "integer" ||
|
|
240
|
-
schema.type === "boolean" ||
|
|
241
|
-
schema.type === "number") {
|
|
242
|
-
if (isRequiredWithoutDefault) {
|
|
243
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
else if (schema.type === "object") {
|
|
250
|
-
const { properties } = schema;
|
|
251
|
-
for (const property in properties) {
|
|
252
|
-
let isRequired = false;
|
|
253
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
254
|
-
isRequired = true;
|
|
255
|
-
}
|
|
256
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
257
|
-
paramResult.requiredNum += result.requiredNum;
|
|
258
|
-
paramResult.optionalNum += result.optionalNum;
|
|
259
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
264
|
-
paramResult.isValid = false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return paramResult;
|
|
268
|
-
}
|
|
269
195
|
static containMultipleMediaTypes(bodyObject) {
|
|
270
196
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
271
197
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Checks if the given API is supported.
|
|
274
|
-
* @param {string} method - The HTTP method of the API.
|
|
275
|
-
* @param {string} path - The path of the API.
|
|
276
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
277
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
278
|
-
* @description The following APIs are supported:
|
|
279
|
-
* 1. only support Get/Post operation without auth property
|
|
280
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
281
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
282
|
-
* 4. request body + required parameters <= 1
|
|
283
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
284
|
-
* 6. only support request body with “application/json” content type
|
|
285
|
-
*/
|
|
286
|
-
static isSupportedApi(method, path, spec, options) {
|
|
287
|
-
var _a;
|
|
288
|
-
const pathObj = spec.paths[path];
|
|
289
|
-
method = method.toLocaleLowerCase();
|
|
290
|
-
if (pathObj) {
|
|
291
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
292
|
-
const securities = pathObj[method].security;
|
|
293
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
294
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
295
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
296
|
-
if (!isTeamsAi) {
|
|
297
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
298
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const operationObject = pathObj[method];
|
|
303
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
const paramObject = operationObject.parameters;
|
|
307
|
-
const requestBody = operationObject.requestBody;
|
|
308
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
309
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
313
|
-
if (Object.keys(responseJson).length === 0) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
// Teams AI project doesn't care about request parameters/body
|
|
317
|
-
if (isTeamsAi) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
let requestBodyParamResult = {
|
|
321
|
-
requiredNum: 0,
|
|
322
|
-
optionalNum: 0,
|
|
323
|
-
isValid: true,
|
|
324
|
-
};
|
|
325
|
-
if (requestJsonBody) {
|
|
326
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
327
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
331
|
-
}
|
|
332
|
-
if (!requestBodyParamResult.isValid) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
336
|
-
if (!paramResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
// Copilot support arbitrary parameters
|
|
340
|
-
if (isCopilot) {
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
344
|
-
if (options.allowMultipleParameters &&
|
|
345
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
346
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
else if (requestBodyParamResult.requiredNum +
|
|
352
|
-
requestBodyParamResult.optionalNum +
|
|
353
|
-
paramResult.requiredNum +
|
|
354
|
-
paramResult.optionalNum ===
|
|
355
|
-
0) {
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
366
|
-
if (authSchemeArray.length === 0) {
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
370
|
-
// Currently we don't support multiple auth in one operation
|
|
371
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
for (const auths of authSchemeArray) {
|
|
375
|
-
if (auths.length === 1) {
|
|
376
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
377
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
378
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
198
|
static isBearerTokenAuth(authScheme) {
|
|
387
199
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
388
200
|
}
|
|
@@ -390,18 +202,18 @@ class Utils {
|
|
|
390
202
|
return authScheme.type === "apiKey";
|
|
391
203
|
}
|
|
392
204
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return false;
|
|
205
|
+
return !!(authScheme.type === "oauth2" &&
|
|
206
|
+
authScheme.flows &&
|
|
207
|
+
authScheme.flows.authorizationCode);
|
|
397
208
|
}
|
|
398
209
|
static getAuthArray(securities, spec) {
|
|
399
210
|
var _a;
|
|
400
211
|
const result = [];
|
|
401
212
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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];
|
|
405
217
|
const authArray = [];
|
|
406
218
|
for (const name in security) {
|
|
407
219
|
const auth = securitySchemas[name];
|
|
@@ -418,17 +230,39 @@ class Utils {
|
|
|
418
230
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
419
231
|
return result;
|
|
420
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
|
+
}
|
|
421
252
|
static updateFirstLetter(str) {
|
|
422
253
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
423
254
|
}
|
|
424
|
-
static getResponseJson(operationObject
|
|
255
|
+
static getResponseJson(operationObject) {
|
|
425
256
|
var _a, _b;
|
|
426
257
|
let json = {};
|
|
258
|
+
let multipleMediaType = false;
|
|
427
259
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
428
260
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
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
|
-
if (
|
|
264
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
265
|
+
multipleMediaType = true;
|
|
432
266
|
json = {};
|
|
433
267
|
}
|
|
434
268
|
else {
|
|
@@ -436,7 +270,7 @@ class Utils {
|
|
|
436
270
|
}
|
|
437
271
|
}
|
|
438
272
|
}
|
|
439
|
-
return json;
|
|
273
|
+
return { json, multipleMediaType };
|
|
440
274
|
}
|
|
441
275
|
static convertPathToCamelCase(path) {
|
|
442
276
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -456,10 +290,10 @@ class Utils {
|
|
|
456
290
|
return undefined;
|
|
457
291
|
}
|
|
458
292
|
}
|
|
459
|
-
static
|
|
293
|
+
static resolveEnv(str) {
|
|
460
294
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
461
|
-
let matches = placeHolderReg.exec(
|
|
462
|
-
let
|
|
295
|
+
let matches = placeHolderReg.exec(str);
|
|
296
|
+
let newStr = str;
|
|
463
297
|
while (matches != null) {
|
|
464
298
|
const envVar = matches[1];
|
|
465
299
|
const envVal = process.env[envVar];
|
|
@@ -467,17 +301,17 @@ class Utils {
|
|
|
467
301
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
468
302
|
}
|
|
469
303
|
else {
|
|
470
|
-
|
|
304
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
471
305
|
}
|
|
472
|
-
matches = placeHolderReg.exec(
|
|
306
|
+
matches = placeHolderReg.exec(str);
|
|
473
307
|
}
|
|
474
|
-
return
|
|
308
|
+
return newStr;
|
|
475
309
|
}
|
|
476
310
|
static checkServerUrl(servers) {
|
|
477
311
|
const errors = [];
|
|
478
312
|
let serverUrl;
|
|
479
313
|
try {
|
|
480
|
-
serverUrl = Utils.
|
|
314
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
481
315
|
}
|
|
482
316
|
catch (err) {
|
|
483
317
|
errors.push({
|
|
@@ -508,6 +342,7 @@ class Utils {
|
|
|
508
342
|
return errors;
|
|
509
343
|
}
|
|
510
344
|
static validateServer(spec, options) {
|
|
345
|
+
var _a;
|
|
511
346
|
const errors = [];
|
|
512
347
|
let hasTopLevelServers = false;
|
|
513
348
|
let hasPathLevelServers = false;
|
|
@@ -528,7 +363,7 @@ class Utils {
|
|
|
528
363
|
}
|
|
529
364
|
for (const method in methods) {
|
|
530
365
|
const operationObject = methods[method];
|
|
531
|
-
if (
|
|
366
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
532
367
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
533
368
|
hasOperationLevelServers = true;
|
|
534
369
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -655,13 +490,7 @@ class Utils {
|
|
|
655
490
|
}
|
|
656
491
|
}
|
|
657
492
|
const operationId = operationItem.operationId;
|
|
658
|
-
const parameters = [];
|
|
659
|
-
if (requiredParams.length !== 0) {
|
|
660
|
-
parameters.push(...requiredParams);
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
parameters.push(optionalParams[0]);
|
|
664
|
-
}
|
|
493
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
665
494
|
const command = {
|
|
666
495
|
context: ["compose"],
|
|
667
496
|
type: "query",
|
|
@@ -670,117 +499,534 @@ class Utils {
|
|
|
670
499
|
parameters: parameters,
|
|
671
500
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
672
501
|
};
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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 "";
|
|
514
|
+
}
|
|
515
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
516
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
517
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
680
518
|
}
|
|
681
|
-
return
|
|
519
|
+
return safeRegistrationIdEnvName;
|
|
682
520
|
}
|
|
683
|
-
static
|
|
684
|
-
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;
|
|
685
540
|
const result = {};
|
|
686
541
|
for (const path in paths) {
|
|
687
542
|
const methods = paths[path];
|
|
688
543
|
for (const method in methods) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
+
};
|
|
692
552
|
}
|
|
693
553
|
}
|
|
694
554
|
}
|
|
555
|
+
this.apiMap = result;
|
|
695
556
|
return result;
|
|
696
557
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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,
|
|
704
565
|
});
|
|
705
566
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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({
|
|
723
587
|
type: ErrorType.NoSupportedApi,
|
|
724
588
|
content: ConstantString.NoSupportedApi,
|
|
589
|
+
data,
|
|
725
590
|
});
|
|
726
591
|
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecOperationId() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
727
597
|
// OperationId missing
|
|
728
598
|
const apisMissingOperationId = [];
|
|
729
599
|
for (const key in apiMap) {
|
|
730
|
-
const
|
|
731
|
-
if (!
|
|
600
|
+
const { operation } = apiMap[key];
|
|
601
|
+
if (!operation.operationId) {
|
|
732
602
|
apisMissingOperationId.push(key);
|
|
733
603
|
}
|
|
734
604
|
}
|
|
735
605
|
if (apisMissingOperationId.length > 0) {
|
|
736
|
-
warnings.push({
|
|
606
|
+
result.warnings.push({
|
|
737
607
|
type: WarningType.OperationIdMissing,
|
|
738
608
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
739
609
|
data: apisMissingOperationId,
|
|
740
610
|
});
|
|
741
611
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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;
|
|
745
620
|
}
|
|
746
|
-
|
|
747
|
-
|
|
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;
|
|
748
626
|
}
|
|
749
|
-
return
|
|
750
|
-
status,
|
|
751
|
-
warnings,
|
|
752
|
-
errors,
|
|
753
|
-
};
|
|
627
|
+
return result;
|
|
754
628
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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;
|
|
761
644
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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);
|
|
765
651
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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));
|
|
769
656
|
}
|
|
770
|
-
return
|
|
657
|
+
return result;
|
|
771
658
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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: [],
|
|
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;
|
|
780
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);
|
|
781
730
|
}
|
|
782
731
|
}
|
|
783
|
-
|
|
732
|
+
else {
|
|
733
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
734
|
+
paramResult.isValid = false;
|
|
735
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return paramResult;
|
|
739
|
+
}
|
|
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;
|
|
796
|
+
}
|
|
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
|
+
}
|
|
805
|
+
}
|
|
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}`);
|
|
1029
|
+
}
|
|
784
1030
|
}
|
|
785
1031
|
}
|
|
786
1032
|
|
|
@@ -803,7 +1049,11 @@ class SpecParser {
|
|
|
803
1049
|
allowBearerTokenAuth: false,
|
|
804
1050
|
allowOauth2: false,
|
|
805
1051
|
allowMethods: ["get", "post"],
|
|
1052
|
+
allowConversationStarters: false,
|
|
1053
|
+
allowResponseSemantics: false,
|
|
1054
|
+
allowConfirmation: false,
|
|
806
1055
|
projectType: ProjectType.SME,
|
|
1056
|
+
isGptPlugin: false,
|
|
807
1057
|
};
|
|
808
1058
|
this.pathOrSpec = pathOrDoc;
|
|
809
1059
|
this.parser = new SwaggerParser();
|
|
@@ -818,11 +1068,7 @@ class SpecParser {
|
|
|
818
1068
|
try {
|
|
819
1069
|
try {
|
|
820
1070
|
await this.loadSpec();
|
|
821
|
-
await this.parser.validate(this.spec
|
|
822
|
-
validate: {
|
|
823
|
-
schema: false,
|
|
824
|
-
},
|
|
825
|
-
});
|
|
1071
|
+
await this.parser.validate(this.spec);
|
|
826
1072
|
}
|
|
827
1073
|
catch (e) {
|
|
828
1074
|
return {
|
|
@@ -831,16 +1077,46 @@ class SpecParser {
|
|
|
831
1077
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
832
1078
|
};
|
|
833
1079
|
}
|
|
1080
|
+
const errors = [];
|
|
1081
|
+
const warnings = [];
|
|
834
1082
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
835
1083
|
return {
|
|
836
1084
|
status: ValidationStatus.Error,
|
|
837
1085
|
warnings: [],
|
|
838
1086
|
errors: [
|
|
839
|
-
{
|
|
1087
|
+
{
|
|
1088
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1089
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1090
|
+
},
|
|
840
1091
|
],
|
|
841
1092
|
};
|
|
842
1093
|
}
|
|
843
|
-
|
|
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
|
+
};
|
|
844
1120
|
}
|
|
845
1121
|
catch (err) {
|
|
846
1122
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -849,17 +1125,20 @@ class SpecParser {
|
|
|
849
1125
|
async listSupportedAPIInfo() {
|
|
850
1126
|
try {
|
|
851
1127
|
await this.loadSpec();
|
|
852
|
-
const apiMap = this.
|
|
1128
|
+
const apiMap = this.getAPIs(this.spec);
|
|
853
1129
|
const apiInfos = [];
|
|
854
1130
|
for (const key in apiMap) {
|
|
855
|
-
const
|
|
1131
|
+
const { operation, isValid } = apiMap[key];
|
|
1132
|
+
if (!isValid) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
856
1135
|
const [method, path] = key.split(" ");
|
|
857
|
-
const operationId =
|
|
1136
|
+
const operationId = operation.operationId;
|
|
858
1137
|
// In Browser environment, this api is by default not support api without operationId
|
|
859
1138
|
if (!operationId) {
|
|
860
1139
|
continue;
|
|
861
1140
|
}
|
|
862
|
-
const
|
|
1141
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
863
1142
|
const apiInfo = {
|
|
864
1143
|
method: method,
|
|
865
1144
|
path: path,
|
|
@@ -868,9 +1147,6 @@ class SpecParser {
|
|
|
868
1147
|
parameters: command.parameters,
|
|
869
1148
|
description: command.description,
|
|
870
1149
|
};
|
|
871
|
-
if (warning) {
|
|
872
|
-
apiInfo.warning = warning;
|
|
873
|
-
}
|
|
874
1150
|
apiInfos.push(apiInfo);
|
|
875
1151
|
}
|
|
876
1152
|
return apiInfos;
|
|
@@ -929,13 +1205,18 @@ class SpecParser {
|
|
|
929
1205
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
930
1206
|
}
|
|
931
1207
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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;
|
|
935
1216
|
}
|
|
936
|
-
const
|
|
937
|
-
this.
|
|
938
|
-
return
|
|
1217
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1218
|
+
this.validator = validator;
|
|
1219
|
+
return validator;
|
|
939
1220
|
}
|
|
940
1221
|
}
|
|
941
1222
|
|
|
@@ -943,7 +1224,7 @@ class SpecParser {
|
|
|
943
1224
|
class AdaptiveCardGenerator {
|
|
944
1225
|
static generateAdaptiveCard(operationItem) {
|
|
945
1226
|
try {
|
|
946
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1227
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
947
1228
|
let cardBody = [];
|
|
948
1229
|
let schema = json.schema;
|
|
949
1230
|
let jsonPath = "$";
|