@microsoft/m365-spec-parser 0.1.1-alpha.4f2290daa.0 → 0.1.1-alpha.5fc8ceacd.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 +536 -339
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +596 -372
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +536 -339
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +644 -418
- 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 +3 -24
- 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 {
|
|
@@ -722,16 +479,49 @@ class Utils {
|
|
|
722
479
|
};
|
|
723
480
|
return command;
|
|
724
481
|
}
|
|
725
|
-
static
|
|
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;
|
|
496
|
+
}
|
|
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;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Copyright (c) Microsoft Corporation.
|
|
511
|
+
class Validator {
|
|
512
|
+
listAPIs() {
|
|
726
513
|
var _a;
|
|
727
|
-
|
|
514
|
+
if (this.apiMap) {
|
|
515
|
+
return this.apiMap;
|
|
516
|
+
}
|
|
517
|
+
const paths = this.spec.paths;
|
|
728
518
|
const result = {};
|
|
729
519
|
for (const path in paths) {
|
|
730
520
|
const methods = paths[path];
|
|
731
521
|
for (const method in methods) {
|
|
732
522
|
const operationObject = methods[method];
|
|
733
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
734
|
-
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);
|
|
735
525
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
736
526
|
operation: operationObject,
|
|
737
527
|
isValid: validateResult.isValid,
|
|
@@ -740,38 +530,48 @@ class Utils {
|
|
|
740
530
|
}
|
|
741
531
|
}
|
|
742
532
|
}
|
|
533
|
+
this.apiMap = result;
|
|
743
534
|
return result;
|
|
744
535
|
}
|
|
745
|
-
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
const serverErrors = Utils.validateServer(spec, options);
|
|
756
|
-
errors.push(...serverErrors);
|
|
757
|
-
// Remote reference not supported
|
|
758
|
-
const refPaths = parser.$refs.paths();
|
|
759
|
-
// refPaths [0] is the current spec file path
|
|
760
|
-
if (refPaths.length > 1) {
|
|
761
|
-
errors.push({
|
|
762
|
-
type: ErrorType.RemoteRefNotSupported,
|
|
763
|
-
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
764
|
-
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,
|
|
765
543
|
});
|
|
766
544
|
}
|
|
767
|
-
|
|
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();
|
|
768
556
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
769
557
|
if (validAPIs.length === 0) {
|
|
770
|
-
|
|
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({
|
|
771
565
|
type: ErrorType.NoSupportedApi,
|
|
772
566
|
content: ConstantString.NoSupportedApi,
|
|
567
|
+
data,
|
|
773
568
|
});
|
|
774
569
|
}
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
validateSpecOperationId() {
|
|
573
|
+
const result = { errors: [], warnings: [] };
|
|
574
|
+
const apiMap = this.listAPIs();
|
|
775
575
|
// OperationId missing
|
|
776
576
|
const apisMissingOperationId = [];
|
|
777
577
|
for (const key in apiMap) {
|
|
@@ -781,54 +581,431 @@ class Utils {
|
|
|
781
581
|
}
|
|
782
582
|
}
|
|
783
583
|
if (apisMissingOperationId.length > 0) {
|
|
784
|
-
warnings.push({
|
|
584
|
+
result.warnings.push({
|
|
785
585
|
type: WarningType.OperationIdMissing,
|
|
786
586
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
787
587
|
data: apisMissingOperationId,
|
|
788
588
|
});
|
|
789
589
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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;
|
|
793
598
|
}
|
|
794
|
-
|
|
795
|
-
|
|
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;
|
|
796
604
|
}
|
|
797
|
-
return
|
|
798
|
-
status,
|
|
799
|
-
warnings,
|
|
800
|
-
errors,
|
|
801
|
-
};
|
|
605
|
+
return result;
|
|
802
606
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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;
|
|
809
620
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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);
|
|
813
627
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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));
|
|
817
632
|
}
|
|
818
|
-
return
|
|
633
|
+
return result;
|
|
819
634
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
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;
|
|
828
779
|
}
|
|
829
780
|
}
|
|
830
781
|
}
|
|
831
|
-
return
|
|
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
|
+
}
|
|
832
1009
|
}
|
|
833
1010
|
}
|
|
834
1011
|
|
|
@@ -846,7 +1023,8 @@ class SpecFilter {
|
|
|
846
1023
|
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
847
1024
|
pathObj &&
|
|
848
1025
|
pathObj[methodName]) {
|
|
849
|
-
const
|
|
1026
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1027
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
850
1028
|
if (!validateResult.isValid) {
|
|
851
1029
|
continue;
|
|
852
1030
|
}
|
|
@@ -879,13 +1057,14 @@ class ManifestUpdater {
|
|
|
879
1057
|
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
|
|
880
1058
|
manifest.plugins = [
|
|
881
1059
|
{
|
|
882
|
-
|
|
1060
|
+
file: apiPluginRelativePath,
|
|
1061
|
+
id: ConstantString.DefaultPluginId,
|
|
883
1062
|
},
|
|
884
1063
|
];
|
|
885
1064
|
const appName = this.removeEnvs(manifest.name.short);
|
|
886
1065
|
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
887
1066
|
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
888
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
1067
|
+
const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options);
|
|
889
1068
|
return [manifest, apiPlugin];
|
|
890
1069
|
}
|
|
891
1070
|
static updateManifestDescription(manifest, spec) {
|
|
@@ -909,8 +1088,8 @@ class ManifestUpdater {
|
|
|
909
1088
|
}
|
|
910
1089
|
return parameter;
|
|
911
1090
|
}
|
|
912
|
-
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
913
|
-
var _a, _b, _c;
|
|
1091
|
+
static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, options) {
|
|
1092
|
+
var _a, _b, _c, _d;
|
|
914
1093
|
const functions = [];
|
|
915
1094
|
const functionNames = [];
|
|
916
1095
|
const paths = spec.paths;
|
|
@@ -972,24 +1151,53 @@ class ManifestUpdater {
|
|
|
972
1151
|
}
|
|
973
1152
|
}
|
|
974
1153
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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",
|
|
990
1184
|
},
|
|
991
|
-
|
|
992
|
-
|
|
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
|
+
}
|
|
993
1201
|
return apiPlugin;
|
|
994
1202
|
}
|
|
995
1203
|
static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
|
|
@@ -1394,6 +1602,8 @@ class SpecParser {
|
|
|
1394
1602
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1395
1603
|
};
|
|
1396
1604
|
}
|
|
1605
|
+
const errors = [];
|
|
1606
|
+
const warnings = [];
|
|
1397
1607
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1398
1608
|
return {
|
|
1399
1609
|
status: ValidationStatus.Error,
|
|
@@ -1403,23 +1613,38 @@ class SpecParser {
|
|
|
1403
1613
|
],
|
|
1404
1614
|
};
|
|
1405
1615
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
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
|
+
});
|
|
1421
1631
|
}
|
|
1422
|
-
|
|
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
|
+
};
|
|
1423
1648
|
}
|
|
1424
1649
|
catch (err) {
|
|
1425
1650
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1448,34 +1673,27 @@ class SpecParser {
|
|
|
1448
1673
|
for (const apiKey in apiMap) {
|
|
1449
1674
|
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1450
1675
|
const [method, path] = apiKey.split(" ");
|
|
1676
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1451
1677
|
const apiResult = {
|
|
1452
|
-
api:
|
|
1678
|
+
api: apiKey,
|
|
1453
1679
|
server: "",
|
|
1454
|
-
operationId:
|
|
1680
|
+
operationId: operationId,
|
|
1455
1681
|
isValid: isValid,
|
|
1456
1682
|
reason: reason,
|
|
1457
1683
|
};
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1470
|
-
apiResult.operationId = operationId;
|
|
1471
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1472
|
-
for (const auths of authArray) {
|
|
1473
|
-
if (auths.length === 1) {
|
|
1474
|
-
apiResult.auth = auths[0];
|
|
1475
|
-
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
|
+
}
|
|
1476
1695
|
}
|
|
1477
1696
|
}
|
|
1478
|
-
apiResult.api = apiKey;
|
|
1479
1697
|
result.APIs.push(apiResult);
|
|
1480
1698
|
}
|
|
1481
1699
|
result.allAPICount = result.APIs.length;
|
|
@@ -1655,12 +1873,18 @@ class SpecParser {
|
|
|
1655
1873
|
}
|
|
1656
1874
|
}
|
|
1657
1875
|
getAPIs(spec) {
|
|
1658
|
-
|
|
1659
|
-
|
|
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;
|
|
1660
1884
|
}
|
|
1661
|
-
const
|
|
1662
|
-
this.
|
|
1663
|
-
return
|
|
1885
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1886
|
+
this.validator = validator;
|
|
1887
|
+
return validator;
|
|
1664
1888
|
}
|
|
1665
1889
|
}
|
|
1666
1890
|
|