@microsoft/m365-spec-parser 0.1.1-alpha.d1a09e202.0 → 0.1.1-alpha.d292dc740.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 +620 -324
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1060 -621
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +620 -324
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1072 -629
- 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 +74 -15
- package/dist/src/manifestUpdater.d.ts +7 -4
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +5 -3
- package/dist/src/utils.d.ts +12 -30
- 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);
|
|
@@ -571,6 +406,7 @@ class Utils {
|
|
|
571
406
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
572
407
|
}
|
|
573
408
|
if (isRequired && schema.default === undefined) {
|
|
409
|
+
parameter.isRequired = true;
|
|
574
410
|
requiredParams.push(parameter);
|
|
575
411
|
}
|
|
576
412
|
else {
|
|
@@ -634,6 +470,7 @@ class Utils {
|
|
|
634
470
|
}
|
|
635
471
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
636
472
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
473
|
+
parameter.isRequired = true;
|
|
637
474
|
requiredParams.push(parameter);
|
|
638
475
|
}
|
|
639
476
|
else {
|
|
@@ -653,13 +490,7 @@ class Utils {
|
|
|
653
490
|
}
|
|
654
491
|
}
|
|
655
492
|
const operationId = operationItem.operationId;
|
|
656
|
-
const parameters = [];
|
|
657
|
-
if (requiredParams.length !== 0) {
|
|
658
|
-
parameters.push(...requiredParams);
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
parameters.push(optionalParams[0]);
|
|
662
|
-
}
|
|
493
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
663
494
|
const command = {
|
|
664
495
|
context: ["compose"],
|
|
665
496
|
type: "query",
|
|
@@ -668,104 +499,534 @@ class Utils {
|
|
|
668
499
|
parameters: parameters,
|
|
669
500
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
670
501
|
};
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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;
|
|
678
518
|
}
|
|
679
|
-
return
|
|
519
|
+
return safeRegistrationIdEnvName;
|
|
680
520
|
}
|
|
681
|
-
static
|
|
682
|
-
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;
|
|
683
540
|
const result = {};
|
|
684
541
|
for (const path in paths) {
|
|
685
542
|
const methods = paths[path];
|
|
686
543
|
for (const method in methods) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
+
};
|
|
690
552
|
}
|
|
691
553
|
}
|
|
692
554
|
}
|
|
555
|
+
this.apiMap = result;
|
|
693
556
|
return result;
|
|
694
557
|
}
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
// Server validation
|
|
705
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
706
|
-
errors.push(...serverErrors);
|
|
707
|
-
// Remote reference not supported
|
|
708
|
-
const refPaths = parser.$refs.paths();
|
|
709
|
-
// refPaths [0] is the current spec file path
|
|
710
|
-
if (refPaths.length > 1) {
|
|
711
|
-
errors.push({
|
|
712
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
713
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
714
|
-
data: refPaths,
|
|
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,
|
|
715
565
|
});
|
|
716
566
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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({
|
|
721
587
|
type: ErrorType.NoSupportedApi,
|
|
722
588
|
content: ConstantString.NoSupportedApi,
|
|
589
|
+
data,
|
|
723
590
|
});
|
|
724
591
|
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecOperationId() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
725
597
|
// OperationId missing
|
|
726
598
|
const apisMissingOperationId = [];
|
|
727
599
|
for (const key in apiMap) {
|
|
728
|
-
const
|
|
729
|
-
if (!
|
|
600
|
+
const { operation } = apiMap[key];
|
|
601
|
+
if (!operation.operationId) {
|
|
730
602
|
apisMissingOperationId.push(key);
|
|
731
603
|
}
|
|
732
604
|
}
|
|
733
605
|
if (apisMissingOperationId.length > 0) {
|
|
734
|
-
warnings.push({
|
|
606
|
+
result.warnings.push({
|
|
735
607
|
type: WarningType.OperationIdMissing,
|
|
736
608
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
737
609
|
data: apisMissingOperationId,
|
|
738
610
|
});
|
|
739
611
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
validateMethodAndPath(method, path) {
|
|
615
|
+
const result = { isValid: true, reason: [] };
|
|
616
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
617
|
+
result.isValid = false;
|
|
618
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
const pathObj = this.spec.paths[path];
|
|
622
|
+
if (!pathObj || !pathObj[method]) {
|
|
623
|
+
result.isValid = false;
|
|
624
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
validateResponse(method, path) {
|
|
630
|
+
const result = { isValid: true, reason: [] };
|
|
631
|
+
const operationObject = this.spec.paths[path][method];
|
|
632
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
633
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
634
|
+
// only support response body only contains “application/json” content type
|
|
635
|
+
if (multipleMediaType) {
|
|
636
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
637
|
+
}
|
|
638
|
+
else if (Object.keys(json).length === 0) {
|
|
639
|
+
// response body should not be empty
|
|
640
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
validateServer(method, path) {
|
|
646
|
+
const result = { isValid: true, reason: [] };
|
|
647
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
648
|
+
if (!serverObj) {
|
|
649
|
+
// should contain server URL
|
|
650
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
// server url should be absolute url with https protocol
|
|
654
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
655
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
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: [] };
|
|
743
666
|
}
|
|
744
|
-
|
|
745
|
-
|
|
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
|
+
}
|
|
746
686
|
}
|
|
747
|
-
return {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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: [],
|
|
751
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;
|
|
752
739
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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;
|
|
759
796
|
}
|
|
760
|
-
|
|
761
|
-
if (
|
|
762
|
-
|
|
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
|
+
}
|
|
763
805
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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}`);
|
|
767
1029
|
}
|
|
768
|
-
return safeRegistrationIdEnvName;
|
|
769
1030
|
}
|
|
770
1031
|
}
|
|
771
1032
|
|
|
@@ -788,7 +1049,11 @@ class SpecParser {
|
|
|
788
1049
|
allowBearerTokenAuth: false,
|
|
789
1050
|
allowOauth2: false,
|
|
790
1051
|
allowMethods: ["get", "post"],
|
|
1052
|
+
allowConversationStarters: false,
|
|
1053
|
+
allowResponseSemantics: false,
|
|
1054
|
+
allowConfirmation: false,
|
|
791
1055
|
projectType: ProjectType.SME,
|
|
1056
|
+
isGptPlugin: false,
|
|
792
1057
|
};
|
|
793
1058
|
this.pathOrSpec = pathOrDoc;
|
|
794
1059
|
this.parser = new SwaggerParser();
|
|
@@ -803,11 +1068,7 @@ class SpecParser {
|
|
|
803
1068
|
try {
|
|
804
1069
|
try {
|
|
805
1070
|
await this.loadSpec();
|
|
806
|
-
await this.parser.validate(this.spec
|
|
807
|
-
validate: {
|
|
808
|
-
schema: false,
|
|
809
|
-
},
|
|
810
|
-
});
|
|
1071
|
+
await this.parser.validate(this.spec);
|
|
811
1072
|
}
|
|
812
1073
|
catch (e) {
|
|
813
1074
|
return {
|
|
@@ -816,16 +1077,46 @@ class SpecParser {
|
|
|
816
1077
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
817
1078
|
};
|
|
818
1079
|
}
|
|
1080
|
+
const errors = [];
|
|
1081
|
+
const warnings = [];
|
|
819
1082
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
820
1083
|
return {
|
|
821
1084
|
status: ValidationStatus.Error,
|
|
822
1085
|
warnings: [],
|
|
823
1086
|
errors: [
|
|
824
|
-
{
|
|
1087
|
+
{
|
|
1088
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1089
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1090
|
+
},
|
|
825
1091
|
],
|
|
826
1092
|
};
|
|
827
1093
|
}
|
|
828
|
-
|
|
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
|
+
};
|
|
829
1120
|
}
|
|
830
1121
|
catch (err) {
|
|
831
1122
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -834,17 +1125,20 @@ class SpecParser {
|
|
|
834
1125
|
async listSupportedAPIInfo() {
|
|
835
1126
|
try {
|
|
836
1127
|
await this.loadSpec();
|
|
837
|
-
const apiMap = this.
|
|
1128
|
+
const apiMap = this.getAPIs(this.spec);
|
|
838
1129
|
const apiInfos = [];
|
|
839
1130
|
for (const key in apiMap) {
|
|
840
|
-
const
|
|
1131
|
+
const { operation, isValid } = apiMap[key];
|
|
1132
|
+
if (!isValid) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
841
1135
|
const [method, path] = key.split(" ");
|
|
842
|
-
const operationId =
|
|
1136
|
+
const operationId = operation.operationId;
|
|
843
1137
|
// In Browser environment, this api is by default not support api without operationId
|
|
844
1138
|
if (!operationId) {
|
|
845
1139
|
continue;
|
|
846
1140
|
}
|
|
847
|
-
const
|
|
1141
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
848
1142
|
const apiInfo = {
|
|
849
1143
|
method: method,
|
|
850
1144
|
path: path,
|
|
@@ -853,9 +1147,6 @@ class SpecParser {
|
|
|
853
1147
|
parameters: command.parameters,
|
|
854
1148
|
description: command.description,
|
|
855
1149
|
};
|
|
856
|
-
if (warning) {
|
|
857
|
-
apiInfo.warning = warning;
|
|
858
|
-
}
|
|
859
1150
|
apiInfos.push(apiInfo);
|
|
860
1151
|
}
|
|
861
1152
|
return apiInfos;
|
|
@@ -914,13 +1205,18 @@ class SpecParser {
|
|
|
914
1205
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
915
1206
|
}
|
|
916
1207
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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;
|
|
920
1216
|
}
|
|
921
|
-
const
|
|
922
|
-
this.
|
|
923
|
-
return
|
|
1217
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1218
|
+
this.validator = validator;
|
|
1219
|
+
return validator;
|
|
924
1220
|
}
|
|
925
1221
|
}
|
|
926
1222
|
|
|
@@ -928,7 +1224,7 @@ class SpecParser {
|
|
|
928
1224
|
class AdaptiveCardGenerator {
|
|
929
1225
|
static generateAdaptiveCard(operationItem) {
|
|
930
1226
|
try {
|
|
931
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1227
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
932
1228
|
let cardBody = [];
|
|
933
1229
|
let schema = json.schema;
|
|
934
1230
|
let jsonPath = "$";
|