@microsoft/m365-spec-parser 0.1.1-alpha.7fe3da414.0 → 0.1.1-alpha.87f45d762.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 +537 -357
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +611 -391
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +537 -357
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +767 -545
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +14 -0
- package/dist/src/manifestUpdater.d.ts +1 -1
- package/dist/src/specParser.browser.d.ts +3 -2
- package/dist/src/specParser.d.ts +2 -0
- package/dist/src/utils.d.ts +4 -25
- package/package.json +3 -3
package/dist/index.esm2017.mjs
CHANGED
|
@@ -166,7 +166,8 @@ ConstantString.CommandDescriptionMaxLens = 128;
|
|
|
166
166
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
167
167
|
ConstantString.CommandTitleMaxLens = 32;
|
|
168
168
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
169
|
-
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
169
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
170
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
170
171
|
|
|
171
172
|
// Copyright (c) Microsoft Corporation.
|
|
172
173
|
class SpecParserError extends Error {
|
|
@@ -189,249 +190,9 @@ class Utils {
|
|
|
189
190
|
}
|
|
190
191
|
return false;
|
|
191
192
|
}
|
|
192
|
-
static checkParameters(paramObject, isCopilot) {
|
|
193
|
-
const paramResult = {
|
|
194
|
-
requiredNum: 0,
|
|
195
|
-
optionalNum: 0,
|
|
196
|
-
isValid: true,
|
|
197
|
-
reason: [],
|
|
198
|
-
};
|
|
199
|
-
if (!paramObject) {
|
|
200
|
-
return paramResult;
|
|
201
|
-
}
|
|
202
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
203
|
-
const param = paramObject[i];
|
|
204
|
-
const schema = param.schema;
|
|
205
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
206
|
-
paramResult.isValid = false;
|
|
207
|
-
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
211
|
-
if (isCopilot) {
|
|
212
|
-
if (isRequiredWithoutDefault) {
|
|
213
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
217
|
-
}
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
221
|
-
if (isRequiredWithoutDefault) {
|
|
222
|
-
paramResult.isValid = false;
|
|
223
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
224
|
-
}
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (schema.type !== "boolean" &&
|
|
228
|
-
schema.type !== "string" &&
|
|
229
|
-
schema.type !== "number" &&
|
|
230
|
-
schema.type !== "integer") {
|
|
231
|
-
if (isRequiredWithoutDefault) {
|
|
232
|
-
paramResult.isValid = false;
|
|
233
|
-
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
234
|
-
}
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (param.in === "query" || param.in === "path") {
|
|
238
|
-
if (isRequiredWithoutDefault) {
|
|
239
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return paramResult;
|
|
247
|
-
}
|
|
248
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
249
|
-
var _a;
|
|
250
|
-
const paramResult = {
|
|
251
|
-
requiredNum: 0,
|
|
252
|
-
optionalNum: 0,
|
|
253
|
-
isValid: true,
|
|
254
|
-
reason: [],
|
|
255
|
-
};
|
|
256
|
-
if (Object.keys(schema).length === 0) {
|
|
257
|
-
return paramResult;
|
|
258
|
-
}
|
|
259
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
260
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
261
|
-
paramResult.isValid = false;
|
|
262
|
-
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
263
|
-
return paramResult;
|
|
264
|
-
}
|
|
265
|
-
if (schema.type === "string" ||
|
|
266
|
-
schema.type === "integer" ||
|
|
267
|
-
schema.type === "boolean" ||
|
|
268
|
-
schema.type === "number") {
|
|
269
|
-
if (isRequiredWithoutDefault) {
|
|
270
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
else if (schema.type === "object") {
|
|
277
|
-
const { properties } = schema;
|
|
278
|
-
for (const property in properties) {
|
|
279
|
-
let isRequired = false;
|
|
280
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
281
|
-
isRequired = true;
|
|
282
|
-
}
|
|
283
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
284
|
-
paramResult.requiredNum += result.requiredNum;
|
|
285
|
-
paramResult.optionalNum += result.optionalNum;
|
|
286
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
287
|
-
paramResult.reason.push(...result.reason);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
292
|
-
paramResult.isValid = false;
|
|
293
|
-
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return paramResult;
|
|
297
|
-
}
|
|
298
193
|
static containMultipleMediaTypes(bodyObject) {
|
|
299
194
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
300
195
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Checks if the given API is supported.
|
|
303
|
-
* @param {string} method - The HTTP method of the API.
|
|
304
|
-
* @param {string} path - The path of the API.
|
|
305
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
306
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
307
|
-
* @description The following APIs are supported:
|
|
308
|
-
* 1. only support Get/Post operation without auth property
|
|
309
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
310
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
311
|
-
* 4. request body + required parameters <= 1
|
|
312
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
313
|
-
* 6. only support request body with “application/json” content type
|
|
314
|
-
*/
|
|
315
|
-
static isSupportedApi(method, path, spec, options) {
|
|
316
|
-
var _a;
|
|
317
|
-
const result = { isValid: true, reason: [] };
|
|
318
|
-
method = method.toLocaleLowerCase();
|
|
319
|
-
if (options.allowMethods && !options.allowMethods.includes(method)) {
|
|
320
|
-
result.isValid = false;
|
|
321
|
-
result.reason.push(ErrorType.MethodNotAllowed);
|
|
322
|
-
return result;
|
|
323
|
-
}
|
|
324
|
-
const pathObj = spec.paths[path];
|
|
325
|
-
if (!pathObj || !pathObj[method]) {
|
|
326
|
-
result.isValid = false;
|
|
327
|
-
result.reason.push(ErrorType.UrlPathNotExist);
|
|
328
|
-
return result;
|
|
329
|
-
}
|
|
330
|
-
const securities = pathObj[method].security;
|
|
331
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
332
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
333
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
334
|
-
if (!isTeamsAi) {
|
|
335
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
336
|
-
const authCheckResult = Utils.isSupportedAuth(authArray, options);
|
|
337
|
-
if (!authCheckResult.isValid) {
|
|
338
|
-
result.reason.push(...authCheckResult.reason);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
const operationObject = pathObj[method];
|
|
342
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
343
|
-
result.reason.push(ErrorType.MissingOperationId);
|
|
344
|
-
}
|
|
345
|
-
const rootServer = spec.servers && spec.servers[0];
|
|
346
|
-
const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
|
|
347
|
-
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
348
|
-
const serverUrl = operationServer || methodServer || rootServer;
|
|
349
|
-
if (!serverUrl) {
|
|
350
|
-
result.reason.push(ErrorType.NoServerInformation);
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
const serverValidateResult = Utils.checkServerUrl([serverUrl]);
|
|
354
|
-
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
355
|
-
}
|
|
356
|
-
const paramObject = operationObject.parameters;
|
|
357
|
-
const requestBody = operationObject.requestBody;
|
|
358
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
359
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
360
|
-
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
361
|
-
}
|
|
362
|
-
const { json, multipleMediaType } = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
363
|
-
if (multipleMediaType && !isTeamsAi) {
|
|
364
|
-
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
365
|
-
}
|
|
366
|
-
else if (Object.keys(json).length === 0) {
|
|
367
|
-
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
368
|
-
}
|
|
369
|
-
// Teams AI project doesn't care about request parameters/body
|
|
370
|
-
if (!isTeamsAi) {
|
|
371
|
-
let requestBodyParamResult = {
|
|
372
|
-
requiredNum: 0,
|
|
373
|
-
optionalNum: 0,
|
|
374
|
-
isValid: true,
|
|
375
|
-
reason: [],
|
|
376
|
-
};
|
|
377
|
-
if (requestJsonBody) {
|
|
378
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
379
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
380
|
-
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
381
|
-
}
|
|
382
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
383
|
-
if (!requestBodyParamResult.isValid && requestBodyParamResult.reason) {
|
|
384
|
-
result.reason.push(...requestBodyParamResult.reason);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
388
|
-
if (!paramResult.isValid && paramResult.reason) {
|
|
389
|
-
result.reason.push(...paramResult.reason);
|
|
390
|
-
}
|
|
391
|
-
// Copilot support arbitrary parameters
|
|
392
|
-
if (!isCopilot && paramResult.isValid && requestBodyParamResult.isValid) {
|
|
393
|
-
const totalRequiredParams = requestBodyParamResult.requiredNum + paramResult.requiredNum;
|
|
394
|
-
const totalParams = totalRequiredParams + requestBodyParamResult.optionalNum + paramResult.optionalNum;
|
|
395
|
-
if (totalRequiredParams > 1) {
|
|
396
|
-
if (!options.allowMultipleParameters ||
|
|
397
|
-
totalRequiredParams > ConstantString.SMERequiredParamsMaxNum) {
|
|
398
|
-
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
else if (totalParams === 0) {
|
|
402
|
-
result.reason.push(ErrorType.NoParameter);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (result.reason.length > 0) {
|
|
407
|
-
result.isValid = false;
|
|
408
|
-
}
|
|
409
|
-
return result;
|
|
410
|
-
}
|
|
411
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
412
|
-
if (authSchemeArray.length === 0) {
|
|
413
|
-
return { isValid: true, reason: [] };
|
|
414
|
-
}
|
|
415
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
416
|
-
// Currently we don't support multiple auth in one operation
|
|
417
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
418
|
-
return {
|
|
419
|
-
isValid: false,
|
|
420
|
-
reason: [ErrorType.MultipleAuthNotSupported],
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
for (const auths of authSchemeArray) {
|
|
424
|
-
if (auths.length === 1) {
|
|
425
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
426
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
427
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
428
|
-
return { isValid: true, reason: [] };
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
434
|
-
}
|
|
435
196
|
static isBearerTokenAuth(authScheme) {
|
|
436
197
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
437
198
|
}
|
|
@@ -439,10 +200,9 @@ class Utils {
|
|
|
439
200
|
return authScheme.type === "apiKey";
|
|
440
201
|
}
|
|
441
202
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
return false;
|
|
203
|
+
return !!(authScheme.type === "oauth2" &&
|
|
204
|
+
authScheme.flows &&
|
|
205
|
+
authScheme.flows.authorizationCode);
|
|
446
206
|
}
|
|
447
207
|
static getAuthArray(securities, spec) {
|
|
448
208
|
var _a;
|
|
@@ -470,7 +230,7 @@ class Utils {
|
|
|
470
230
|
static updateFirstLetter(str) {
|
|
471
231
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
472
232
|
}
|
|
473
|
-
static getResponseJson(operationObject
|
|
233
|
+
static getResponseJson(operationObject) {
|
|
474
234
|
var _a, _b;
|
|
475
235
|
let json = {};
|
|
476
236
|
let multipleMediaType = false;
|
|
@@ -481,9 +241,6 @@ class Utils {
|
|
|
481
241
|
json = responseObject.content["application/json"];
|
|
482
242
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
483
243
|
multipleMediaType = true;
|
|
484
|
-
if (isTeamsAiProject) {
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
244
|
json = {};
|
|
488
245
|
}
|
|
489
246
|
else {
|
|
@@ -711,13 +468,7 @@ class Utils {
|
|
|
711
468
|
}
|
|
712
469
|
}
|
|
713
470
|
const operationId = operationItem.operationId;
|
|
714
|
-
const parameters = [];
|
|
715
|
-
if (requiredParams.length !== 0) {
|
|
716
|
-
parameters.push(...requiredParams);
|
|
717
|
-
}
|
|
718
|
-
else {
|
|
719
|
-
parameters.push(optionalParams[0]);
|
|
720
|
-
}
|
|
471
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
721
472
|
const command = {
|
|
722
473
|
context: ["compose"],
|
|
723
474
|
type: "query",
|
|
@@ -726,26 +477,51 @@ class Utils {
|
|
|
726
477
|
parameters: parameters,
|
|
727
478
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
728
479
|
};
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
480
|
+
return command;
|
|
481
|
+
}
|
|
482
|
+
static format(str, ...args) {
|
|
483
|
+
let index = 0;
|
|
484
|
+
return str.replace(/%s/g, () => {
|
|
485
|
+
const arg = args[index++];
|
|
486
|
+
return arg !== undefined ? arg : "";
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
490
|
+
if (!authName) {
|
|
491
|
+
return "";
|
|
492
|
+
}
|
|
493
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
494
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
495
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
736
496
|
}
|
|
737
|
-
return
|
|
497
|
+
return safeRegistrationIdEnvName;
|
|
498
|
+
}
|
|
499
|
+
static getServerObject(spec, method, path) {
|
|
500
|
+
const pathObj = spec.paths[path];
|
|
501
|
+
const operationObject = pathObj[method];
|
|
502
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
503
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
504
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
505
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
506
|
+
return serverUrl;
|
|
738
507
|
}
|
|
739
|
-
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Copyright (c) Microsoft Corporation.
|
|
511
|
+
class Validator {
|
|
512
|
+
listAPIs() {
|
|
740
513
|
var _a;
|
|
741
|
-
|
|
514
|
+
if (this.apiMap) {
|
|
515
|
+
return this.apiMap;
|
|
516
|
+
}
|
|
517
|
+
const paths = this.spec.paths;
|
|
742
518
|
const result = {};
|
|
743
519
|
for (const path in paths) {
|
|
744
520
|
const methods = paths[path];
|
|
745
521
|
for (const method in methods) {
|
|
746
522
|
const operationObject = methods[method];
|
|
747
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
748
|
-
const validateResult =
|
|
523
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
524
|
+
const validateResult = this.validateAPI(method, path);
|
|
749
525
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
750
526
|
operation: operationObject,
|
|
751
527
|
isValid: validateResult.isValid,
|
|
@@ -754,38 +530,48 @@ class Utils {
|
|
|
754
530
|
}
|
|
755
531
|
}
|
|
756
532
|
}
|
|
533
|
+
this.apiMap = result;
|
|
757
534
|
return result;
|
|
758
535
|
}
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
770
|
-
errors.push(...serverErrors);
|
|
771
|
-
// Remote reference not supported
|
|
772
|
-
const refPaths = parser.$refs.paths();
|
|
773
|
-
// refPaths [0] is the current spec file path
|
|
774
|
-
if (refPaths.length > 1) {
|
|
775
|
-
errors.push({
|
|
776
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
777
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
778
|
-
data: refPaths,
|
|
536
|
+
validateSpecVersion() {
|
|
537
|
+
const result = { errors: [], warnings: [] };
|
|
538
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
539
|
+
result.errors.push({
|
|
540
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
541
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
542
|
+
data: this.spec.openapi,
|
|
779
543
|
});
|
|
780
544
|
}
|
|
781
|
-
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
validateSpecServer() {
|
|
548
|
+
const result = { errors: [], warnings: [] };
|
|
549
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
550
|
+
result.errors.push(...serverErrors);
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
validateSpecNoSupportAPI() {
|
|
554
|
+
const result = { errors: [], warnings: [] };
|
|
555
|
+
const apiMap = this.listAPIs();
|
|
782
556
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
783
557
|
if (validAPIs.length === 0) {
|
|
784
|
-
|
|
558
|
+
const data = [];
|
|
559
|
+
for (const key in apiMap) {
|
|
560
|
+
const { reason } = apiMap[key];
|
|
561
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
562
|
+
data.push(apiInvalidReason);
|
|
563
|
+
}
|
|
564
|
+
result.errors.push({
|
|
785
565
|
type: ErrorType.NoSupportedApi,
|
|
786
566
|
content: ConstantString.NoSupportedApi,
|
|
567
|
+
data,
|
|
787
568
|
});
|
|
788
569
|
}
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
validateSpecOperationId() {
|
|
573
|
+
const result = { errors: [], warnings: [] };
|
|
574
|
+
const apiMap = this.listAPIs();
|
|
789
575
|
// OperationId missing
|
|
790
576
|
const apisMissingOperationId = [];
|
|
791
577
|
for (const key in apiMap) {
|
|
@@ -795,54 +581,431 @@ class Utils {
|
|
|
795
581
|
}
|
|
796
582
|
}
|
|
797
583
|
if (apisMissingOperationId.length > 0) {
|
|
798
|
-
warnings.push({
|
|
584
|
+
result.warnings.push({
|
|
799
585
|
type: WarningType.OperationIdMissing,
|
|
800
586
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
801
587
|
data: apisMissingOperationId,
|
|
802
588
|
});
|
|
803
589
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
validateMethodAndPath(method, path) {
|
|
593
|
+
const result = { isValid: true, reason: [] };
|
|
594
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
595
|
+
result.isValid = false;
|
|
596
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
597
|
+
return result;
|
|
807
598
|
}
|
|
808
|
-
|
|
809
|
-
|
|
599
|
+
const pathObj = this.spec.paths[path];
|
|
600
|
+
if (!pathObj || !pathObj[method]) {
|
|
601
|
+
result.isValid = false;
|
|
602
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
603
|
+
return result;
|
|
810
604
|
}
|
|
811
|
-
return
|
|
812
|
-
status,
|
|
813
|
-
warnings,
|
|
814
|
-
errors,
|
|
815
|
-
};
|
|
605
|
+
return result;
|
|
816
606
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
607
|
+
validateResponse(method, path) {
|
|
608
|
+
const result = { isValid: true, reason: [] };
|
|
609
|
+
const operationObject = this.spec.paths[path][method];
|
|
610
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
611
|
+
// only support response body only contains “application/json” content type
|
|
612
|
+
if (multipleMediaType) {
|
|
613
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
614
|
+
}
|
|
615
|
+
else if (Object.keys(json).length === 0) {
|
|
616
|
+
// response body should not be empty
|
|
617
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
618
|
+
}
|
|
619
|
+
return result;
|
|
823
620
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
621
|
+
validateServer(method, path) {
|
|
622
|
+
const result = { isValid: true, reason: [] };
|
|
623
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
624
|
+
if (!serverObj) {
|
|
625
|
+
// should contain server URL
|
|
626
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
827
627
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
628
|
+
else {
|
|
629
|
+
// server url should be absolute url with https protocol
|
|
630
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
631
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
831
632
|
}
|
|
832
|
-
return
|
|
633
|
+
return result;
|
|
833
634
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
635
|
+
validateAuth(method, path) {
|
|
636
|
+
const pathObj = this.spec.paths[path];
|
|
637
|
+
const operationObject = pathObj[method];
|
|
638
|
+
const securities = operationObject.security;
|
|
639
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
640
|
+
if (authSchemeArray.length === 0) {
|
|
641
|
+
return { isValid: true, reason: [] };
|
|
642
|
+
}
|
|
643
|
+
if (this.options.allowAPIKeyAuth ||
|
|
644
|
+
this.options.allowOauth2 ||
|
|
645
|
+
this.options.allowBearerTokenAuth) {
|
|
646
|
+
// Currently we don't support multiple auth in one operation
|
|
647
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
648
|
+
return {
|
|
649
|
+
isValid: false,
|
|
650
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
for (const auths of authSchemeArray) {
|
|
654
|
+
if (auths.length === 1) {
|
|
655
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
656
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
657
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
658
|
+
return { isValid: true, reason: [] };
|
|
659
|
+
}
|
|
842
660
|
}
|
|
843
661
|
}
|
|
844
662
|
}
|
|
845
|
-
return
|
|
663
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
664
|
+
}
|
|
665
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
666
|
+
var _a;
|
|
667
|
+
const paramResult = {
|
|
668
|
+
requiredNum: 0,
|
|
669
|
+
optionalNum: 0,
|
|
670
|
+
isValid: true,
|
|
671
|
+
reason: [],
|
|
672
|
+
};
|
|
673
|
+
if (Object.keys(schema).length === 0) {
|
|
674
|
+
return paramResult;
|
|
675
|
+
}
|
|
676
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
677
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
678
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
679
|
+
paramResult.isValid = false;
|
|
680
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
681
|
+
return paramResult;
|
|
682
|
+
}
|
|
683
|
+
if (schema.type === "string" ||
|
|
684
|
+
schema.type === "integer" ||
|
|
685
|
+
schema.type === "boolean" ||
|
|
686
|
+
schema.type === "number") {
|
|
687
|
+
if (isRequiredWithoutDefault) {
|
|
688
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else if (schema.type === "object") {
|
|
695
|
+
const { properties } = schema;
|
|
696
|
+
for (const property in properties) {
|
|
697
|
+
let isRequired = false;
|
|
698
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
699
|
+
isRequired = true;
|
|
700
|
+
}
|
|
701
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
702
|
+
paramResult.requiredNum += result.requiredNum;
|
|
703
|
+
paramResult.optionalNum += result.optionalNum;
|
|
704
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
705
|
+
paramResult.reason.push(...result.reason);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
710
|
+
paramResult.isValid = false;
|
|
711
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return paramResult;
|
|
715
|
+
}
|
|
716
|
+
checkParamSchema(paramObject) {
|
|
717
|
+
const paramResult = {
|
|
718
|
+
requiredNum: 0,
|
|
719
|
+
optionalNum: 0,
|
|
720
|
+
isValid: true,
|
|
721
|
+
reason: [],
|
|
722
|
+
};
|
|
723
|
+
if (!paramObject) {
|
|
724
|
+
return paramResult;
|
|
725
|
+
}
|
|
726
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
727
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
728
|
+
const param = paramObject[i];
|
|
729
|
+
const schema = param.schema;
|
|
730
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
731
|
+
paramResult.isValid = false;
|
|
732
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
736
|
+
if (isCopilot) {
|
|
737
|
+
if (isRequiredWithoutDefault) {
|
|
738
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
746
|
+
if (isRequiredWithoutDefault) {
|
|
747
|
+
paramResult.isValid = false;
|
|
748
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (schema.type !== "boolean" &&
|
|
753
|
+
schema.type !== "string" &&
|
|
754
|
+
schema.type !== "number" &&
|
|
755
|
+
schema.type !== "integer") {
|
|
756
|
+
if (isRequiredWithoutDefault) {
|
|
757
|
+
paramResult.isValid = false;
|
|
758
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
759
|
+
}
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (param.in === "query" || param.in === "path") {
|
|
763
|
+
if (isRequiredWithoutDefault) {
|
|
764
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return paramResult;
|
|
772
|
+
}
|
|
773
|
+
hasNestedObjectInSchema(schema) {
|
|
774
|
+
if (schema.type === "object") {
|
|
775
|
+
for (const property in schema.properties) {
|
|
776
|
+
const nestedSchema = schema.properties[property];
|
|
777
|
+
if (nestedSchema.type === "object") {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Copyright (c) Microsoft Corporation.
|
|
787
|
+
class CopilotValidator extends Validator {
|
|
788
|
+
constructor(spec, options) {
|
|
789
|
+
super();
|
|
790
|
+
this.projectType = ProjectType.Copilot;
|
|
791
|
+
this.options = options;
|
|
792
|
+
this.spec = spec;
|
|
793
|
+
}
|
|
794
|
+
validateSpec() {
|
|
795
|
+
const result = { errors: [], warnings: [] };
|
|
796
|
+
// validate spec version
|
|
797
|
+
let validationResult = this.validateSpecVersion();
|
|
798
|
+
result.errors.push(...validationResult.errors);
|
|
799
|
+
// validate spec server
|
|
800
|
+
validationResult = this.validateSpecServer();
|
|
801
|
+
result.errors.push(...validationResult.errors);
|
|
802
|
+
// validate no supported API
|
|
803
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
804
|
+
result.errors.push(...validationResult.errors);
|
|
805
|
+
// validate operationId missing
|
|
806
|
+
validationResult = this.validateSpecOperationId();
|
|
807
|
+
result.warnings.push(...validationResult.warnings);
|
|
808
|
+
return result;
|
|
809
|
+
}
|
|
810
|
+
validateAPI(method, path) {
|
|
811
|
+
const result = { isValid: true, reason: [] };
|
|
812
|
+
method = method.toLocaleLowerCase();
|
|
813
|
+
// validate method and path
|
|
814
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
815
|
+
if (!methodAndPathResult.isValid) {
|
|
816
|
+
return methodAndPathResult;
|
|
817
|
+
}
|
|
818
|
+
const operationObject = this.spec.paths[path][method];
|
|
819
|
+
// validate auth
|
|
820
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
821
|
+
result.reason.push(...authCheckResult.reason);
|
|
822
|
+
// validate operationId
|
|
823
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
824
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
825
|
+
}
|
|
826
|
+
// validate server
|
|
827
|
+
const validateServerResult = this.validateServer(method, path);
|
|
828
|
+
result.reason.push(...validateServerResult.reason);
|
|
829
|
+
// validate response
|
|
830
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
831
|
+
result.reason.push(...validateResponseResult.reason);
|
|
832
|
+
// validate requestBody
|
|
833
|
+
const requestBody = operationObject.requestBody;
|
|
834
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
835
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
836
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
837
|
+
}
|
|
838
|
+
if (requestJsonBody) {
|
|
839
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
840
|
+
if (requestBodySchema.type !== "object") {
|
|
841
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
842
|
+
}
|
|
843
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
844
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
845
|
+
}
|
|
846
|
+
// validate parameters
|
|
847
|
+
const paramObject = operationObject.parameters;
|
|
848
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
849
|
+
result.reason.push(...paramResult.reason);
|
|
850
|
+
if (result.reason.length > 0) {
|
|
851
|
+
result.isValid = false;
|
|
852
|
+
}
|
|
853
|
+
return result;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Copyright (c) Microsoft Corporation.
|
|
858
|
+
class SMEValidator extends Validator {
|
|
859
|
+
constructor(spec, options) {
|
|
860
|
+
super();
|
|
861
|
+
this.projectType = ProjectType.SME;
|
|
862
|
+
this.options = options;
|
|
863
|
+
this.spec = spec;
|
|
864
|
+
}
|
|
865
|
+
validateSpec() {
|
|
866
|
+
const result = { errors: [], warnings: [] };
|
|
867
|
+
// validate spec version
|
|
868
|
+
let validationResult = this.validateSpecVersion();
|
|
869
|
+
result.errors.push(...validationResult.errors);
|
|
870
|
+
// validate spec server
|
|
871
|
+
validationResult = this.validateSpecServer();
|
|
872
|
+
result.errors.push(...validationResult.errors);
|
|
873
|
+
// validate no supported API
|
|
874
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
875
|
+
result.errors.push(...validationResult.errors);
|
|
876
|
+
// validate operationId missing
|
|
877
|
+
if (this.options.allowMissingId) {
|
|
878
|
+
validationResult = this.validateSpecOperationId();
|
|
879
|
+
result.warnings.push(...validationResult.warnings);
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
validateAPI(method, path) {
|
|
884
|
+
const result = { isValid: true, reason: [] };
|
|
885
|
+
method = method.toLocaleLowerCase();
|
|
886
|
+
// validate method and path
|
|
887
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
888
|
+
if (!methodAndPathResult.isValid) {
|
|
889
|
+
return methodAndPathResult;
|
|
890
|
+
}
|
|
891
|
+
const operationObject = this.spec.paths[path][method];
|
|
892
|
+
// validate auth
|
|
893
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
894
|
+
result.reason.push(...authCheckResult.reason);
|
|
895
|
+
// validate operationId
|
|
896
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
897
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
898
|
+
}
|
|
899
|
+
// validate server
|
|
900
|
+
const validateServerResult = this.validateServer(method, path);
|
|
901
|
+
result.reason.push(...validateServerResult.reason);
|
|
902
|
+
// validate response
|
|
903
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
904
|
+
result.reason.push(...validateResponseResult.reason);
|
|
905
|
+
let postBodyResult = {
|
|
906
|
+
requiredNum: 0,
|
|
907
|
+
optionalNum: 0,
|
|
908
|
+
isValid: true,
|
|
909
|
+
reason: [],
|
|
910
|
+
};
|
|
911
|
+
// validate requestBody
|
|
912
|
+
const requestBody = operationObject.requestBody;
|
|
913
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
914
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
915
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
916
|
+
}
|
|
917
|
+
if (requestJsonBody) {
|
|
918
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
919
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
920
|
+
result.reason.push(...postBodyResult.reason);
|
|
921
|
+
}
|
|
922
|
+
// validate parameters
|
|
923
|
+
const paramObject = operationObject.parameters;
|
|
924
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
925
|
+
result.reason.push(...paramResult.reason);
|
|
926
|
+
// validate total parameters count
|
|
927
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
928
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
929
|
+
result.reason.push(...paramCountResult.reason);
|
|
930
|
+
}
|
|
931
|
+
if (result.reason.length > 0) {
|
|
932
|
+
result.isValid = false;
|
|
933
|
+
}
|
|
934
|
+
return result;
|
|
935
|
+
}
|
|
936
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
937
|
+
const result = { isValid: true, reason: [] };
|
|
938
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
939
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
940
|
+
if (totalRequiredParams > 1) {
|
|
941
|
+
if (!this.options.allowMultipleParameters ||
|
|
942
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
943
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else if (totalParams === 0) {
|
|
947
|
+
result.reason.push(ErrorType.NoParameter);
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
953
|
+
|
|
954
|
+
// Copyright (c) Microsoft Corporation.
|
|
955
|
+
class TeamsAIValidator extends Validator {
|
|
956
|
+
constructor(spec, options) {
|
|
957
|
+
super();
|
|
958
|
+
this.projectType = ProjectType.TeamsAi;
|
|
959
|
+
this.options = options;
|
|
960
|
+
this.spec = spec;
|
|
961
|
+
}
|
|
962
|
+
validateSpec() {
|
|
963
|
+
const result = { errors: [], warnings: [] };
|
|
964
|
+
// validate spec server
|
|
965
|
+
let validationResult = this.validateSpecServer();
|
|
966
|
+
result.errors.push(...validationResult.errors);
|
|
967
|
+
// validate no supported API
|
|
968
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
969
|
+
result.errors.push(...validationResult.errors);
|
|
970
|
+
return result;
|
|
971
|
+
}
|
|
972
|
+
validateAPI(method, path) {
|
|
973
|
+
const result = { isValid: true, reason: [] };
|
|
974
|
+
method = method.toLocaleLowerCase();
|
|
975
|
+
// validate method and path
|
|
976
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
977
|
+
if (!methodAndPathResult.isValid) {
|
|
978
|
+
return methodAndPathResult;
|
|
979
|
+
}
|
|
980
|
+
const operationObject = this.spec.paths[path][method];
|
|
981
|
+
// validate operationId
|
|
982
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
983
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
984
|
+
}
|
|
985
|
+
// validate server
|
|
986
|
+
const validateServerResult = this.validateServer(method, path);
|
|
987
|
+
result.reason.push(...validateServerResult.reason);
|
|
988
|
+
if (result.reason.length > 0) {
|
|
989
|
+
result.isValid = false;
|
|
990
|
+
}
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
class ValidatorFactory {
|
|
996
|
+
static create(spec, options) {
|
|
997
|
+
var _a;
|
|
998
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
999
|
+
switch (type) {
|
|
1000
|
+
case ProjectType.SME:
|
|
1001
|
+
return new SMEValidator(spec, options);
|
|
1002
|
+
case ProjectType.Copilot:
|
|
1003
|
+
return new CopilotValidator(spec, options);
|
|
1004
|
+
case ProjectType.TeamsAi:
|
|
1005
|
+
return new TeamsAIValidator(spec, options);
|
|
1006
|
+
default:
|
|
1007
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
1008
|
+
}
|
|
846
1009
|
}
|
|
847
1010
|
}
|
|
848
1011
|
|
|
@@ -860,7 +1023,8 @@ class SpecFilter {
|
|
|
860
1023
|
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
861
1024
|
pathObj &&
|
|
862
1025
|
pathObj[methodName]) {
|
|
863
|
-
const
|
|
1026
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1027
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
864
1028
|
if (!validateResult.isValid) {
|
|
865
1029
|
continue;
|
|
866
1030
|
}
|
|
@@ -893,13 +1057,14 @@ class ManifestUpdater {
|
|
|
893
1057
|
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
894
1058
|
manifest.plugins = [
|
|
895
1059
|
{
|
|
896
|
-
|
|
1060
|
+
file: apiPluginRelativePath,
|
|
1061
|
+
id: ConstantString.DefaultPluginId,
|
|
897
1062
|
},
|
|
898
1063
|
];
|
|
899
1064
|
const appName = this.removeEnvs(manifest.name.short);
|
|
900
1065
|
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
901
1066
|
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
902
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
1067
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
|
|
903
1068
|
return [manifest, apiPlugin];
|
|
904
1069
|
}
|
|
905
1070
|
static updateManifestDescription(manifest, spec) {
|
|
@@ -923,8 +1088,8 @@ class ManifestUpdater {
|
|
|
923
1088
|
}
|
|
924
1089
|
return parameter;
|
|
925
1090
|
}
|
|
926
|
-
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
927
|
-
var _a, _b, _c;
|
|
1091
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
|
|
1092
|
+
var _a, _b, _c, _d;
|
|
928
1093
|
const functions = [];
|
|
929
1094
|
const functionNames = [];
|
|
930
1095
|
const paths = spec.paths;
|
|
@@ -986,24 +1151,53 @@ class ManifestUpdater {
|
|
|
986
1151
|
}
|
|
987
1152
|
}
|
|
988
1153
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1154
|
+
let apiPlugin;
|
|
1155
|
+
if (await fs.pathExists(apiPluginFilePath)) {
|
|
1156
|
+
apiPlugin = await fs.readJSON(apiPluginFilePath);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
apiPlugin = {
|
|
1160
|
+
schema_version: "v2",
|
|
1161
|
+
name_for_human: "",
|
|
1162
|
+
description_for_human: "",
|
|
1163
|
+
functions: [],
|
|
1164
|
+
runtimes: [],
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
apiPlugin.functions = apiPlugin.functions || [];
|
|
1168
|
+
for (const func of functions) {
|
|
1169
|
+
const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
|
|
1170
|
+
if (index === -1) {
|
|
1171
|
+
apiPlugin.functions.push(func);
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
apiPlugin.functions[index] = func;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
apiPlugin.runtimes = apiPlugin.runtimes || [];
|
|
1178
|
+
const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath);
|
|
1179
|
+
if (index === -1) {
|
|
1180
|
+
apiPlugin.runtimes.push({
|
|
1181
|
+
type: "OpenApi",
|
|
1182
|
+
auth: {
|
|
1183
|
+
type: "none",
|
|
1004
1184
|
},
|
|
1005
|
-
|
|
1006
|
-
|
|
1185
|
+
spec: {
|
|
1186
|
+
url: specRelativePath,
|
|
1187
|
+
},
|
|
1188
|
+
run_for_functions: functionNames,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
apiPlugin.runtimes[index].run_for_functions = functionNames;
|
|
1193
|
+
}
|
|
1194
|
+
if (!apiPlugin.name_for_human) {
|
|
1195
|
+
apiPlugin.name_for_human = appName;
|
|
1196
|
+
}
|
|
1197
|
+
if (!apiPlugin.description_for_human) {
|
|
1198
|
+
apiPlugin.description_for_human =
|
|
1199
|
+
(_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
|
|
1200
|
+
}
|
|
1007
1201
|
return apiPlugin;
|
|
1008
1202
|
}
|
|
1009
1203
|
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
@@ -1072,16 +1266,26 @@ class ManifestUpdater {
|
|
|
1072
1266
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1073
1267
|
const operationItem = operations[method];
|
|
1074
1268
|
if (operationItem) {
|
|
1075
|
-
const
|
|
1269
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1270
|
+
if (command.parameters &&
|
|
1271
|
+
command.parameters.length >= 1 &&
|
|
1272
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1273
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1274
|
+
}
|
|
1275
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1276
|
+
command.parameters = [command.parameters[0]];
|
|
1277
|
+
warnings.push({
|
|
1278
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1279
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1280
|
+
data: command.id,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1076
1283
|
if (adaptiveCardFolder) {
|
|
1077
1284
|
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1078
1285
|
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1079
1286
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1080
1287
|
: "";
|
|
1081
1288
|
}
|
|
1082
|
-
if (warning) {
|
|
1083
|
-
warnings.push(warning);
|
|
1084
|
-
}
|
|
1085
1289
|
commands.push(command);
|
|
1086
1290
|
}
|
|
1087
1291
|
}
|
|
@@ -1398,6 +1602,8 @@ class SpecParser {
|
|
|
1398
1602
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1399
1603
|
};
|
|
1400
1604
|
}
|
|
1605
|
+
const errors = [];
|
|
1606
|
+
const warnings = [];
|
|
1401
1607
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1402
1608
|
return {
|
|
1403
1609
|
status: ValidationStatus.Error,
|
|
@@ -1407,23 +1613,38 @@ class SpecParser {
|
|
|
1407
1613
|
],
|
|
1408
1614
|
};
|
|
1409
1615
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1616
|
+
// Remote reference not supported
|
|
1617
|
+
const refPaths = this.parser.$refs.paths();
|
|
1618
|
+
// refPaths [0] is the current spec file path
|
|
1619
|
+
if (refPaths.length > 1) {
|
|
1620
|
+
errors.push({
|
|
1621
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1622
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1623
|
+
data: refPaths,
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1627
|
+
warnings.push({
|
|
1628
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1629
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1630
|
+
});
|
|
1425
1631
|
}
|
|
1426
|
-
|
|
1632
|
+
const validator = this.getValidator(this.spec);
|
|
1633
|
+
const validationResult = validator.validateSpec();
|
|
1634
|
+
warnings.push(...validationResult.warnings);
|
|
1635
|
+
errors.push(...validationResult.errors);
|
|
1636
|
+
let status = ValidationStatus.Valid;
|
|
1637
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1638
|
+
status = ValidationStatus.Warning;
|
|
1639
|
+
}
|
|
1640
|
+
else if (errors.length > 0) {
|
|
1641
|
+
status = ValidationStatus.Error;
|
|
1642
|
+
}
|
|
1643
|
+
return {
|
|
1644
|
+
status: status,
|
|
1645
|
+
warnings: warnings,
|
|
1646
|
+
errors: errors,
|
|
1647
|
+
};
|
|
1427
1648
|
}
|
|
1428
1649
|
catch (err) {
|
|
1429
1650
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1452,34 +1673,27 @@ class SpecParser {
|
|
|
1452
1673
|
for (const apiKey in apiMap) {
|
|
1453
1674
|
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1454
1675
|
const [method, path] = apiKey.split(" ");
|
|
1676
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1455
1677
|
const apiResult = {
|
|
1456
|
-
api:
|
|
1678
|
+
api: apiKey,
|
|
1457
1679
|
server: "",
|
|
1458
|
-
operationId:
|
|
1680
|
+
operationId: operationId,
|
|
1459
1681
|
isValid: isValid,
|
|
1460
1682
|
reason: reason,
|
|
1461
1683
|
};
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
apiResult.operationId = operationId;
|
|
1475
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1476
|
-
for (const auths of authArray) {
|
|
1477
|
-
if (auths.length === 1) {
|
|
1478
|
-
apiResult.auth = auths[0];
|
|
1479
|
-
break;
|
|
1684
|
+
if (isValid) {
|
|
1685
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1686
|
+
if (serverObj) {
|
|
1687
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1688
|
+
}
|
|
1689
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1690
|
+
for (const auths of authArray) {
|
|
1691
|
+
if (auths.length === 1) {
|
|
1692
|
+
apiResult.auth = auths[0];
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1480
1695
|
}
|
|
1481
1696
|
}
|
|
1482
|
-
apiResult.api = apiKey;
|
|
1483
1697
|
result.APIs.push(apiResult);
|
|
1484
1698
|
}
|
|
1485
1699
|
result.allAPICount = result.APIs.length;
|
|
@@ -1659,12 +1873,18 @@ class SpecParser {
|
|
|
1659
1873
|
}
|
|
1660
1874
|
}
|
|
1661
1875
|
getAPIs(spec) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1876
|
+
const validator = this.getValidator(spec);
|
|
1877
|
+
const apiMap = validator.listAPIs();
|
|
1878
|
+
this.apiMap = apiMap;
|
|
1879
|
+
return apiMap;
|
|
1880
|
+
}
|
|
1881
|
+
getValidator(spec) {
|
|
1882
|
+
if (this.validator) {
|
|
1883
|
+
return this.validator;
|
|
1664
1884
|
}
|
|
1665
|
-
const
|
|
1666
|
-
this.
|
|
1667
|
-
return
|
|
1885
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1886
|
+
this.validator = validator;
|
|
1887
|
+
return validator;
|
|
1668
1888
|
}
|
|
1669
1889
|
}
|
|
1670
1890
|
|