@microsoft/m365-spec-parser 0.1.1-alpha.cf377d39f.0 → 0.1.1-alpha.ded43fb2d.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 +656 -302
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1173 -496
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +658 -302
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1187 -502
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +7 -1
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +106 -18
- package/dist/src/manifestUpdater.d.ts +11 -4
- package/dist/src/specFilter.d.ts +2 -1
- package/dist/src/specParser.browser.d.ts +12 -3
- package/dist/src/specParser.d.ts +14 -5
- package/dist/src/utils.d.ts +19 -34
- package/package.json +4 -4
package/dist/index.esm5.js
CHANGED
|
@@ -45,7 +45,8 @@ var ErrorType;
|
|
|
45
45
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
46
46
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
47
47
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
48
|
-
ErrorType["
|
|
48
|
+
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
49
|
+
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
49
50
|
ErrorType["ListFailed"] = "list-failed";
|
|
50
51
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
51
52
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
@@ -54,6 +55,21 @@ var ErrorType;
|
|
|
54
55
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
55
56
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
56
57
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
58
|
+
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
59
|
+
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
60
|
+
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
61
|
+
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
62
|
+
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
63
|
+
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
64
|
+
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
65
|
+
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
66
|
+
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
67
|
+
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
68
|
+
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
69
|
+
ErrorType["NoParameter"] = "no-parameter";
|
|
70
|
+
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
71
|
+
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
72
|
+
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
57
73
|
ErrorType["Cancelled"] = "cancelled";
|
|
58
74
|
ErrorType["Unknown"] = "unknown";
|
|
59
75
|
})(ErrorType || (ErrorType = {}));
|
|
@@ -76,7 +92,13 @@ var ValidationStatus;
|
|
|
76
92
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
77
93
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
78
94
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
79
|
-
})(ValidationStatus || (ValidationStatus = {}));
|
|
95
|
+
})(ValidationStatus || (ValidationStatus = {}));
|
|
96
|
+
var ProjectType;
|
|
97
|
+
(function (ProjectType) {
|
|
98
|
+
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
99
|
+
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
100
|
+
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
101
|
+
})(ProjectType || (ProjectType = {}));
|
|
80
102
|
|
|
81
103
|
// Copyright (c) Microsoft Corporation.
|
|
82
104
|
class SpecParserError extends Error {
|
|
@@ -103,7 +125,9 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
|
|
|
103
125
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
104
126
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
105
127
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
106
|
-
ConstantString.
|
|
128
|
+
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
129
|
+
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
130
|
+
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
107
131
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
108
132
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
109
133
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
@@ -113,8 +137,10 @@ ConstantString.AdaptiveCardVersion = "1.5";
|
|
|
113
137
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
114
138
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
115
139
|
ConstantString.TextBlockType = "TextBlock";
|
|
140
|
+
ConstantString.ImageType = "Image";
|
|
116
141
|
ConstantString.ContainerType = "Container";
|
|
117
142
|
ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
|
|
143
|
+
ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
|
|
118
144
|
ConstantString.ResponseCodeFor20X = [
|
|
119
145
|
"200",
|
|
120
146
|
"201",
|
|
@@ -174,205 +200,36 @@ ConstantString.FullDescriptionMaxLens = 4000;
|
|
|
174
200
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
175
201
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
176
202
|
ConstantString.CommandTitleMaxLens = 32;
|
|
177
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
203
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
204
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
205
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
178
206
|
|
|
179
207
|
// Copyright (c) Microsoft Corporation.
|
|
180
208
|
class Utils {
|
|
181
|
-
static
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
};
|
|
187
|
-
if (!paramObject) {
|
|
188
|
-
return paramResult;
|
|
189
|
-
}
|
|
190
|
-
for (let i = 0; i < paramObject.length; i++) {
|
|
191
|
-
const param = paramObject[i];
|
|
192
|
-
const schema = param.schema;
|
|
193
|
-
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
194
|
-
if (param.in === "header" || param.in === "cookie") {
|
|
195
|
-
if (isRequiredWithoutDefault) {
|
|
196
|
-
paramResult.isValid = false;
|
|
197
|
-
}
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
if (schema.type !== "boolean" &&
|
|
201
|
-
schema.type !== "string" &&
|
|
202
|
-
schema.type !== "number" &&
|
|
203
|
-
schema.type !== "integer") {
|
|
204
|
-
if (isRequiredWithoutDefault) {
|
|
205
|
-
paramResult.isValid = false;
|
|
206
|
-
}
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (param.in === "query" || param.in === "path") {
|
|
210
|
-
if (isRequiredWithoutDefault) {
|
|
211
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return paramResult;
|
|
219
|
-
}
|
|
220
|
-
static checkPostBody(schema, isRequired = false) {
|
|
221
|
-
var _a;
|
|
222
|
-
const paramResult = {
|
|
223
|
-
requiredNum: 0,
|
|
224
|
-
optionalNum: 0,
|
|
225
|
-
isValid: true,
|
|
226
|
-
};
|
|
227
|
-
if (Object.keys(schema).length === 0) {
|
|
228
|
-
return paramResult;
|
|
229
|
-
}
|
|
230
|
-
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
231
|
-
if (schema.type === "string" ||
|
|
232
|
-
schema.type === "integer" ||
|
|
233
|
-
schema.type === "boolean" ||
|
|
234
|
-
schema.type === "number") {
|
|
235
|
-
if (isRequiredWithoutDefault) {
|
|
236
|
-
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
else if (schema.type === "object") {
|
|
243
|
-
const { properties } = schema;
|
|
244
|
-
for (const property in properties) {
|
|
245
|
-
let isRequired = false;
|
|
246
|
-
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
247
|
-
isRequired = true;
|
|
248
|
-
}
|
|
249
|
-
const result = Utils.checkPostBody(properties[property], isRequired);
|
|
250
|
-
paramResult.requiredNum += result.requiredNum;
|
|
251
|
-
paramResult.optionalNum += result.optionalNum;
|
|
252
|
-
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
if (isRequiredWithoutDefault) {
|
|
257
|
-
paramResult.isValid = false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return paramResult;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Checks if the given API is supported.
|
|
264
|
-
* @param {string} method - The HTTP method of the API.
|
|
265
|
-
* @param {string} path - The path of the API.
|
|
266
|
-
* @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
|
|
267
|
-
* @returns {boolean} - Returns true if the API is supported, false otherwise.
|
|
268
|
-
* @description The following APIs are supported:
|
|
269
|
-
* 1. only support Get/Post operation without auth property
|
|
270
|
-
* 2. parameter inside query or path only support string, number, boolean and integer
|
|
271
|
-
* 3. parameter inside post body only support string, number, boolean, integer and object
|
|
272
|
-
* 4. request body + required parameters <= 1
|
|
273
|
-
* 5. response body should be “application/json” and not empty, and response code should be 20X
|
|
274
|
-
* 6. only support request body with “application/json” content type
|
|
275
|
-
*/
|
|
276
|
-
static isSupportedApi(method, path, spec, allowMissingId, allowAPIKeyAuth, allowMultipleParameters, allowOauth2) {
|
|
277
|
-
const pathObj = spec.paths[path];
|
|
278
|
-
method = method.toLocaleLowerCase();
|
|
279
|
-
if (pathObj) {
|
|
280
|
-
if ((method === ConstantString.PostMethod || method === ConstantString.GetMethod) &&
|
|
281
|
-
pathObj[method]) {
|
|
282
|
-
const securities = pathObj[method].security;
|
|
283
|
-
const authArray = Utils.getAuthArray(securities, spec);
|
|
284
|
-
if (!Utils.isSupportedAuth(authArray, allowAPIKeyAuth, allowOauth2)) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
const operationObject = pathObj[method];
|
|
288
|
-
if (!allowMissingId && !operationObject.operationId) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
const paramObject = operationObject.parameters;
|
|
292
|
-
const requestBody = operationObject.requestBody;
|
|
293
|
-
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
294
|
-
const mediaTypesCount = Object.keys((requestBody === null || requestBody === void 0 ? void 0 : requestBody.content) || {}).length;
|
|
295
|
-
if (mediaTypesCount > 1) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
const responseJson = Utils.getResponseJson(operationObject);
|
|
299
|
-
if (Object.keys(responseJson).length === 0) {
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
let requestBodyParamResult = {
|
|
303
|
-
requiredNum: 0,
|
|
304
|
-
optionalNum: 0,
|
|
305
|
-
isValid: true,
|
|
306
|
-
};
|
|
307
|
-
if (requestJsonBody) {
|
|
308
|
-
const requestBodySchema = requestJsonBody.schema;
|
|
309
|
-
requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required);
|
|
310
|
-
}
|
|
311
|
-
if (!requestBodyParamResult.isValid) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const paramResult = Utils.checkParameters(paramObject);
|
|
315
|
-
if (!paramResult.isValid) {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
|
|
319
|
-
if (allowMultipleParameters &&
|
|
320
|
-
requestBodyParamResult.requiredNum + paramResult.requiredNum <= 5) {
|
|
321
|
-
return true;
|
|
322
|
-
}
|
|
323
|
-
return false;
|
|
324
|
-
}
|
|
325
|
-
else if (requestBodyParamResult.requiredNum +
|
|
326
|
-
requestBodyParamResult.optionalNum +
|
|
327
|
-
paramResult.requiredNum +
|
|
328
|
-
paramResult.optionalNum ===
|
|
329
|
-
0) {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
209
|
+
static hasNestedObjectInSchema(schema) {
|
|
210
|
+
if (schema.type === "object") {
|
|
211
|
+
for (const property in schema.properties) {
|
|
212
|
+
const nestedSchema = schema.properties[property];
|
|
213
|
+
if (nestedSchema.type === "object") {
|
|
333
214
|
return true;
|
|
334
215
|
}
|
|
335
216
|
}
|
|
336
217
|
}
|
|
337
218
|
return false;
|
|
338
219
|
}
|
|
339
|
-
static
|
|
340
|
-
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
if (allowAPIKeyAuth || allowOauth2) {
|
|
344
|
-
// Currently we don't support multiple auth in one operation
|
|
345
|
-
if (authSchemaArray.length > 0 && authSchemaArray.every((auths) => auths.length > 1)) {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
for (const auths of authSchemaArray) {
|
|
349
|
-
if (auths.length === 1) {
|
|
350
|
-
if (!allowOauth2 && allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authSchema)) {
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
else if (!allowAPIKeyAuth &&
|
|
354
|
-
allowOauth2 &&
|
|
355
|
-
Utils.isBearerTokenAuth(auths[0].authSchema)) {
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
else if (allowAPIKeyAuth &&
|
|
359
|
-
allowOauth2 &&
|
|
360
|
-
(Utils.isAPIKeyAuth(auths[0].authSchema) ||
|
|
361
|
-
Utils.isBearerTokenAuth(auths[0].authSchema))) {
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return false;
|
|
220
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
221
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
368
222
|
}
|
|
369
|
-
static
|
|
370
|
-
return
|
|
223
|
+
static isBearerTokenAuth(authScheme) {
|
|
224
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
371
225
|
}
|
|
372
|
-
static
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
|
|
226
|
+
static isAPIKeyAuth(authScheme) {
|
|
227
|
+
return authScheme.type === "apiKey";
|
|
228
|
+
}
|
|
229
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
230
|
+
return !!(authScheme.type === "oauth2" &&
|
|
231
|
+
authScheme.flows &&
|
|
232
|
+
authScheme.flows.authorizationCode);
|
|
376
233
|
}
|
|
377
234
|
static getAuthArray(securities, spec) {
|
|
378
235
|
var _a;
|
|
@@ -385,7 +242,7 @@ class Utils {
|
|
|
385
242
|
for (const name in security) {
|
|
386
243
|
const auth = securitySchemas[name];
|
|
387
244
|
authArray.push({
|
|
388
|
-
|
|
245
|
+
authScheme: auth,
|
|
389
246
|
name: name,
|
|
390
247
|
});
|
|
391
248
|
}
|
|
@@ -397,24 +254,47 @@ class Utils {
|
|
|
397
254
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
398
255
|
return result;
|
|
399
256
|
}
|
|
257
|
+
static getAuthInfo(spec) {
|
|
258
|
+
let authInfo = undefined;
|
|
259
|
+
for (const url in spec.paths) {
|
|
260
|
+
for (const method in spec.paths[url]) {
|
|
261
|
+
const operation = spec.paths[url][method];
|
|
262
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
263
|
+
if (authArray && authArray.length > 0) {
|
|
264
|
+
const currentAuth = authArray[0][0];
|
|
265
|
+
if (!authInfo) {
|
|
266
|
+
authInfo = authArray[0][0];
|
|
267
|
+
}
|
|
268
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
269
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return authInfo;
|
|
275
|
+
}
|
|
400
276
|
static updateFirstLetter(str) {
|
|
401
277
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
402
278
|
}
|
|
403
279
|
static getResponseJson(operationObject) {
|
|
404
280
|
var _a, _b;
|
|
405
281
|
let json = {};
|
|
282
|
+
let multipleMediaType = false;
|
|
406
283
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
407
284
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
408
|
-
const mediaTypesCount = Object.keys((responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) || {}).length;
|
|
409
|
-
if (mediaTypesCount > 1) {
|
|
410
|
-
return {};
|
|
411
|
-
}
|
|
412
285
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
286
|
+
multipleMediaType = false;
|
|
413
287
|
json = responseObject.content["application/json"];
|
|
414
|
-
|
|
288
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
289
|
+
multipleMediaType = true;
|
|
290
|
+
json = {};
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
415
295
|
}
|
|
416
296
|
}
|
|
417
|
-
return json;
|
|
297
|
+
return { json, multipleMediaType };
|
|
418
298
|
}
|
|
419
299
|
static convertPathToCamelCase(path) {
|
|
420
300
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -434,10 +314,10 @@ class Utils {
|
|
|
434
314
|
return undefined;
|
|
435
315
|
}
|
|
436
316
|
}
|
|
437
|
-
static
|
|
317
|
+
static resolveEnv(str) {
|
|
438
318
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
439
|
-
let matches = placeHolderReg.exec(
|
|
440
|
-
let
|
|
319
|
+
let matches = placeHolderReg.exec(str);
|
|
320
|
+
let newStr = str;
|
|
441
321
|
while (matches != null) {
|
|
442
322
|
const envVar = matches[1];
|
|
443
323
|
const envVal = process.env[envVar];
|
|
@@ -445,17 +325,17 @@ class Utils {
|
|
|
445
325
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
446
326
|
}
|
|
447
327
|
else {
|
|
448
|
-
|
|
328
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
449
329
|
}
|
|
450
|
-
matches = placeHolderReg.exec(
|
|
330
|
+
matches = placeHolderReg.exec(str);
|
|
451
331
|
}
|
|
452
|
-
return
|
|
332
|
+
return newStr;
|
|
453
333
|
}
|
|
454
334
|
static checkServerUrl(servers) {
|
|
455
335
|
const errors = [];
|
|
456
336
|
let serverUrl;
|
|
457
337
|
try {
|
|
458
|
-
serverUrl = Utils.
|
|
338
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
459
339
|
}
|
|
460
340
|
catch (err) {
|
|
461
341
|
errors.push({
|
|
@@ -485,7 +365,8 @@ class Utils {
|
|
|
485
365
|
}
|
|
486
366
|
return errors;
|
|
487
367
|
}
|
|
488
|
-
static validateServer(spec,
|
|
368
|
+
static validateServer(spec, options) {
|
|
369
|
+
var _a;
|
|
489
370
|
const errors = [];
|
|
490
371
|
let hasTopLevelServers = false;
|
|
491
372
|
let hasPathLevelServers = false;
|
|
@@ -506,7 +387,7 @@ class Utils {
|
|
|
506
387
|
}
|
|
507
388
|
for (const method in methods) {
|
|
508
389
|
const operationObject = methods[method];
|
|
509
|
-
if (
|
|
390
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
510
391
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
511
392
|
hasOperationLevelServers = true;
|
|
512
393
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -549,6 +430,7 @@ class Utils {
|
|
|
549
430
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
550
431
|
}
|
|
551
432
|
if (isRequired && schema.default === undefined) {
|
|
433
|
+
parameter.isRequired = true;
|
|
552
434
|
requiredParams.push(parameter);
|
|
553
435
|
}
|
|
554
436
|
else {
|
|
@@ -593,7 +475,7 @@ class Utils {
|
|
|
593
475
|
param.value = schema.default;
|
|
594
476
|
}
|
|
595
477
|
}
|
|
596
|
-
static parseApiInfo(operationItem,
|
|
478
|
+
static parseApiInfo(operationItem, options) {
|
|
597
479
|
var _a, _b;
|
|
598
480
|
const requiredParams = [];
|
|
599
481
|
const optionalParams = [];
|
|
@@ -607,11 +489,12 @@ class Utils {
|
|
|
607
489
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
608
490
|
};
|
|
609
491
|
const schema = param.schema;
|
|
610
|
-
if (allowMultipleParameters && schema) {
|
|
492
|
+
if (options.allowMultipleParameters && schema) {
|
|
611
493
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
612
494
|
}
|
|
613
495
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
614
496
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
497
|
+
parameter.isRequired = true;
|
|
615
498
|
requiredParams.push(parameter);
|
|
616
499
|
}
|
|
617
500
|
else {
|
|
@@ -625,19 +508,13 @@ class Utils {
|
|
|
625
508
|
const requestJson = requestBody.content["application/json"];
|
|
626
509
|
if (Object.keys(requestJson).length !== 0) {
|
|
627
510
|
const schema = requestJson.schema;
|
|
628
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
511
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
629
512
|
requiredParams.push(...requiredP);
|
|
630
513
|
optionalParams.push(...optionalP);
|
|
631
514
|
}
|
|
632
515
|
}
|
|
633
516
|
const operationId = operationItem.operationId;
|
|
634
|
-
const parameters = [];
|
|
635
|
-
if (requiredParams.length !== 0) {
|
|
636
|
-
parameters.push(...requiredParams);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
parameters.push(optionalParams[0]);
|
|
640
|
-
}
|
|
517
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
641
518
|
const command = {
|
|
642
519
|
context: ["compose"],
|
|
643
520
|
type: "query",
|
|
@@ -646,105 +523,534 @@ class Utils {
|
|
|
646
523
|
parameters: parameters,
|
|
647
524
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
648
525
|
};
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
526
|
+
return command;
|
|
527
|
+
}
|
|
528
|
+
static format(str, ...args) {
|
|
529
|
+
let index = 0;
|
|
530
|
+
return str.replace(/%s/g, () => {
|
|
531
|
+
const arg = args[index++];
|
|
532
|
+
return arg !== undefined ? arg : "";
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
536
|
+
if (!authName) {
|
|
537
|
+
return "";
|
|
656
538
|
}
|
|
657
|
-
|
|
539
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
540
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
541
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
542
|
+
}
|
|
543
|
+
return safeRegistrationIdEnvName;
|
|
658
544
|
}
|
|
659
|
-
static
|
|
660
|
-
const
|
|
545
|
+
static getServerObject(spec, method, path) {
|
|
546
|
+
const pathObj = spec.paths[path];
|
|
547
|
+
const operationObject = pathObj[method];
|
|
548
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
549
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
550
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
551
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
552
|
+
return serverUrl;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Copyright (c) Microsoft Corporation.
|
|
557
|
+
class Validator {
|
|
558
|
+
listAPIs() {
|
|
559
|
+
var _a;
|
|
560
|
+
if (this.apiMap) {
|
|
561
|
+
return this.apiMap;
|
|
562
|
+
}
|
|
563
|
+
const paths = this.spec.paths;
|
|
661
564
|
const result = {};
|
|
662
565
|
for (const path in paths) {
|
|
663
566
|
const methods = paths[path];
|
|
664
567
|
for (const method in methods) {
|
|
665
|
-
|
|
666
|
-
if (
|
|
667
|
-
const
|
|
668
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
568
|
+
const operationObject = methods[method];
|
|
569
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
570
|
+
const validateResult = this.validateAPI(method, path);
|
|
571
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
572
|
+
operation: operationObject,
|
|
573
|
+
isValid: validateResult.isValid,
|
|
574
|
+
reason: validateResult.reason,
|
|
575
|
+
};
|
|
669
576
|
}
|
|
670
577
|
}
|
|
671
578
|
}
|
|
579
|
+
this.apiMap = result;
|
|
672
580
|
return result;
|
|
673
581
|
}
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
582
|
+
validateSpecVersion() {
|
|
583
|
+
const result = { errors: [], warnings: [] };
|
|
584
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
585
|
+
result.errors.push({
|
|
586
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
587
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
588
|
+
data: this.spec.openapi,
|
|
681
589
|
});
|
|
682
590
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
validateSpecServer() {
|
|
594
|
+
const result = { errors: [], warnings: [] };
|
|
595
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
596
|
+
result.errors.push(...serverErrors);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
validateSpecNoSupportAPI() {
|
|
600
|
+
const result = { errors: [], warnings: [] };
|
|
601
|
+
const apiMap = this.listAPIs();
|
|
602
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
603
|
+
if (validAPIs.length === 0) {
|
|
604
|
+
const data = [];
|
|
605
|
+
for (const key in apiMap) {
|
|
606
|
+
const { reason } = apiMap[key];
|
|
607
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
608
|
+
data.push(apiInvalidReason);
|
|
609
|
+
}
|
|
610
|
+
result.errors.push({
|
|
700
611
|
type: ErrorType.NoSupportedApi,
|
|
701
612
|
content: ConstantString.NoSupportedApi,
|
|
613
|
+
data,
|
|
702
614
|
});
|
|
703
615
|
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
validateSpecOperationId() {
|
|
619
|
+
const result = { errors: [], warnings: [] };
|
|
620
|
+
const apiMap = this.listAPIs();
|
|
704
621
|
// OperationId missing
|
|
705
622
|
const apisMissingOperationId = [];
|
|
706
623
|
for (const key in apiMap) {
|
|
707
|
-
const
|
|
708
|
-
if (!
|
|
624
|
+
const { operation } = apiMap[key];
|
|
625
|
+
if (!operation.operationId) {
|
|
709
626
|
apisMissingOperationId.push(key);
|
|
710
627
|
}
|
|
711
628
|
}
|
|
712
629
|
if (apisMissingOperationId.length > 0) {
|
|
713
|
-
warnings.push({
|
|
630
|
+
result.warnings.push({
|
|
714
631
|
type: WarningType.OperationIdMissing,
|
|
715
632
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
716
633
|
data: apisMissingOperationId,
|
|
717
634
|
});
|
|
718
635
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
validateMethodAndPath(method, path) {
|
|
639
|
+
const result = { isValid: true, reason: [] };
|
|
640
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
641
|
+
result.isValid = false;
|
|
642
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
const pathObj = this.spec.paths[path];
|
|
646
|
+
if (!pathObj || !pathObj[method]) {
|
|
647
|
+
result.isValid = false;
|
|
648
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
649
|
+
return result;
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
validateResponse(method, path) {
|
|
654
|
+
const result = { isValid: true, reason: [] };
|
|
655
|
+
const operationObject = this.spec.paths[path][method];
|
|
656
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
657
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
658
|
+
// only support response body only contains “application/json” content type
|
|
659
|
+
if (multipleMediaType) {
|
|
660
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
661
|
+
}
|
|
662
|
+
else if (Object.keys(json).length === 0) {
|
|
663
|
+
// response body should not be empty
|
|
664
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
validateServer(method, path) {
|
|
670
|
+
const result = { isValid: true, reason: [] };
|
|
671
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
672
|
+
if (!serverObj) {
|
|
673
|
+
// should contain server URL
|
|
674
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
722
675
|
}
|
|
723
|
-
else
|
|
724
|
-
|
|
676
|
+
else {
|
|
677
|
+
// server url should be absolute url with https protocol
|
|
678
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
679
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
validateAuth(method, path) {
|
|
684
|
+
const pathObj = this.spec.paths[path];
|
|
685
|
+
const operationObject = pathObj[method];
|
|
686
|
+
const securities = operationObject.security;
|
|
687
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
688
|
+
if (authSchemeArray.length === 0) {
|
|
689
|
+
return { isValid: true, reason: [] };
|
|
690
|
+
}
|
|
691
|
+
if (this.options.allowAPIKeyAuth ||
|
|
692
|
+
this.options.allowOauth2 ||
|
|
693
|
+
this.options.allowBearerTokenAuth) {
|
|
694
|
+
// Currently we don't support multiple auth in one operation
|
|
695
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
696
|
+
return {
|
|
697
|
+
isValid: false,
|
|
698
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
for (const auths of authSchemeArray) {
|
|
702
|
+
if (auths.length === 1) {
|
|
703
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
704
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
705
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
706
|
+
return { isValid: true, reason: [] };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
725
710
|
}
|
|
726
|
-
return {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
711
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
712
|
+
}
|
|
713
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
714
|
+
var _a;
|
|
715
|
+
const paramResult = {
|
|
716
|
+
requiredNum: 0,
|
|
717
|
+
optionalNum: 0,
|
|
718
|
+
isValid: true,
|
|
719
|
+
reason: [],
|
|
730
720
|
};
|
|
721
|
+
if (Object.keys(schema).length === 0) {
|
|
722
|
+
return paramResult;
|
|
723
|
+
}
|
|
724
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
725
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
726
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
727
|
+
paramResult.isValid = false;
|
|
728
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
729
|
+
return paramResult;
|
|
730
|
+
}
|
|
731
|
+
if (schema.type === "string" ||
|
|
732
|
+
schema.type === "integer" ||
|
|
733
|
+
schema.type === "boolean" ||
|
|
734
|
+
schema.type === "number") {
|
|
735
|
+
if (isRequiredWithoutDefault) {
|
|
736
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
else if (schema.type === "object") {
|
|
743
|
+
const { properties } = schema;
|
|
744
|
+
for (const property in properties) {
|
|
745
|
+
let isRequired = false;
|
|
746
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
747
|
+
isRequired = true;
|
|
748
|
+
}
|
|
749
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
750
|
+
paramResult.requiredNum += result.requiredNum;
|
|
751
|
+
paramResult.optionalNum += result.optionalNum;
|
|
752
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
753
|
+
paramResult.reason.push(...result.reason);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
758
|
+
paramResult.isValid = false;
|
|
759
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return paramResult;
|
|
731
763
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
764
|
+
checkParamSchema(paramObject) {
|
|
765
|
+
const paramResult = {
|
|
766
|
+
requiredNum: 0,
|
|
767
|
+
optionalNum: 0,
|
|
768
|
+
isValid: true,
|
|
769
|
+
reason: [],
|
|
770
|
+
};
|
|
771
|
+
if (!paramObject) {
|
|
772
|
+
return paramResult;
|
|
773
|
+
}
|
|
774
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
775
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
776
|
+
const param = paramObject[i];
|
|
777
|
+
const schema = param.schema;
|
|
778
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
779
|
+
paramResult.isValid = false;
|
|
780
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
784
|
+
if (isCopilot) {
|
|
785
|
+
if (isRequiredWithoutDefault) {
|
|
786
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
790
|
+
}
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
794
|
+
if (isRequiredWithoutDefault) {
|
|
795
|
+
paramResult.isValid = false;
|
|
796
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
797
|
+
}
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (schema.type !== "boolean" &&
|
|
801
|
+
schema.type !== "string" &&
|
|
802
|
+
schema.type !== "number" &&
|
|
803
|
+
schema.type !== "integer") {
|
|
804
|
+
if (isRequiredWithoutDefault) {
|
|
805
|
+
paramResult.isValid = false;
|
|
806
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (param.in === "query" || param.in === "path") {
|
|
811
|
+
if (isRequiredWithoutDefault) {
|
|
812
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return paramResult;
|
|
738
820
|
}
|
|
739
|
-
|
|
740
|
-
if (
|
|
741
|
-
|
|
821
|
+
hasNestedObjectInSchema(schema) {
|
|
822
|
+
if (schema.type === "object") {
|
|
823
|
+
for (const property in schema.properties) {
|
|
824
|
+
const nestedSchema = schema.properties[property];
|
|
825
|
+
if (nestedSchema.type === "object") {
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
742
829
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Copyright (c) Microsoft Corporation.
|
|
835
|
+
class CopilotValidator extends Validator {
|
|
836
|
+
constructor(spec, options) {
|
|
837
|
+
super();
|
|
838
|
+
this.projectType = ProjectType.Copilot;
|
|
839
|
+
this.options = options;
|
|
840
|
+
this.spec = spec;
|
|
841
|
+
}
|
|
842
|
+
validateSpec() {
|
|
843
|
+
const result = { errors: [], warnings: [] };
|
|
844
|
+
// validate spec version
|
|
845
|
+
let validationResult = this.validateSpecVersion();
|
|
846
|
+
result.errors.push(...validationResult.errors);
|
|
847
|
+
// validate spec server
|
|
848
|
+
validationResult = this.validateSpecServer();
|
|
849
|
+
result.errors.push(...validationResult.errors);
|
|
850
|
+
// validate no supported API
|
|
851
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
852
|
+
result.errors.push(...validationResult.errors);
|
|
853
|
+
// validate operationId missing
|
|
854
|
+
validationResult = this.validateSpecOperationId();
|
|
855
|
+
result.warnings.push(...validationResult.warnings);
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
validateAPI(method, path) {
|
|
859
|
+
const result = { isValid: true, reason: [] };
|
|
860
|
+
method = method.toLocaleLowerCase();
|
|
861
|
+
// validate method and path
|
|
862
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
863
|
+
if (!methodAndPathResult.isValid) {
|
|
864
|
+
return methodAndPathResult;
|
|
865
|
+
}
|
|
866
|
+
const operationObject = this.spec.paths[path][method];
|
|
867
|
+
// validate auth
|
|
868
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
869
|
+
result.reason.push(...authCheckResult.reason);
|
|
870
|
+
// validate operationId
|
|
871
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
872
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
873
|
+
}
|
|
874
|
+
// validate server
|
|
875
|
+
const validateServerResult = this.validateServer(method, path);
|
|
876
|
+
result.reason.push(...validateServerResult.reason);
|
|
877
|
+
// validate response
|
|
878
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
879
|
+
result.reason.push(...validateResponseResult.reason);
|
|
880
|
+
// validate requestBody
|
|
881
|
+
const requestBody = operationObject.requestBody;
|
|
882
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
883
|
+
if (requestJsonBody) {
|
|
884
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
885
|
+
if (requestBodySchema.type !== "object") {
|
|
886
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
887
|
+
}
|
|
888
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
889
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
890
|
+
}
|
|
891
|
+
// validate parameters
|
|
892
|
+
const paramObject = operationObject.parameters;
|
|
893
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
894
|
+
result.reason.push(...paramResult.reason);
|
|
895
|
+
if (result.reason.length > 0) {
|
|
896
|
+
result.isValid = false;
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Copyright (c) Microsoft Corporation.
|
|
903
|
+
class SMEValidator extends Validator {
|
|
904
|
+
constructor(spec, options) {
|
|
905
|
+
super();
|
|
906
|
+
this.projectType = ProjectType.SME;
|
|
907
|
+
this.options = options;
|
|
908
|
+
this.spec = spec;
|
|
909
|
+
}
|
|
910
|
+
validateSpec() {
|
|
911
|
+
const result = { errors: [], warnings: [] };
|
|
912
|
+
// validate spec version
|
|
913
|
+
let validationResult = this.validateSpecVersion();
|
|
914
|
+
result.errors.push(...validationResult.errors);
|
|
915
|
+
// validate spec server
|
|
916
|
+
validationResult = this.validateSpecServer();
|
|
917
|
+
result.errors.push(...validationResult.errors);
|
|
918
|
+
// validate no supported API
|
|
919
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
920
|
+
result.errors.push(...validationResult.errors);
|
|
921
|
+
// validate operationId missing
|
|
922
|
+
if (this.options.allowMissingId) {
|
|
923
|
+
validationResult = this.validateSpecOperationId();
|
|
924
|
+
result.warnings.push(...validationResult.warnings);
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
validateAPI(method, path) {
|
|
929
|
+
const result = { isValid: true, reason: [] };
|
|
930
|
+
method = method.toLocaleLowerCase();
|
|
931
|
+
// validate method and path
|
|
932
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
933
|
+
if (!methodAndPathResult.isValid) {
|
|
934
|
+
return methodAndPathResult;
|
|
935
|
+
}
|
|
936
|
+
const operationObject = this.spec.paths[path][method];
|
|
937
|
+
// validate auth
|
|
938
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
939
|
+
result.reason.push(...authCheckResult.reason);
|
|
940
|
+
// validate operationId
|
|
941
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
942
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
943
|
+
}
|
|
944
|
+
// validate server
|
|
945
|
+
const validateServerResult = this.validateServer(method, path);
|
|
946
|
+
result.reason.push(...validateServerResult.reason);
|
|
947
|
+
// validate response
|
|
948
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
949
|
+
result.reason.push(...validateResponseResult.reason);
|
|
950
|
+
let postBodyResult = {
|
|
951
|
+
requiredNum: 0,
|
|
952
|
+
optionalNum: 0,
|
|
953
|
+
isValid: true,
|
|
954
|
+
reason: [],
|
|
955
|
+
};
|
|
956
|
+
// validate requestBody
|
|
957
|
+
const requestBody = operationObject.requestBody;
|
|
958
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
959
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
960
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
961
|
+
}
|
|
962
|
+
if (requestJsonBody) {
|
|
963
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
964
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
965
|
+
result.reason.push(...postBodyResult.reason);
|
|
966
|
+
}
|
|
967
|
+
// validate parameters
|
|
968
|
+
const paramObject = operationObject.parameters;
|
|
969
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
970
|
+
result.reason.push(...paramResult.reason);
|
|
971
|
+
// validate total parameters count
|
|
972
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
973
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
974
|
+
result.reason.push(...paramCountResult.reason);
|
|
975
|
+
}
|
|
976
|
+
if (result.reason.length > 0) {
|
|
977
|
+
result.isValid = false;
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
}
|
|
981
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
982
|
+
const result = { isValid: true, reason: [] };
|
|
983
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
984
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
985
|
+
if (totalRequiredParams > 1) {
|
|
986
|
+
if (!this.options.allowMultipleParameters ||
|
|
987
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
988
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
else if (totalParams === 0) {
|
|
992
|
+
result.reason.push(ErrorType.NoParameter);
|
|
993
|
+
}
|
|
994
|
+
return result;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
998
|
+
|
|
999
|
+
// Copyright (c) Microsoft Corporation.
|
|
1000
|
+
class TeamsAIValidator extends Validator {
|
|
1001
|
+
constructor(spec, options) {
|
|
1002
|
+
super();
|
|
1003
|
+
this.projectType = ProjectType.TeamsAi;
|
|
1004
|
+
this.options = options;
|
|
1005
|
+
this.spec = spec;
|
|
1006
|
+
}
|
|
1007
|
+
validateSpec() {
|
|
1008
|
+
const result = { errors: [], warnings: [] };
|
|
1009
|
+
// validate spec server
|
|
1010
|
+
let validationResult = this.validateSpecServer();
|
|
1011
|
+
result.errors.push(...validationResult.errors);
|
|
1012
|
+
// validate no supported API
|
|
1013
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
1014
|
+
result.errors.push(...validationResult.errors);
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
1017
|
+
validateAPI(method, path) {
|
|
1018
|
+
const result = { isValid: true, reason: [] };
|
|
1019
|
+
method = method.toLocaleLowerCase();
|
|
1020
|
+
// validate method and path
|
|
1021
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
1022
|
+
if (!methodAndPathResult.isValid) {
|
|
1023
|
+
return methodAndPathResult;
|
|
1024
|
+
}
|
|
1025
|
+
const operationObject = this.spec.paths[path][method];
|
|
1026
|
+
// validate operationId
|
|
1027
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1028
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1029
|
+
}
|
|
1030
|
+
// validate server
|
|
1031
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1032
|
+
result.reason.push(...validateServerResult.reason);
|
|
1033
|
+
if (result.reason.length > 0) {
|
|
1034
|
+
result.isValid = false;
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
class ValidatorFactory {
|
|
1041
|
+
static create(spec, options) {
|
|
1042
|
+
var _a;
|
|
1043
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1044
|
+
switch (type) {
|
|
1045
|
+
case ProjectType.SME:
|
|
1046
|
+
return new SMEValidator(spec, options);
|
|
1047
|
+
case ProjectType.Copilot:
|
|
1048
|
+
return new CopilotValidator(spec, options);
|
|
1049
|
+
case ProjectType.TeamsAi:
|
|
1050
|
+
return new TeamsAIValidator(spec, options);
|
|
1051
|
+
default:
|
|
1052
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
746
1053
|
}
|
|
747
|
-
return safeRegistrationIdEnvName;
|
|
748
1054
|
}
|
|
749
1055
|
}
|
|
750
1056
|
|
|
@@ -764,7 +1070,13 @@ class SpecParser {
|
|
|
764
1070
|
allowSwagger: false,
|
|
765
1071
|
allowAPIKeyAuth: false,
|
|
766
1072
|
allowMultipleParameters: false,
|
|
1073
|
+
allowBearerTokenAuth: false,
|
|
767
1074
|
allowOauth2: false,
|
|
1075
|
+
allowMethods: ["get", "post"],
|
|
1076
|
+
allowConversationStarters: false,
|
|
1077
|
+
allowResponseSemantics: false,
|
|
1078
|
+
allowConfirmation: false,
|
|
1079
|
+
projectType: ProjectType.SME,
|
|
768
1080
|
};
|
|
769
1081
|
this.pathOrSpec = pathOrDoc;
|
|
770
1082
|
this.parser = new SwaggerParser();
|
|
@@ -780,11 +1092,7 @@ class SpecParser {
|
|
|
780
1092
|
try {
|
|
781
1093
|
try {
|
|
782
1094
|
yield this.loadSpec();
|
|
783
|
-
yield this.parser.validate(this.spec
|
|
784
|
-
validate: {
|
|
785
|
-
schema: false,
|
|
786
|
-
},
|
|
787
|
-
});
|
|
1095
|
+
yield this.parser.validate(this.spec);
|
|
788
1096
|
}
|
|
789
1097
|
catch (e) {
|
|
790
1098
|
return {
|
|
@@ -793,16 +1101,46 @@ class SpecParser {
|
|
|
793
1101
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
794
1102
|
};
|
|
795
1103
|
}
|
|
1104
|
+
const errors = [];
|
|
1105
|
+
const warnings = [];
|
|
796
1106
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
797
1107
|
return {
|
|
798
1108
|
status: ValidationStatus.Error,
|
|
799
1109
|
warnings: [],
|
|
800
1110
|
errors: [
|
|
801
|
-
{
|
|
1111
|
+
{
|
|
1112
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1113
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1114
|
+
},
|
|
802
1115
|
],
|
|
803
1116
|
};
|
|
804
1117
|
}
|
|
805
|
-
|
|
1118
|
+
// Remote reference not supported
|
|
1119
|
+
const refPaths = this.parser.$refs.paths();
|
|
1120
|
+
// refPaths [0] is the current spec file path
|
|
1121
|
+
if (refPaths.length > 1) {
|
|
1122
|
+
errors.push({
|
|
1123
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1124
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1125
|
+
data: refPaths,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const validator = this.getValidator(this.spec);
|
|
1129
|
+
const validationResult = validator.validateSpec();
|
|
1130
|
+
warnings.push(...validationResult.warnings);
|
|
1131
|
+
errors.push(...validationResult.errors);
|
|
1132
|
+
let status = ValidationStatus.Valid;
|
|
1133
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1134
|
+
status = ValidationStatus.Warning;
|
|
1135
|
+
}
|
|
1136
|
+
else if (errors.length > 0) {
|
|
1137
|
+
status = ValidationStatus.Error;
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
status: status,
|
|
1141
|
+
warnings: warnings,
|
|
1142
|
+
errors: errors,
|
|
1143
|
+
};
|
|
806
1144
|
}
|
|
807
1145
|
catch (err) {
|
|
808
1146
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -813,17 +1151,20 @@ class SpecParser {
|
|
|
813
1151
|
return __awaiter(this, void 0, void 0, function* () {
|
|
814
1152
|
try {
|
|
815
1153
|
yield this.loadSpec();
|
|
816
|
-
const apiMap = this.
|
|
1154
|
+
const apiMap = this.getAPIs(this.spec);
|
|
817
1155
|
const apiInfos = [];
|
|
818
1156
|
for (const key in apiMap) {
|
|
819
|
-
const
|
|
1157
|
+
const { operation, isValid } = apiMap[key];
|
|
1158
|
+
if (!isValid) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
820
1161
|
const [method, path] = key.split(" ");
|
|
821
|
-
const operationId =
|
|
1162
|
+
const operationId = operation.operationId;
|
|
822
1163
|
// In Browser environment, this api is by default not support api without operationId
|
|
823
1164
|
if (!operationId) {
|
|
824
1165
|
continue;
|
|
825
1166
|
}
|
|
826
|
-
const
|
|
1167
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
827
1168
|
const apiInfo = {
|
|
828
1169
|
method: method,
|
|
829
1170
|
path: path,
|
|
@@ -832,9 +1173,6 @@ class SpecParser {
|
|
|
832
1173
|
parameters: command.parameters,
|
|
833
1174
|
description: command.description,
|
|
834
1175
|
};
|
|
835
|
-
if (warning) {
|
|
836
|
-
apiInfo.warning = warning;
|
|
837
|
-
}
|
|
838
1176
|
apiInfos.push(apiInfo);
|
|
839
1177
|
}
|
|
840
1178
|
return apiInfos;
|
|
@@ -865,6 +1203,19 @@ class SpecParser {
|
|
|
865
1203
|
throw new Error("Method not implemented.");
|
|
866
1204
|
});
|
|
867
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1208
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1209
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1210
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1211
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1212
|
+
*/
|
|
1213
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1214
|
+
generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1215
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1216
|
+
throw new Error("Method not implemented.");
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
868
1219
|
/**
|
|
869
1220
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
870
1221
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
@@ -874,7 +1225,7 @@ class SpecParser {
|
|
|
874
1225
|
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
875
1226
|
*/
|
|
876
1227
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
877
|
-
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal
|
|
1228
|
+
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
878
1229
|
return __awaiter(this, void 0, void 0, function* () {
|
|
879
1230
|
throw new Error("Method not implemented.");
|
|
880
1231
|
});
|
|
@@ -891,13 +1242,18 @@ class SpecParser {
|
|
|
891
1242
|
}
|
|
892
1243
|
});
|
|
893
1244
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1245
|
+
getAPIs(spec) {
|
|
1246
|
+
const validator = this.getValidator(spec);
|
|
1247
|
+
const apiMap = validator.listAPIs();
|
|
1248
|
+
return apiMap;
|
|
1249
|
+
}
|
|
1250
|
+
getValidator(spec) {
|
|
1251
|
+
if (this.validator) {
|
|
1252
|
+
return this.validator;
|
|
897
1253
|
}
|
|
898
|
-
const
|
|
899
|
-
this.
|
|
900
|
-
return
|
|
1254
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1255
|
+
this.validator = validator;
|
|
1256
|
+
return validator;
|
|
901
1257
|
}
|
|
902
1258
|
}
|
|
903
1259
|
|
|
@@ -905,7 +1261,7 @@ class SpecParser {
|
|
|
905
1261
|
class AdaptiveCardGenerator {
|
|
906
1262
|
static generateAdaptiveCard(operationItem) {
|
|
907
1263
|
try {
|
|
908
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1264
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
909
1265
|
let cardBody = [];
|
|
910
1266
|
let schema = json.schema;
|
|
911
1267
|
let jsonPath = "$";
|