@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.42af26ca6.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 +571 -325
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +640 -365
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +571 -325
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +640 -365
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/interfaces.d.ts +41 -1
- package/dist/src/manifestUpdater.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +3 -1
- package/dist/src/specParser.d.ts +3 -1
- package/dist/src/utils.d.ts +8 -28
- package/package.json +3 -3
package/dist/index.esm2017.mjs
CHANGED
|
@@ -20,6 +20,7 @@ var ErrorType;
|
|
|
20
20
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
21
21
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
22
22
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
23
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
23
24
|
ErrorType["ListFailed"] = "list-failed";
|
|
24
25
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
25
26
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -28,6 +29,21 @@ var ErrorType;
|
|
|
28
29
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
29
30
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
30
31
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
32
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
33
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
34
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
35
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
36
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
37
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
38
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
39
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
40
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
41
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
42
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
43
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
44
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
45
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
46
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
31
47
|
ErrorType["Cancelled"] = "cancelled";
|
|
32
48
|
ErrorType["Unknown"] = "unknown";
|
|
33
49
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -75,6 +91,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
75
91
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
76
92
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
77
93
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
94
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
78
95
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
79
96
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
80
97
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
@@ -172,221 +189,9 @@ class Utils {
|
|
|
172
189
|
}
|
|
173
190
|
return false;
|
|
174
191
|
}
|
|
175
|
-
static checkParameters(paramObject, isCopilot) {
|
|
176
|
-
const paramResult = {
|
|
177
|
-
requiredNum: 0,
|
|
178
|
-
optionalNum: 0,
|
|
179
|
-
isValid: true,
|
|
180
|
-
};
|
|
181
|
-
if (!paramObject) {
|
|
182
|
-
return paramResult;
|
|
183
|
-
}
|
|
184
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
185
|
-
const param = paramObject[i];
|
|
186
|
-
const schema = param.schema;
|
|
187
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
188
|
-
paramResult.isValid = false;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
192
|
-
if (isCopilot) {
|
|
193
|
-
if (isRequiredWithoutDefault) {
|
|
194
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
198
|
-
}
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
202
|
-
if (isRequiredWithoutDefault) {
|
|
203
|
-
paramResult.isValid = false;
|
|
204
|
-
}
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (schema.type !== "boolean" &&
|
|
208
|
-
schema.type !== "string" &&
|
|
209
|
-
schema.type !== "number" &&
|
|
210
|
-
schema.type !== "integer") {
|
|
211
|
-
if (isRequiredWithoutDefault) {
|
|
212
|
-
paramResult.isValid = false;
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (param.in === "query" || param.in === "path") {
|
|
217
|
-
if (isRequiredWithoutDefault) {
|
|
218
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return paramResult;
|
|
226
|
-
}
|
|
227
|
-
static checkPostBody(schema, isRequired = false, isCopilot = false) {
|
|
228
|
-
var _a;
|
|
229
|
-
const paramResult = {
|
|
230
|
-
requiredNum: 0,
|
|
231
|
-
optionalNum: 0,
|
|
232
|
-
isValid: true,
|
|
233
|
-
};
|
|
234
|
-
if (Object.keys(schema).length === 0) {
|
|
235
|
-
return paramResult;
|
|
236
|
-
}
|
|
237
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
238
|
-
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
239
|
-
paramResult.isValid = false;
|
|
240
|
-
return paramResult;
|
|
241
|
-
}
|
|
242
|
-
if (schema.type === "string" ||
|
|
243
|
-
schema.type === "integer" ||
|
|
244
|
-
schema.type === "boolean" ||
|
|
245
|
-
schema.type === "number") {
|
|
246
|
-
if (isRequiredWithoutDefault) {
|
|
247
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
else if (schema.type === "object") {
|
|
254
|
-
const { properties } = schema;
|
|
255
|
-
for (const property in properties) {
|
|
256
|
-
let isRequired = false;
|
|
257
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
258
|
-
isRequired = true;
|
|
259
|
-
}
|
|
260
|
-
const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
|
|
261
|
-
paramResult.requiredNum += result.requiredNum;
|
|
262
|
-
paramResult.optionalNum += result.optionalNum;
|
|
263
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
if (isRequiredWithoutDefault && !isCopilot) {
|
|
268
|
-
paramResult.isValid = false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return paramResult;
|
|
272
|
-
}
|
|
273
192
|
static containMultipleMediaTypes(bodyObject) {
|
|
274
193
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
275
194
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Checks if the given API is supported.
|
|
278
|
-
* @param {string} method - The HTTP method of the API.
|
|
279
|
-
* @param {string} path - The path of the API.
|
|
280
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
281
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
282
|
-
* @description The following APIs are supported:
|
|
283
|
-
* 1. only support Get/Post operation without auth property
|
|
284
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
285
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
286
|
-
* 4. request body + required parameters <= 1
|
|
287
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
288
|
-
* 6. only support request body with “application/json” content type
|
|
289
|
-
*/
|
|
290
|
-
static isSupportedApi(method, path, spec, options) {
|
|
291
|
-
var _a;
|
|
292
|
-
const pathObj = spec.paths[path];
|
|
293
|
-
method = method.toLocaleLowerCase();
|
|
294
|
-
if (pathObj) {
|
|
295
|
-
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
|
|
296
|
-
const securities = pathObj[method].security;
|
|
297
|
-
const isTeamsAi = options.projectType === ProjectType.TeamsAi;
|
|
298
|
-
const isCopilot = options.projectType === ProjectType.Copilot;
|
|
299
|
-
// Teams AI project doesn't care about auth, it will use authProvider for user to implement
|
|
300
|
-
if (!isTeamsAi) {
|
|
301
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
302
|
-
if (!Utils.isSupportedAuth(authArray, options)) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
const operationObject = pathObj[method];
|
|
307
|
-
if (!options.allowMissingId && !operationObject.operationId) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
const paramObject = operationObject.parameters;
|
|
311
|
-
const requestBody = operationObject.requestBody;
|
|
312
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
313
|
-
if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
|
|
317
|
-
if (Object.keys(responseJson).length === 0) {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
// Teams AI project doesn't care about request parameters/body
|
|
321
|
-
if (isTeamsAi) {
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
let requestBodyParamResult = {
|
|
325
|
-
requiredNum: 0,
|
|
326
|
-
optionalNum: 0,
|
|
327
|
-
isValid: true,
|
|
328
|
-
};
|
|
329
|
-
if (requestJsonBody) {
|
|
330
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
331
|
-
if (isCopilot && requestBodySchema.type !== "object") {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
|
|
335
|
-
}
|
|
336
|
-
if (!requestBodyParamResult.isValid) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
const paramResult = Utils.checkParameters(paramObject, isCopilot);
|
|
340
|
-
if (!paramResult.isValid) {
|
|
341
|
-
return false;
|
|
342
|
-
}
|
|
343
|
-
// Copilot support arbitrary parameters
|
|
344
|
-
if (isCopilot) {
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
348
|
-
if (options.allowMultipleParameters &&
|
|
349
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <=
|
|
350
|
-
ConstantString.SMERequiredParamsMaxNum) {
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
else if (requestBodyParamResult.requiredNum +
|
|
356
|
-
requestBodyParamResult.optionalNum +
|
|
357
|
-
paramResult.requiredNum +
|
|
358
|
-
paramResult.optionalNum ===
|
|
359
|
-
0) {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
return true;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
static isSupportedAuth(authSchemeArray, options) {
|
|
370
|
-
if (authSchemeArray.length === 0) {
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
|
|
374
|
-
// Currently we don't support multiple auth in one operation
|
|
375
|
-
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
for (const auths of authSchemeArray) {
|
|
379
|
-
if (auths.length === 1) {
|
|
380
|
-
if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
381
|
-
(options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
382
|
-
(options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
195
|
static isBearerTokenAuth(authScheme) {
|
|
391
196
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
392
197
|
}
|
|
@@ -394,10 +199,9 @@ class Utils {
|
|
|
394
199
|
return authScheme.type === "apiKey";
|
|
395
200
|
}
|
|
396
201
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return false;
|
|
202
|
+
return !!(authScheme.type === "oauth2" &&
|
|
203
|
+
authScheme.flows &&
|
|
204
|
+
authScheme.flows.authorizationCode);
|
|
401
205
|
}
|
|
402
206
|
static getAuthArray(securities, spec) {
|
|
403
207
|
var _a;
|
|
@@ -425,14 +229,17 @@ class Utils {
|
|
|
425
229
|
static updateFirstLetter(str) {
|
|
426
230
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
427
231
|
}
|
|
428
|
-
static getResponseJson(operationObject
|
|
232
|
+
static getResponseJson(operationObject) {
|
|
429
233
|
var _a, _b;
|
|
430
234
|
let json = {};
|
|
235
|
+
let multipleMediaType = false;
|
|
431
236
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
432
237
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
433
238
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
239
|
+
multipleMediaType = false;
|
|
434
240
|
json = responseObject.content["application/json"];
|
|
435
|
-
if (
|
|
241
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
242
|
+
multipleMediaType = true;
|
|
436
243
|
json = {};
|
|
437
244
|
}
|
|
438
245
|
else {
|
|
@@ -440,7 +247,7 @@ class Utils {
|
|
|
440
247
|
}
|
|
441
248
|
}
|
|
442
249
|
}
|
|
443
|
-
return json;
|
|
250
|
+
return { json, multipleMediaType };
|
|
444
251
|
}
|
|
445
252
|
static convertPathToCamelCase(path) {
|
|
446
253
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -460,10 +267,10 @@ class Utils {
|
|
|
460
267
|
return undefined;
|
|
461
268
|
}
|
|
462
269
|
}
|
|
463
|
-
static
|
|
270
|
+
static resolveEnv(str) {
|
|
464
271
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
465
|
-
let matches = placeHolderReg.exec(
|
|
466
|
-
let
|
|
272
|
+
let matches = placeHolderReg.exec(str);
|
|
273
|
+
let newStr = str;
|
|
467
274
|
while (matches != null) {
|
|
468
275
|
const envVar = matches[1];
|
|
469
276
|
const envVal = process.env[envVar];
|
|
@@ -471,17 +278,17 @@ class Utils {
|
|
|
471
278
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
472
279
|
}
|
|
473
280
|
else {
|
|
474
|
-
|
|
281
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
475
282
|
}
|
|
476
|
-
matches = placeHolderReg.exec(
|
|
283
|
+
matches = placeHolderReg.exec(str);
|
|
477
284
|
}
|
|
478
|
-
return
|
|
285
|
+
return newStr;
|
|
479
286
|
}
|
|
480
287
|
static checkServerUrl(servers) {
|
|
481
288
|
const errors = [];
|
|
482
289
|
let serverUrl;
|
|
483
290
|
try {
|
|
484
|
-
serverUrl = Utils.
|
|
291
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
485
292
|
}
|
|
486
293
|
catch (err) {
|
|
487
294
|
errors.push({
|
|
@@ -512,6 +319,7 @@ class Utils {
|
|
|
512
319
|
return errors;
|
|
513
320
|
}
|
|
514
321
|
static validateServer(spec, options) {
|
|
322
|
+
var _a;
|
|
515
323
|
const errors = [];
|
|
516
324
|
let hasTopLevelServers = false;
|
|
517
325
|
let hasPathLevelServers = false;
|
|
@@ -532,7 +340,7 @@ class Utils {
|
|
|
532
340
|
}
|
|
533
341
|
for (const method in methods) {
|
|
534
342
|
const operationObject = methods[method];
|
|
535
|
-
if (
|
|
343
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
536
344
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
537
345
|
hasOperationLevelServers = true;
|
|
538
346
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -659,13 +467,7 @@ class Utils {
|
|
|
659
467
|
}
|
|
660
468
|
}
|
|
661
469
|
const operationId = operationItem.operationId;
|
|
662
|
-
const parameters = [];
|
|
663
|
-
if (requiredParams.length !== 0) {
|
|
664
|
-
parameters.push(...requiredParams);
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
parameters.push(optionalParams[0]);
|
|
668
|
-
}
|
|
470
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
669
471
|
const command = {
|
|
670
472
|
context: ["compose"],
|
|
671
473
|
type: "query",
|
|
@@ -674,142 +476,559 @@ class Utils {
|
|
|
674
476
|
parameters: parameters,
|
|
675
477
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
676
478
|
};
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
479
|
+
return command;
|
|
480
|
+
}
|
|
481
|
+
static format(str, ...args) {
|
|
482
|
+
let index = 0;
|
|
483
|
+
return str.replace(/%s/g, () => {
|
|
484
|
+
const arg = args[index++];
|
|
485
|
+
return arg !== undefined ? arg : "";
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
489
|
+
if (!authName) {
|
|
490
|
+
return "";
|
|
684
491
|
}
|
|
685
|
-
|
|
492
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
493
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
494
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
495
|
+
}
|
|
496
|
+
return safeRegistrationIdEnvName;
|
|
686
497
|
}
|
|
687
|
-
static
|
|
688
|
-
const
|
|
498
|
+
static getServerObject(spec, method, path) {
|
|
499
|
+
const pathObj = spec.paths[path];
|
|
500
|
+
const operationObject = pathObj[method];
|
|
501
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
502
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
503
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
504
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
505
|
+
return serverUrl;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Copyright (c) Microsoft Corporation.
|
|
510
|
+
class Validator {
|
|
511
|
+
listAPIs() {
|
|
512
|
+
var _a;
|
|
513
|
+
if (this.apiMap) {
|
|
514
|
+
return this.apiMap;
|
|
515
|
+
}
|
|
516
|
+
const paths = this.spec.paths;
|
|
689
517
|
const result = {};
|
|
690
518
|
for (const path in paths) {
|
|
691
519
|
const methods = paths[path];
|
|
692
520
|
for (const method in methods) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
521
|
+
const operationObject = methods[method];
|
|
522
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
523
|
+
const validateResult = this.validateAPI(method, path);
|
|
524
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
525
|
+
operation: operationObject,
|
|
526
|
+
isValid: validateResult.isValid,
|
|
527
|
+
reason: validateResult.reason,
|
|
528
|
+
};
|
|
696
529
|
}
|
|
697
530
|
}
|
|
698
531
|
}
|
|
532
|
+
this.apiMap = result;
|
|
699
533
|
return result;
|
|
700
534
|
}
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
535
|
+
validateSpecVersion() {
|
|
536
|
+
const result = { errors: [], warnings: [] };
|
|
537
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
538
|
+
result.errors.push({
|
|
539
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
540
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
541
|
+
data: this.spec.openapi,
|
|
708
542
|
});
|
|
709
543
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const apiMap = Utils.listSupportedAPIs(spec, options);
|
|
725
|
-
if (Object.keys(apiMap).length === 0) {
|
|
726
|
-
errors.push({
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
546
|
+
validateSpecServer() {
|
|
547
|
+
const result = { errors: [], warnings: [] };
|
|
548
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
549
|
+
result.errors.push(...serverErrors);
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
validateSpecNoSupportAPI() {
|
|
553
|
+
const result = { errors: [], warnings: [] };
|
|
554
|
+
const apiMap = this.listAPIs();
|
|
555
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
556
|
+
if (validAPIs.length === 0) {
|
|
557
|
+
result.errors.push({
|
|
727
558
|
type: ErrorType.NoSupportedApi,
|
|
728
559
|
content: ConstantString.NoSupportedApi,
|
|
729
560
|
});
|
|
730
561
|
}
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
validateSpecOperationId() {
|
|
565
|
+
const result = { errors: [], warnings: [] };
|
|
566
|
+
const apiMap = this.listAPIs();
|
|
731
567
|
// OperationId missing
|
|
732
568
|
const apisMissingOperationId = [];
|
|
733
569
|
for (const key in apiMap) {
|
|
734
|
-
const
|
|
735
|
-
if (!
|
|
570
|
+
const { operation } = apiMap[key];
|
|
571
|
+
if (!operation.operationId) {
|
|
736
572
|
apisMissingOperationId.push(key);
|
|
737
573
|
}
|
|
738
574
|
}
|
|
739
575
|
if (apisMissingOperationId.length > 0) {
|
|
740
|
-
warnings.push({
|
|
576
|
+
result.warnings.push({
|
|
741
577
|
type: WarningType.OperationIdMissing,
|
|
742
578
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
743
579
|
data: apisMissingOperationId,
|
|
744
580
|
});
|
|
745
581
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
validateMethodAndPath(method, path) {
|
|
585
|
+
const result = { isValid: true, reason: [] };
|
|
586
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
587
|
+
result.isValid = false;
|
|
588
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
589
|
+
return result;
|
|
749
590
|
}
|
|
750
|
-
|
|
751
|
-
|
|
591
|
+
const pathObj = this.spec.paths[path];
|
|
592
|
+
if (!pathObj || !pathObj[method]) {
|
|
593
|
+
result.isValid = false;
|
|
594
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
595
|
+
return result;
|
|
752
596
|
}
|
|
753
|
-
return
|
|
754
|
-
status,
|
|
755
|
-
warnings,
|
|
756
|
-
errors,
|
|
757
|
-
};
|
|
597
|
+
return result;
|
|
758
598
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
599
|
+
validateResponse(method, path) {
|
|
600
|
+
const result = { isValid: true, reason: [] };
|
|
601
|
+
const operationObject = this.spec.paths[path][method];
|
|
602
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
603
|
+
// only support response body only contains “application/json” content type
|
|
604
|
+
if (multipleMediaType) {
|
|
605
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
606
|
+
}
|
|
607
|
+
else if (Object.keys(json).length === 0) {
|
|
608
|
+
// response body should not be empty
|
|
609
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
765
612
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
613
|
+
validateServer(method, path) {
|
|
614
|
+
const result = { isValid: true, reason: [] };
|
|
615
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
616
|
+
if (!serverObj) {
|
|
617
|
+
// should contain server URL
|
|
618
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
769
619
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
620
|
+
else {
|
|
621
|
+
// server url should be absolute url with https protocol
|
|
622
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
623
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
773
624
|
}
|
|
774
|
-
return
|
|
625
|
+
return result;
|
|
775
626
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
627
|
+
validateAuth(method, path) {
|
|
628
|
+
const pathObj = this.spec.paths[path];
|
|
629
|
+
const operationObject = pathObj[method];
|
|
630
|
+
const securities = operationObject.security;
|
|
631
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
632
|
+
if (authSchemeArray.length === 0) {
|
|
633
|
+
return { isValid: true, reason: [] };
|
|
634
|
+
}
|
|
635
|
+
if (this.options.allowAPIKeyAuth ||
|
|
636
|
+
this.options.allowOauth2 ||
|
|
637
|
+
this.options.allowBearerTokenAuth) {
|
|
638
|
+
// Currently we don't support multiple auth in one operation
|
|
639
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
640
|
+
return {
|
|
641
|
+
isValid: false,
|
|
642
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
for (const auths of authSchemeArray) {
|
|
646
|
+
if (auths.length === 1) {
|
|
647
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
648
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
649
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
650
|
+
return { isValid: true, reason: [] };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
656
|
+
}
|
|
657
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
658
|
+
var _a;
|
|
659
|
+
const paramResult = {
|
|
660
|
+
requiredNum: 0,
|
|
661
|
+
optionalNum: 0,
|
|
662
|
+
isValid: true,
|
|
663
|
+
reason: [],
|
|
664
|
+
};
|
|
665
|
+
if (Object.keys(schema).length === 0) {
|
|
666
|
+
return paramResult;
|
|
667
|
+
}
|
|
668
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
669
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
670
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
671
|
+
paramResult.isValid = false;
|
|
672
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
673
|
+
return paramResult;
|
|
674
|
+
}
|
|
675
|
+
if (schema.type === "string" ||
|
|
676
|
+
schema.type === "integer" ||
|
|
677
|
+
schema.type === "boolean" ||
|
|
678
|
+
schema.type === "number") {
|
|
679
|
+
if (isRequiredWithoutDefault) {
|
|
680
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else if (schema.type === "object") {
|
|
687
|
+
const { properties } = schema;
|
|
688
|
+
for (const property in properties) {
|
|
689
|
+
let isRequired = false;
|
|
690
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
691
|
+
isRequired = true;
|
|
784
692
|
}
|
|
693
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
694
|
+
paramResult.requiredNum += result.requiredNum;
|
|
695
|
+
paramResult.optionalNum += result.optionalNum;
|
|
696
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
697
|
+
paramResult.reason.push(...result.reason);
|
|
785
698
|
}
|
|
786
699
|
}
|
|
787
|
-
|
|
700
|
+
else {
|
|
701
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
702
|
+
paramResult.isValid = false;
|
|
703
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return paramResult;
|
|
707
|
+
}
|
|
708
|
+
checkParamSchema(paramObject) {
|
|
709
|
+
const paramResult = {
|
|
710
|
+
requiredNum: 0,
|
|
711
|
+
optionalNum: 0,
|
|
712
|
+
isValid: true,
|
|
713
|
+
reason: [],
|
|
714
|
+
};
|
|
715
|
+
if (!paramObject) {
|
|
716
|
+
return paramResult;
|
|
717
|
+
}
|
|
718
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
719
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
720
|
+
const param = paramObject[i];
|
|
721
|
+
const schema = param.schema;
|
|
722
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
723
|
+
paramResult.isValid = false;
|
|
724
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
728
|
+
if (isCopilot) {
|
|
729
|
+
if (isRequiredWithoutDefault) {
|
|
730
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
738
|
+
if (isRequiredWithoutDefault) {
|
|
739
|
+
paramResult.isValid = false;
|
|
740
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (schema.type !== "boolean" &&
|
|
745
|
+
schema.type !== "string" &&
|
|
746
|
+
schema.type !== "number" &&
|
|
747
|
+
schema.type !== "integer") {
|
|
748
|
+
if (isRequiredWithoutDefault) {
|
|
749
|
+
paramResult.isValid = false;
|
|
750
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
751
|
+
}
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (param.in === "query" || param.in === "path") {
|
|
755
|
+
if (isRequiredWithoutDefault) {
|
|
756
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return paramResult;
|
|
764
|
+
}
|
|
765
|
+
hasNestedObjectInSchema(schema) {
|
|
766
|
+
if (schema.type === "object") {
|
|
767
|
+
for (const property in schema.properties) {
|
|
768
|
+
const nestedSchema = schema.properties[property];
|
|
769
|
+
if (nestedSchema.type === "object") {
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Copyright (c) Microsoft Corporation.
|
|
779
|
+
class CopilotValidator extends Validator {
|
|
780
|
+
constructor(spec, options) {
|
|
781
|
+
super();
|
|
782
|
+
this.projectType = ProjectType.Copilot;
|
|
783
|
+
this.options = options;
|
|
784
|
+
this.spec = spec;
|
|
785
|
+
}
|
|
786
|
+
validateSpec() {
|
|
787
|
+
const result = { errors: [], warnings: [] };
|
|
788
|
+
// validate spec version
|
|
789
|
+
let validationResult = this.validateSpecVersion();
|
|
790
|
+
result.errors.push(...validationResult.errors);
|
|
791
|
+
// validate spec server
|
|
792
|
+
validationResult = this.validateSpecServer();
|
|
793
|
+
result.errors.push(...validationResult.errors);
|
|
794
|
+
// validate no supported API
|
|
795
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
796
|
+
result.errors.push(...validationResult.errors);
|
|
797
|
+
// validate operationId missing
|
|
798
|
+
validationResult = this.validateSpecOperationId();
|
|
799
|
+
result.warnings.push(...validationResult.warnings);
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
validateAPI(method, path) {
|
|
803
|
+
const result = { isValid: true, reason: [] };
|
|
804
|
+
method = method.toLocaleLowerCase();
|
|
805
|
+
// validate method and path
|
|
806
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
807
|
+
if (!methodAndPathResult.isValid) {
|
|
808
|
+
return methodAndPathResult;
|
|
809
|
+
}
|
|
810
|
+
const operationObject = this.spec.paths[path][method];
|
|
811
|
+
// validate auth
|
|
812
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
813
|
+
result.reason.push(...authCheckResult.reason);
|
|
814
|
+
// validate operationId
|
|
815
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
816
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
817
|
+
}
|
|
818
|
+
// validate server
|
|
819
|
+
const validateServerResult = this.validateServer(method, path);
|
|
820
|
+
result.reason.push(...validateServerResult.reason);
|
|
821
|
+
// validate response
|
|
822
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
823
|
+
result.reason.push(...validateResponseResult.reason);
|
|
824
|
+
// validate requestBody
|
|
825
|
+
const requestBody = operationObject.requestBody;
|
|
826
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
827
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
828
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
829
|
+
}
|
|
830
|
+
if (requestJsonBody) {
|
|
831
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
832
|
+
if (requestBodySchema.type !== "object") {
|
|
833
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
834
|
+
}
|
|
835
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
836
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
837
|
+
}
|
|
838
|
+
// validate parameters
|
|
839
|
+
const paramObject = operationObject.parameters;
|
|
840
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
841
|
+
result.reason.push(...paramResult.reason);
|
|
842
|
+
if (result.reason.length > 0) {
|
|
843
|
+
result.isValid = false;
|
|
844
|
+
}
|
|
845
|
+
return result;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Copyright (c) Microsoft Corporation.
|
|
850
|
+
class SMEValidator extends Validator {
|
|
851
|
+
constructor(spec, options) {
|
|
852
|
+
super();
|
|
853
|
+
this.projectType = ProjectType.SME;
|
|
854
|
+
this.options = options;
|
|
855
|
+
this.spec = spec;
|
|
856
|
+
}
|
|
857
|
+
validateSpec() {
|
|
858
|
+
const result = { errors: [], warnings: [] };
|
|
859
|
+
// validate spec version
|
|
860
|
+
let validationResult = this.validateSpecVersion();
|
|
861
|
+
result.errors.push(...validationResult.errors);
|
|
862
|
+
// validate spec server
|
|
863
|
+
validationResult = this.validateSpecServer();
|
|
864
|
+
result.errors.push(...validationResult.errors);
|
|
865
|
+
// validate no supported API
|
|
866
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
867
|
+
result.errors.push(...validationResult.errors);
|
|
868
|
+
// validate operationId missing
|
|
869
|
+
validationResult = this.validateSpecOperationId();
|
|
870
|
+
result.warnings.push(...validationResult.warnings);
|
|
871
|
+
return result;
|
|
872
|
+
}
|
|
873
|
+
validateAPI(method, path) {
|
|
874
|
+
const result = { isValid: true, reason: [] };
|
|
875
|
+
method = method.toLocaleLowerCase();
|
|
876
|
+
// validate method and path
|
|
877
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
878
|
+
if (!methodAndPathResult.isValid) {
|
|
879
|
+
return methodAndPathResult;
|
|
880
|
+
}
|
|
881
|
+
const operationObject = this.spec.paths[path][method];
|
|
882
|
+
// validate auth
|
|
883
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
884
|
+
result.reason.push(...authCheckResult.reason);
|
|
885
|
+
// validate operationId
|
|
886
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
887
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
888
|
+
}
|
|
889
|
+
// validate server
|
|
890
|
+
const validateServerResult = this.validateServer(method, path);
|
|
891
|
+
result.reason.push(...validateServerResult.reason);
|
|
892
|
+
// validate response
|
|
893
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
894
|
+
result.reason.push(...validateResponseResult.reason);
|
|
895
|
+
let postBodyResult = {
|
|
896
|
+
requiredNum: 0,
|
|
897
|
+
optionalNum: 0,
|
|
898
|
+
isValid: true,
|
|
899
|
+
reason: [],
|
|
900
|
+
};
|
|
901
|
+
// validate requestBody
|
|
902
|
+
const requestBody = operationObject.requestBody;
|
|
903
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
904
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
905
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
906
|
+
}
|
|
907
|
+
if (requestJsonBody) {
|
|
908
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
909
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
910
|
+
result.reason.push(...postBodyResult.reason);
|
|
911
|
+
}
|
|
912
|
+
// validate parameters
|
|
913
|
+
const paramObject = operationObject.parameters;
|
|
914
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
915
|
+
result.reason.push(...paramResult.reason);
|
|
916
|
+
// validate total parameters count
|
|
917
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
918
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
919
|
+
result.reason.push(...paramCountResult.reason);
|
|
920
|
+
}
|
|
921
|
+
if (result.reason.length > 0) {
|
|
922
|
+
result.isValid = false;
|
|
923
|
+
}
|
|
924
|
+
return result;
|
|
925
|
+
}
|
|
926
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
927
|
+
const result = { isValid: true, reason: [] };
|
|
928
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
929
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
930
|
+
if (totalRequiredParams > 1) {
|
|
931
|
+
if (!this.options.allowMultipleParameters ||
|
|
932
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
933
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
else if (totalParams === 0) {
|
|
937
|
+
result.reason.push(ErrorType.NoParameter);
|
|
938
|
+
}
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
943
|
+
|
|
944
|
+
// Copyright (c) Microsoft Corporation.
|
|
945
|
+
class TeamsAIValidator extends Validator {
|
|
946
|
+
constructor(spec, options) {
|
|
947
|
+
super();
|
|
948
|
+
this.projectType = ProjectType.TeamsAi;
|
|
949
|
+
this.options = options;
|
|
950
|
+
this.spec = spec;
|
|
951
|
+
}
|
|
952
|
+
validateSpec() {
|
|
953
|
+
const result = { errors: [], warnings: [] };
|
|
954
|
+
// validate spec server
|
|
955
|
+
let validationResult = this.validateSpecServer();
|
|
956
|
+
result.errors.push(...validationResult.errors);
|
|
957
|
+
// validate no supported API
|
|
958
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
959
|
+
result.errors.push(...validationResult.errors);
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
validateAPI(method, path) {
|
|
963
|
+
const result = { isValid: true, reason: [] };
|
|
964
|
+
method = method.toLocaleLowerCase();
|
|
965
|
+
// validate method and path
|
|
966
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
967
|
+
if (!methodAndPathResult.isValid) {
|
|
968
|
+
return methodAndPathResult;
|
|
969
|
+
}
|
|
970
|
+
const operationObject = this.spec.paths[path][method];
|
|
971
|
+
// validate operationId
|
|
972
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
973
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
974
|
+
}
|
|
975
|
+
// validate server
|
|
976
|
+
const validateServerResult = this.validateServer(method, path);
|
|
977
|
+
result.reason.push(...validateServerResult.reason);
|
|
978
|
+
if (result.reason.length > 0) {
|
|
979
|
+
result.isValid = false;
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
class ValidatorFactory {
|
|
986
|
+
static create(spec, options) {
|
|
987
|
+
var _a;
|
|
988
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
989
|
+
switch (type) {
|
|
990
|
+
case ProjectType.SME:
|
|
991
|
+
return new SMEValidator(spec, options);
|
|
992
|
+
case ProjectType.Copilot:
|
|
993
|
+
return new CopilotValidator(spec, options);
|
|
994
|
+
case ProjectType.TeamsAi:
|
|
995
|
+
return new TeamsAIValidator(spec, options);
|
|
996
|
+
default:
|
|
997
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
998
|
+
}
|
|
788
999
|
}
|
|
789
1000
|
}
|
|
790
1001
|
|
|
791
1002
|
// Copyright (c) Microsoft Corporation.
|
|
792
1003
|
class SpecFilter {
|
|
793
1004
|
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
|
|
1005
|
+
var _a;
|
|
794
1006
|
try {
|
|
795
1007
|
const newSpec = Object.assign({}, unResolveSpec);
|
|
796
1008
|
const newPaths = {};
|
|
797
1009
|
for (const filterItem of filter) {
|
|
798
1010
|
const [method, path] = filterItem.split(" ");
|
|
799
1011
|
const methodName = method.toLowerCase();
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1012
|
+
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
|
|
1013
|
+
if (ConstantString.AllOperationMethods.includes(methodName) &&
|
|
1014
|
+
pathObj &&
|
|
1015
|
+
pathObj[methodName]) {
|
|
1016
|
+
const validator = ValidatorFactory.create(resolvedSpec, options);
|
|
1017
|
+
const validateResult = validator.validateAPI(methodName, path);
|
|
1018
|
+
if (!validateResult.isValid) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
if (!newPaths[path]) {
|
|
1022
|
+
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
|
|
1023
|
+
for (const m of ConstantString.AllOperationMethods) {
|
|
1024
|
+
delete newPaths[path][m];
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
1028
|
+
// Add the operationId if missing
|
|
1029
|
+
if (!newPaths[path][methodName].operationId) {
|
|
1030
|
+
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
807
1031
|
}
|
|
808
|
-
}
|
|
809
|
-
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
|
|
810
|
-
// Add the operationId if missing
|
|
811
|
-
if (!newPaths[path][methodName].operationId) {
|
|
812
|
-
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
|
|
813
1032
|
}
|
|
814
1033
|
}
|
|
815
1034
|
newSpec.paths = newPaths;
|
|
@@ -831,9 +1050,10 @@ class ManifestUpdater {
|
|
|
831
1050
|
pluginFile: apiPluginRelativePath,
|
|
832
1051
|
},
|
|
833
1052
|
];
|
|
1053
|
+
const appName = this.removeEnvs(manifest.name.short);
|
|
834
1054
|
ManifestUpdater.updateManifestDescription(manifest, spec);
|
|
835
1055
|
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
|
|
836
|
-
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
|
|
1056
|
+
const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, appName, options);
|
|
837
1057
|
return [manifest, apiPlugin];
|
|
838
1058
|
}
|
|
839
1059
|
static updateManifestDescription(manifest, spec) {
|
|
@@ -857,7 +1077,7 @@ class ManifestUpdater {
|
|
|
857
1077
|
}
|
|
858
1078
|
return parameter;
|
|
859
1079
|
}
|
|
860
|
-
static generatePluginManifestSchema(spec, specRelativePath, options) {
|
|
1080
|
+
static generatePluginManifestSchema(spec, specRelativePath, appName, options) {
|
|
861
1081
|
var _a, _b, _c;
|
|
862
1082
|
const functions = [];
|
|
863
1083
|
const functionNames = [];
|
|
@@ -922,7 +1142,7 @@ class ManifestUpdater {
|
|
|
922
1142
|
}
|
|
923
1143
|
const apiPlugin = {
|
|
924
1144
|
schema_version: "v2",
|
|
925
|
-
name_for_human:
|
|
1145
|
+
name_for_human: appName,
|
|
926
1146
|
description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
|
|
927
1147
|
functions: functions,
|
|
928
1148
|
runtimes: [
|
|
@@ -1006,16 +1226,26 @@ class ManifestUpdater {
|
|
|
1006
1226
|
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
|
|
1007
1227
|
const operationItem = operations[method];
|
|
1008
1228
|
if (operationItem) {
|
|
1009
|
-
const
|
|
1229
|
+
const command = Utils.parseApiInfo(operationItem, options);
|
|
1230
|
+
if (command.parameters &&
|
|
1231
|
+
command.parameters.length >= 1 &&
|
|
1232
|
+
command.parameters.some((param) => param.isRequired)) {
|
|
1233
|
+
command.parameters = command.parameters.filter((param) => param.isRequired);
|
|
1234
|
+
}
|
|
1235
|
+
else if (command.parameters && command.parameters.length > 0) {
|
|
1236
|
+
command.parameters = [command.parameters[0]];
|
|
1237
|
+
warnings.push({
|
|
1238
|
+
type: WarningType.OperationOnlyContainsOptionalParam,
|
|
1239
|
+
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
|
|
1240
|
+
data: command.id,
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1010
1243
|
if (adaptiveCardFolder) {
|
|
1011
1244
|
const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
|
|
1012
1245
|
command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
|
|
1013
1246
|
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
|
|
1014
1247
|
: "";
|
|
1015
1248
|
}
|
|
1016
|
-
if (warning) {
|
|
1017
|
-
warnings.push(warning);
|
|
1018
|
-
}
|
|
1019
1249
|
commands.push(command);
|
|
1020
1250
|
}
|
|
1021
1251
|
}
|
|
@@ -1029,13 +1259,22 @@ class ManifestUpdater {
|
|
|
1029
1259
|
const relativePath = path.relative(path.dirname(from), to);
|
|
1030
1260
|
return path.normalize(relativePath).replace(/\\/g, "/");
|
|
1031
1261
|
}
|
|
1262
|
+
static removeEnvs(str) {
|
|
1263
|
+
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
1264
|
+
const matches = placeHolderReg.exec(str);
|
|
1265
|
+
let newStr = str;
|
|
1266
|
+
if (matches != null) {
|
|
1267
|
+
newStr = newStr.replace(matches[0], "");
|
|
1268
|
+
}
|
|
1269
|
+
return newStr;
|
|
1270
|
+
}
|
|
1032
1271
|
}
|
|
1033
1272
|
|
|
1034
1273
|
// Copyright (c) Microsoft Corporation.
|
|
1035
1274
|
class AdaptiveCardGenerator {
|
|
1036
1275
|
static generateAdaptiveCard(operationItem) {
|
|
1037
1276
|
try {
|
|
1038
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1277
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
1039
1278
|
let cardBody = [];
|
|
1040
1279
|
let schema = json.schema;
|
|
1041
1280
|
let jsonPath = "$";
|
|
@@ -1323,6 +1562,8 @@ class SpecParser {
|
|
|
1323
1562
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
1324
1563
|
};
|
|
1325
1564
|
}
|
|
1565
|
+
const errors = [];
|
|
1566
|
+
const warnings = [];
|
|
1326
1567
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
1327
1568
|
return {
|
|
1328
1569
|
status: ValidationStatus.Error,
|
|
@@ -1332,7 +1573,38 @@ class SpecParser {
|
|
|
1332
1573
|
],
|
|
1333
1574
|
};
|
|
1334
1575
|
}
|
|
1335
|
-
|
|
1576
|
+
// Remote reference not supported
|
|
1577
|
+
const refPaths = this.parser.$refs.paths();
|
|
1578
|
+
// refPaths [0] is the current spec file path
|
|
1579
|
+
if (refPaths.length > 1) {
|
|
1580
|
+
errors.push({
|
|
1581
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1582
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1583
|
+
data: refPaths,
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
if (!!this.isSwaggerFile && this.options.allowSwagger) {
|
|
1587
|
+
warnings.push({
|
|
1588
|
+
type: WarningType.ConvertSwaggerToOpenAPI,
|
|
1589
|
+
content: ConstantString.ConvertSwaggerToOpenAPI,
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
const validator = this.getValidator(this.spec);
|
|
1593
|
+
const validationResult = validator.validateSpec();
|
|
1594
|
+
warnings.push(...validationResult.warnings);
|
|
1595
|
+
errors.push(...validationResult.errors);
|
|
1596
|
+
let status = ValidationStatus.Valid;
|
|
1597
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1598
|
+
status = ValidationStatus.Warning;
|
|
1599
|
+
}
|
|
1600
|
+
else if (errors.length > 0) {
|
|
1601
|
+
status = ValidationStatus.Error;
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
status: status,
|
|
1605
|
+
warnings: warnings,
|
|
1606
|
+
errors: errors,
|
|
1607
|
+
};
|
|
1336
1608
|
}
|
|
1337
1609
|
catch (err) {
|
|
1338
1610
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -1352,45 +1624,40 @@ class SpecParser {
|
|
|
1352
1624
|
try {
|
|
1353
1625
|
await this.loadSpec();
|
|
1354
1626
|
const spec = this.spec;
|
|
1355
|
-
const apiMap = this.
|
|
1627
|
+
const apiMap = this.getAPIs(spec);
|
|
1356
1628
|
const result = {
|
|
1357
|
-
|
|
1629
|
+
APIs: [],
|
|
1358
1630
|
allAPICount: 0,
|
|
1359
1631
|
validAPICount: 0,
|
|
1360
1632
|
};
|
|
1361
1633
|
for (const apiKey in apiMap) {
|
|
1634
|
+
const { operation, isValid, reason } = apiMap[apiKey];
|
|
1635
|
+
const [method, path] = apiKey.split(" ");
|
|
1636
|
+
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1362
1637
|
const apiResult = {
|
|
1363
|
-
api:
|
|
1638
|
+
api: apiKey,
|
|
1364
1639
|
server: "",
|
|
1365
|
-
operationId:
|
|
1640
|
+
operationId: operationId,
|
|
1641
|
+
isValid: isValid,
|
|
1642
|
+
reason: reason,
|
|
1366
1643
|
};
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
if (!operationId) {
|
|
1379
|
-
operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
|
|
1380
|
-
}
|
|
1381
|
-
apiResult.operationId = operationId;
|
|
1382
|
-
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1383
|
-
for (const auths of authArray) {
|
|
1384
|
-
if (auths.length === 1) {
|
|
1385
|
-
apiResult.auth = auths[0];
|
|
1386
|
-
break;
|
|
1644
|
+
if (isValid) {
|
|
1645
|
+
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
|
|
1646
|
+
if (serverObj) {
|
|
1647
|
+
apiResult.server = Utils.resolveEnv(serverObj.url);
|
|
1648
|
+
}
|
|
1649
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
1650
|
+
for (const auths of authArray) {
|
|
1651
|
+
if (auths.length === 1) {
|
|
1652
|
+
apiResult.auth = auths[0];
|
|
1653
|
+
break;
|
|
1654
|
+
}
|
|
1387
1655
|
}
|
|
1388
1656
|
}
|
|
1389
|
-
apiResult
|
|
1390
|
-
result.validAPIs.push(apiResult);
|
|
1657
|
+
result.APIs.push(apiResult);
|
|
1391
1658
|
}
|
|
1392
|
-
result.allAPICount =
|
|
1393
|
-
result.validAPICount = result.
|
|
1659
|
+
result.allAPICount = result.APIs.length;
|
|
1660
|
+
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
|
|
1394
1661
|
return result;
|
|
1395
1662
|
}
|
|
1396
1663
|
catch (err) {
|
|
@@ -1482,15 +1749,18 @@ class SpecParser {
|
|
|
1482
1749
|
const newSpecs = await this.getFilteredSpecs(filter, signal);
|
|
1483
1750
|
const newUnResolvedSpec = newSpecs[0];
|
|
1484
1751
|
const newSpec = newSpecs[1];
|
|
1485
|
-
const authSet = new Set();
|
|
1486
1752
|
let hasMultipleAuth = false;
|
|
1753
|
+
let authInfo = undefined;
|
|
1487
1754
|
for (const url in newSpec.paths) {
|
|
1488
1755
|
for (const method in newSpec.paths[url]) {
|
|
1489
1756
|
const operation = newSpec.paths[url][method];
|
|
1490
1757
|
const authArray = Utils.getAuthArray(operation.security, newSpec);
|
|
1491
1758
|
if (authArray && authArray.length > 0) {
|
|
1492
|
-
|
|
1493
|
-
if (
|
|
1759
|
+
const currentAuth = authArray[0][0];
|
|
1760
|
+
if (!authInfo) {
|
|
1761
|
+
authInfo = authArray[0][0];
|
|
1762
|
+
}
|
|
1763
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
1494
1764
|
hasMultipleAuth = true;
|
|
1495
1765
|
break;
|
|
1496
1766
|
}
|
|
@@ -1537,7 +1807,6 @@ class SpecParser {
|
|
|
1537
1807
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
1538
1808
|
throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
|
|
1539
1809
|
}
|
|
1540
|
-
const authInfo = Array.from(authSet)[0];
|
|
1541
1810
|
const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
|
|
1542
1811
|
await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
|
|
1543
1812
|
result.warnings.push(...warnings);
|
|
@@ -1563,13 +1832,19 @@ class SpecParser {
|
|
|
1563
1832
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
1564
1833
|
}
|
|
1565
1834
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1835
|
+
getAPIs(spec) {
|
|
1836
|
+
const validator = this.getValidator(spec);
|
|
1837
|
+
const apiMap = validator.listAPIs();
|
|
1838
|
+
this.apiMap = apiMap;
|
|
1839
|
+
return apiMap;
|
|
1840
|
+
}
|
|
1841
|
+
getValidator(spec) {
|
|
1842
|
+
if (this.validator) {
|
|
1843
|
+
return this.validator;
|
|
1569
1844
|
}
|
|
1570
|
-
const
|
|
1571
|
-
this.
|
|
1572
|
-
return
|
|
1845
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1846
|
+
this.validator = validator;
|
|
1847
|
+
return validator;
|
|
1573
1848
|
}
|
|
1574
1849
|
}
|
|
1575
1850
|
|