@microsoft/m365-spec-parser 0.1.1-alpha.b015b287e.0 → 0.1.1-alpha.b389a0e82.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 +570 -348
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1027 -700
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +570 -348
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +998 -667
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +7 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +35 -0
- package/dist/src/manifestUpdater.d.ts +6 -4
- package/dist/src/specParser.browser.d.ts +3 -3
- package/dist/src/specParser.d.ts +3 -1
- package/dist/src/utils.d.ts +4 -24
- package/package.json +3 -3
package/dist/index.esm2017.js
CHANGED
|
@@ -87,7 +87,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
87
87
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
88
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.";
|
|
89
89
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
90
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
90
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
91
91
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
92
92
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
93
93
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -107,9 +107,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
107
107
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
108
108
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
109
109
|
ConstantString.TextBlockType = "TextBlock";
|
|
110
|
+
ConstantString.ImageType = "Image";
|
|
110
111
|
ConstantString.ContainerType = "Container";
|
|
111
|
-
ConstantString.RegistrationIdPostfix =
|
|
112
|
-
|
|
112
|
+
ConstantString.RegistrationIdPostfix = {
|
|
113
|
+
apiKey: "REGISTRATION_ID",
|
|
114
|
+
oauth2: "CONFIGURATION_ID",
|
|
115
|
+
http: "REGISTRATION_ID",
|
|
116
|
+
openIdConnect: "REGISTRATION_ID",
|
|
117
|
+
};
|
|
113
118
|
ConstantString.ResponseCodeFor20X = [
|
|
114
119
|
"200",
|
|
115
120
|
"201",
|
|
@@ -168,9 +173,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
168
173
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
169
174
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
170
175
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
176
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
171
177
|
ConstantString.CommandTitleMaxLens = 32;
|
|
172
178
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
173
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
179
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
180
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
174
181
|
|
|
175
182
|
// Copyright (c) Microsoft Corporation.
|
|
176
183
|
class Utils {
|
|
@@ -185,249 +192,9 @@ class Utils {
|
|
|
185
192
|
}
|
|
186
193
|
return false;
|
|
187
194
|
}
|
|
188
|
-
static checkParameters(paramObject, isCopilot) {
|
|
189
|
-
const paramResult = {
|
|
190
|
-
requiredNum: 0,
|
|
191
|
-
optionalNum: 0,
|
|
192
|
-
isValid: true,
|
|
193
|
-
reason: [],
|
|
194
|
-
};
|
|
195
|
-
if (!paramObject) {
|
|
196
|
-
return paramResult;
|
|
197
|
-
}
|
|
198
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
199
|
-
const param = paramObject[i];
|
|
200
|
-
const schema = param.schema;
|
|
201
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
202
|
-
paramResult.isValid = false;
|
|
203
|
-
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
207
|
-
if (isCopilot) {
|
|
208
|
-
if (isRequiredWithoutDefault) {
|
|
209
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
217
|
-
if (isRequiredWithoutDefault) {
|
|
218
|
-
paramResult.isValid = false;
|
|
219
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
220
|
-
}
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
if (schema.type !== "boolean" &&
|
|
224
|
-
schema.type !== "string" &&
|
|
225
|
-
schema.type !== "number" &&
|
|
226
|
-
schema.type !== "integer") {
|
|
227
|
-
if (isRequiredWithoutDefault) {
|
|
228
|
-
paramResult.isValid = false;
|
|
229
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
230
|
-
}
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (param.in === "query" || param.in === "path") {
|
|
234
|
-
if (isRequiredWithoutDefault) {
|
|
235
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return paramResult;
|
|
243
|
-
}
|
|
244
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
245
|
-
var _a;
|
|
246
|
-
const paramResult = {
|
|
247
|
-
requiredNum: 0,
|
|
248
|
-
optionalNum: 0,
|
|
249
|
-
isValid: true,
|
|
250
|
-
reason: [],
|
|
251
|
-
};
|
|
252
|
-
if (Object.keys(schema).length === 0) {
|
|
253
|
-
return paramResult;
|
|
254
|
-
}
|
|
255
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
256
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
257
|
-
paramResult.isValid = false;
|
|
258
|
-
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
259
|
-
return paramResult;
|
|
260
|
-
}
|
|
261
|
-
if (schema.type === "string" ||
|
|
262
|
-
schema.type === "integer" ||
|
|
263
|
-
schema.type === "boolean" ||
|
|
264
|
-
schema.type === "number") {
|
|
265
|
-
if (isRequiredWithoutDefault) {
|
|
266
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
else if (schema.type === "object") {
|
|
273
|
-
const { properties } = schema;
|
|
274
|
-
for (const property in properties) {
|
|
275
|
-
let isRequired = false;
|
|
276
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
277
|
-
isRequired = true;
|
|
278
|
-
}
|
|
279
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
280
|
-
paramResult.requiredNum += result.requiredNum;
|
|
281
|
-
paramResult.optionalNum += result.optionalNum;
|
|
282
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
283
|
-
paramResult.reason.push(...result.reason);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
288
|
-
paramResult.isValid = false;
|
|
289
|
-
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return paramResult;
|
|
293
|
-
}
|
|
294
195
|
static containMultipleMediaTypes(bodyObject) {
|
|
295
196
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
296
197
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Checks if the given API is supported.
|
|
299
|
-
* @param {string} method - The HTTP method of the API.
|
|
300
|
-
* @param {string} path - The path of the API.
|
|
301
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
302
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
303
|
-
* @description The following APIs are supported:
|
|
304
|
-
* 1. only support Get/Post operation without auth property
|
|
305
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
306
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
307
|
-
* 4. request body + required parameters <= 1
|
|
308
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
309
|
-
* 6. only support request body with “application/json” content type
|
|
310
|
-
*/
|
|
311
|
-
static isSupportedApi(method, path, spec, options) {
|
|
312
|
-
var _a;
|
|
313
|
-
const result = { isValid: true, reason: [] };
|
|
314
|
-
method = method.toLocaleLowerCase();
|
|
315
|
-
if (options.allowMethods && !options.allowMethods.includes(method)) {
|
|
316
|
-
result.isValid = false;
|
|
317
|
-
result.reason.push(ErrorType.MethodNotAllowed);
|
|
318
|
-
return result;
|
|
319
|
-
}
|
|
320
|
-
const pathObj = spec.paths[path];
|
|
321
|
-
if (!pathObj || !pathObj[method]) {
|
|
322
|
-
result.isValid = false;
|
|
323
|
-
result.reason.push(ErrorType.UrlPathNotExist);
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
const securities = pathObj[method].security;
|
|
327
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
328
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
329
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
330
|
-
if (!isTeamsAi) {
|
|
331
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
332
|
-
const authCheckResult = Utils.isSupportedAuth(authArray, options);
|
|
333
|
-
if (!authCheckResult.isValid) {
|
|
334
|
-
result.reason.push(...authCheckResult.reason);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
const operationObject = pathObj[method];
|
|
338
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
339
|
-
result.reason.push(ErrorType.MissingOperationId);
|
|
340
|
-
}
|
|
341
|
-
const rootServer = spec.servers && spec.servers[0];
|
|
342
|
-
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
343
|
-
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
344
|
-
const serverUrl = operationServer || methodServer || rootServer;
|
|
345
|
-
if (!serverUrl) {
|
|
346
|
-
result.reason.push(ErrorType.NoServerInformation);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
350
|
-
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
351
|
-
}
|
|
352
|
-
const paramObject = operationObject.parameters;
|
|
353
|
-
const requestBody = operationObject.requestBody;
|
|
354
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
355
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
356
|
-
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
357
|
-
}
|
|
358
|
-
const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
359
|
-
if (multipleMediaType && !isTeamsAi) {
|
|
360
|
-
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
361
|
-
}
|
|
362
|
-
else if (Object.keys(json).length === 0) {
|
|
363
|
-
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
364
|
-
}
|
|
365
|
-
// Teams AI project doesn't care about request parameters/body
|
|
366
|
-
if (!isTeamsAi) {
|
|
367
|
-
let requestBodyParamResult = {
|
|
368
|
-
requiredNum: 0,
|
|
369
|
-
optionalNum: 0,
|
|
370
|
-
isValid: true,
|
|
371
|
-
reason: [],
|
|
372
|
-
};
|
|
373
|
-
if (requestJsonBody) {
|
|
374
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
375
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
376
|
-
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
377
|
-
}
|
|
378
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
379
|
-
if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
|
|
380
|
-
result.reason.push(...requestBodyParamResult.reason);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
384
|
-
if (!paramResult.isValid && paramResult.reason) {
|
|
385
|
-
result.reason.push(...paramResult.reason);
|
|
386
|
-
}
|
|
387
|
-
// Copilot support arbitrary parameters
|
|
388
|
-
if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
|
|
389
|
-
const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
|
|
390
|
-
const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
|
|
391
|
-
if (totalRequiredParams > 1) {
|
|
392
|
-
if (!options.allowMultipleParameters ||
|
|
393
|
-
totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
|
|
394
|
-
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
else if (totalParams === 0) {
|
|
398
|
-
result.reason.push(ErrorType.NoParameter);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (result.reason.length > 0) {
|
|
403
|
-
result.isValid = false;
|
|
404
|
-
}
|
|
405
|
-
return result;
|
|
406
|
-
}
|
|
407
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
408
|
-
if (authSchemeArray.length === 0) {
|
|
409
|
-
return { isValid: true, reason: [] };
|
|
410
|
-
}
|
|
411
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
412
|
-
// Currently we don't support multiple auth in one operation
|
|
413
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
414
|
-
return {
|
|
415
|
-
isValid: false,
|
|
416
|
-
reason: [ErrorType.MultipleAuthNotSupported],
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
for (const auths of authSchemeArray) {
|
|
420
|
-
if (auths.length === 1) {
|
|
421
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
422
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
423
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
424
|
-
return { isValid: true, reason: [] };
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
430
|
-
}
|
|
431
198
|
static isBearerTokenAuth(authScheme) {
|
|
432
199
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
433
200
|
}
|
|
@@ -435,18 +202,18 @@ class Utils {
|
|
|
435
202
|
return authScheme.type === "apiKey";
|
|
436
203
|
}
|
|
437
204
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return false;
|
|
205
|
+
return !!(authScheme.type === "oauth2" &&
|
|
206
|
+
authScheme.flows &&
|
|
207
|
+
authScheme.flows.authorizationCode);
|
|
442
208
|
}
|
|
443
209
|
static getAuthArray(securities, spec) {
|
|
444
210
|
var _a;
|
|
445
211
|
const result = [];
|
|
446
212
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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];
|
|
450
217
|
const authArray = [];
|
|
451
218
|
for (const name in security) {
|
|
452
219
|
const auth = securitySchemas[name];
|
|
@@ -463,10 +230,29 @@ class Utils {
|
|
|
463
230
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
464
231
|
return result;
|
|
465
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
|
+
}
|
|
466
252
|
static updateFirstLetter(str) {
|
|
467
253
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
468
254
|
}
|
|
469
|
-
static getResponseJson(operationObject
|
|
255
|
+
static getResponseJson(operationObject) {
|
|
470
256
|
var _a, _b;
|
|
471
257
|
let json = {};
|
|
472
258
|
let multipleMediaType = false;
|
|
@@ -477,9 +263,6 @@ class Utils {
|
|
|
477
263
|
json = responseObject.content["application/json"];
|
|
478
264
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
479
265
|
multipleMediaType = true;
|
|
480
|
-
if (isTeamsAiProject) {
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
266
|
json = {};
|
|
484
267
|
}
|
|
485
268
|
else {
|
|
@@ -718,16 +501,49 @@ class Utils {
|
|
|
718
501
|
};
|
|
719
502
|
return command;
|
|
720
503
|
}
|
|
721
|
-
static
|
|
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;
|
|
518
|
+
}
|
|
519
|
+
return safeRegistrationIdEnvName;
|
|
520
|
+
}
|
|
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() {
|
|
722
535
|
var _a;
|
|
723
|
-
|
|
536
|
+
if (this.apiMap) {
|
|
537
|
+
return this.apiMap;
|
|
538
|
+
}
|
|
539
|
+
const paths = this.spec.paths;
|
|
724
540
|
const result = {};
|
|
725
541
|
for (const path in paths) {
|
|
726
542
|
const methods = paths[path];
|
|
727
543
|
for (const method in methods) {
|
|
728
544
|
const operationObject = methods[method];
|
|
729
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
730
|
-
const validateResult =
|
|
545
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
546
|
+
const validateResult = this.validateAPI(method, path);
|
|
731
547
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
732
548
|
operation: operationObject,
|
|
733
549
|
isValid: validateResult.isValid,
|
|
@@ -736,38 +552,48 @@ class Utils {
|
|
|
736
552
|
}
|
|
737
553
|
}
|
|
738
554
|
}
|
|
555
|
+
this.apiMap = result;
|
|
739
556
|
return result;
|
|
740
557
|
}
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
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,
|
|
749
565
|
});
|
|
750
566
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
// No supported API
|
|
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();
|
|
764
578
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
765
579
|
if (validAPIs.length === 0) {
|
|
766
|
-
|
|
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({
|
|
767
587
|
type: ErrorType.NoSupportedApi,
|
|
768
588
|
content: ConstantString.NoSupportedApi,
|
|
589
|
+
data,
|
|
769
590
|
});
|
|
770
591
|
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecOperationId() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
771
597
|
// OperationId missing
|
|
772
598
|
const apisMissingOperationId = [];
|
|
773
599
|
for (const key in apiMap) {
|
|
@@ -777,54 +603,430 @@ class Utils {
|
|
|
777
603
|
}
|
|
778
604
|
}
|
|
779
605
|
if (apisMissingOperationId.length > 0) {
|
|
780
|
-
warnings.push({
|
|
606
|
+
result.warnings.push({
|
|
781
607
|
type: WarningType.OperationIdMissing,
|
|
782
608
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
783
609
|
data: apisMissingOperationId,
|
|
784
610
|
});
|
|
785
611
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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;
|
|
789
620
|
}
|
|
790
|
-
|
|
791
|
-
|
|
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;
|
|
792
626
|
}
|
|
793
|
-
return
|
|
794
|
-
status,
|
|
795
|
-
warnings,
|
|
796
|
-
errors,
|
|
797
|
-
};
|
|
627
|
+
return result;
|
|
798
628
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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;
|
|
805
644
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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);
|
|
809
651
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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));
|
|
813
656
|
}
|
|
814
|
-
return
|
|
657
|
+
return result;
|
|
815
658
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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;
|
|
824
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);
|
|
825
730
|
}
|
|
826
731
|
}
|
|
827
|
-
|
|
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
|
+
}
|
|
828
1030
|
}
|
|
829
1031
|
}
|
|
830
1032
|
|
|
@@ -847,7 +1049,11 @@ class SpecParser {
|
|
|
847
1049
|
allowBearerTokenAuth: false,
|
|
848
1050
|
allowOauth2: false,
|
|
849
1051
|
allowMethods: ["get", "post"],
|
|
1052
|
+
allowConversationStarters: false,
|
|
1053
|
+
allowResponseSemantics: false,
|
|
1054
|
+
allowConfirmation: false,
|
|
850
1055
|
projectType: ProjectType.SME,
|
|
1056
|
+
isGptPlugin: false,
|
|
851
1057
|
};
|
|
852
1058
|
this.pathOrSpec = pathOrDoc;
|
|
853
1059
|
this.parser = new SwaggerParser();
|
|
@@ -862,11 +1068,7 @@ class SpecParser {
|
|
|
862
1068
|
try {
|
|
863
1069
|
try {
|
|
864
1070
|
await this.loadSpec();
|
|
865
|
-
await this.parser.validate(this.spec
|
|
866
|
-
validate: {
|
|
867
|
-
schema: false,
|
|
868
|
-
},
|
|
869
|
-
});
|
|
1071
|
+
await this.parser.validate(this.spec);
|
|
870
1072
|
}
|
|
871
1073
|
catch (e) {
|
|
872
1074
|
return {
|
|
@@ -875,16 +1077,46 @@ class SpecParser {
|
|
|
875
1077
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
876
1078
|
};
|
|
877
1079
|
}
|
|
1080
|
+
const errors = [];
|
|
1081
|
+
const warnings = [];
|
|
878
1082
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
879
1083
|
return {
|
|
880
1084
|
status: ValidationStatus.Error,
|
|
881
1085
|
warnings: [],
|
|
882
1086
|
errors: [
|
|
883
|
-
{
|
|
1087
|
+
{
|
|
1088
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1089
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1090
|
+
},
|
|
884
1091
|
],
|
|
885
1092
|
};
|
|
886
1093
|
}
|
|
887
|
-
|
|
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
|
+
};
|
|
888
1120
|
}
|
|
889
1121
|
catch (err) {
|
|
890
1122
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -893,17 +1125,20 @@ class SpecParser {
|
|
|
893
1125
|
async listSupportedAPIInfo() {
|
|
894
1126
|
try {
|
|
895
1127
|
await this.loadSpec();
|
|
896
|
-
const apiMap = this.
|
|
1128
|
+
const apiMap = this.getAPIs(this.spec);
|
|
897
1129
|
const apiInfos = [];
|
|
898
1130
|
for (const key in apiMap) {
|
|
899
|
-
const
|
|
1131
|
+
const { operation, isValid } = apiMap[key];
|
|
1132
|
+
if (!isValid) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
900
1135
|
const [method, path] = key.split(" ");
|
|
901
|
-
const operationId =
|
|
1136
|
+
const operationId = operation.operationId;
|
|
902
1137
|
// In Browser environment, this api is by default not support api without operationId
|
|
903
1138
|
if (!operationId) {
|
|
904
1139
|
continue;
|
|
905
1140
|
}
|
|
906
|
-
const command = Utils.parseApiInfo(
|
|
1141
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
907
1142
|
const apiInfo = {
|
|
908
1143
|
method: method,
|
|
909
1144
|
path: path,
|
|
@@ -970,31 +1205,18 @@ class SpecParser {
|
|
|
970
1205
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
971
1206
|
}
|
|
972
1207
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const result = this.listSupportedAPIs(spec, this.options);
|
|
978
|
-
this.apiMap = result;
|
|
979
|
-
return result;
|
|
1208
|
+
getAPIs(spec) {
|
|
1209
|
+
const validator = this.getValidator(spec);
|
|
1210
|
+
const apiMap = validator.listAPIs();
|
|
1211
|
+
return apiMap;
|
|
980
1212
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const result = {};
|
|
985
|
-
for (const path in paths) {
|
|
986
|
-
const methods = paths[path];
|
|
987
|
-
for (const method in methods) {
|
|
988
|
-
const operationObject = methods[method];
|
|
989
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
990
|
-
const validateResult = Utils.isSupportedApi(method, path, spec, options);
|
|
991
|
-
if (validateResult.isValid) {
|
|
992
|
-
result[`${method.toUpperCase()} ${path}`] = operationObject;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
1213
|
+
getValidator(spec) {
|
|
1214
|
+
if (this.validator) {
|
|
1215
|
+
return this.validator;
|
|
996
1216
|
}
|
|
997
|
-
|
|
1217
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1218
|
+
this.validator = validator;
|
|
1219
|
+
return validator;
|
|
998
1220
|
}
|
|
999
1221
|
}
|
|
1000
1222
|
|