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