@microsoft/m365-spec-parser 0.1.1-alpha.cf377d39f.0 → 0.1.1-alpha.d292dc740.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 +668 -307
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +1158 -501
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +670 -307
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1169 -504
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/src/adaptiveCardWrapper.d.ts +2 -0
- package/dist/src/constants.d.ts +11 -3
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/interfaces.d.ts +110 -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 -5
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 {
|
|
@@ -65,7 +87,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
|
65
87
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
66
88
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
67
89
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
68
|
-
ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
90
|
+
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
69
91
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
70
92
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
71
93
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
@@ -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,14 @@ 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
|
-
ConstantString.RegistrationIdPostfix =
|
|
112
|
+
ConstantString.RegistrationIdPostfix = {
|
|
113
|
+
apiKey: "REGISTRATION_ID",
|
|
114
|
+
oauth2: "CONFIGURATION_ID",
|
|
115
|
+
http: "REGISTRATION_ID",
|
|
116
|
+
openIdConnect: "REGISTRATION_ID",
|
|
117
|
+
};
|
|
88
118
|
ConstantString.ResponseCodeFor20X = [
|
|
89
119
|
"200",
|
|
90
120
|
"201",
|
|
@@ -143,219 +173,52 @@ ConstantString.ShortDescriptionMaxLens = 80;
|
|
|
143
173
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
144
174
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
145
175
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
176
|
+
ConstantString.ConversationStarterMaxLens = 50;
|
|
146
177
|
ConstantString.CommandTitleMaxLens = 32;
|
|
147
|
-
ConstantString.ParameterTitleMaxLens = 32;
|
|
178
|
+
ConstantString.ParameterTitleMaxLens = 32;
|
|
179
|
+
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
180
|
+
ConstantString.DefaultPluginId = "plugin_1";
|
|
148
181
|
|
|
149
182
|
// Copyright (c) Microsoft Corporation.
|
|
150
183
|
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 {
|
|
184
|
+
static hasNestedObjectInSchema(schema) {
|
|
185
|
+
if (schema.type === "object") {
|
|
186
|
+
for (const property in schema.properties) {
|
|
187
|
+
const nestedSchema = schema.properties[property];
|
|
188
|
+
if (nestedSchema.type === "object") {
|
|
303
189
|
return true;
|
|
304
190
|
}
|
|
305
191
|
}
|
|
306
192
|
}
|
|
307
193
|
return false;
|
|
308
194
|
}
|
|
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;
|
|
195
|
+
static containMultipleMediaTypes(bodyObject) {
|
|
196
|
+
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
338
197
|
}
|
|
339
|
-
static
|
|
340
|
-
return
|
|
198
|
+
static isBearerTokenAuth(authScheme) {
|
|
199
|
+
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
341
200
|
}
|
|
342
|
-
static
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
|
|
201
|
+
static isAPIKeyAuth(authScheme) {
|
|
202
|
+
return authScheme.type === "apiKey";
|
|
203
|
+
}
|
|
204
|
+
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
205
|
+
return !!(authScheme.type === "oauth2" &&
|
|
206
|
+
authScheme.flows &&
|
|
207
|
+
authScheme.flows.authorizationCode);
|
|
346
208
|
}
|
|
347
209
|
static getAuthArray(securities, spec) {
|
|
348
210
|
var _a;
|
|
349
211
|
const result = [];
|
|
350
212
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
213
|
+
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
214
|
+
if (securitiesArr && securitySchemas) {
|
|
215
|
+
for (let i = 0; i < securitiesArr.length; i++) {
|
|
216
|
+
const security = securitiesArr[i];
|
|
354
217
|
const authArray = [];
|
|
355
218
|
for (const name in security) {
|
|
356
219
|
const auth = securitySchemas[name];
|
|
357
220
|
authArray.push({
|
|
358
|
-
|
|
221
|
+
authScheme: auth,
|
|
359
222
|
name: name,
|
|
360
223
|
});
|
|
361
224
|
}
|
|
@@ -367,24 +230,47 @@ class Utils {
|
|
|
367
230
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
368
231
|
return result;
|
|
369
232
|
}
|
|
233
|
+
static getAuthInfo(spec) {
|
|
234
|
+
let authInfo = undefined;
|
|
235
|
+
for (const url in spec.paths) {
|
|
236
|
+
for (const method in spec.paths[url]) {
|
|
237
|
+
const operation = spec.paths[url][method];
|
|
238
|
+
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
239
|
+
if (authArray && authArray.length > 0) {
|
|
240
|
+
const currentAuth = authArray[0][0];
|
|
241
|
+
if (!authInfo) {
|
|
242
|
+
authInfo = authArray[0][0];
|
|
243
|
+
}
|
|
244
|
+
else if (authInfo.name !== currentAuth.name) {
|
|
245
|
+
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return authInfo;
|
|
251
|
+
}
|
|
370
252
|
static updateFirstLetter(str) {
|
|
371
253
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
372
254
|
}
|
|
373
255
|
static getResponseJson(operationObject) {
|
|
374
256
|
var _a, _b;
|
|
375
257
|
let json = {};
|
|
258
|
+
let multipleMediaType = false;
|
|
376
259
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
377
260
|
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
261
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
262
|
+
multipleMediaType = false;
|
|
383
263
|
json = responseObject.content["application/json"];
|
|
384
|
-
|
|
264
|
+
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
265
|
+
multipleMediaType = true;
|
|
266
|
+
json = {};
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
385
271
|
}
|
|
386
272
|
}
|
|
387
|
-
return json;
|
|
273
|
+
return { json, multipleMediaType };
|
|
388
274
|
}
|
|
389
275
|
static convertPathToCamelCase(path) {
|
|
390
276
|
const pathSegments = path.split(/[./{]/);
|
|
@@ -404,10 +290,10 @@ class Utils {
|
|
|
404
290
|
return undefined;
|
|
405
291
|
}
|
|
406
292
|
}
|
|
407
|
-
static
|
|
293
|
+
static resolveEnv(str) {
|
|
408
294
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
409
|
-
let matches = placeHolderReg.exec(
|
|
410
|
-
let
|
|
295
|
+
let matches = placeHolderReg.exec(str);
|
|
296
|
+
let newStr = str;
|
|
411
297
|
while (matches != null) {
|
|
412
298
|
const envVar = matches[1];
|
|
413
299
|
const envVal = process.env[envVar];
|
|
@@ -415,17 +301,17 @@ class Utils {
|
|
|
415
301
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
416
302
|
}
|
|
417
303
|
else {
|
|
418
|
-
|
|
304
|
+
newStr = newStr.replace(matches[0], envVal);
|
|
419
305
|
}
|
|
420
|
-
matches = placeHolderReg.exec(
|
|
306
|
+
matches = placeHolderReg.exec(str);
|
|
421
307
|
}
|
|
422
|
-
return
|
|
308
|
+
return newStr;
|
|
423
309
|
}
|
|
424
310
|
static checkServerUrl(servers) {
|
|
425
311
|
const errors = [];
|
|
426
312
|
let serverUrl;
|
|
427
313
|
try {
|
|
428
|
-
serverUrl = Utils.
|
|
314
|
+
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
429
315
|
}
|
|
430
316
|
catch (err) {
|
|
431
317
|
errors.push({
|
|
@@ -455,7 +341,8 @@ class Utils {
|
|
|
455
341
|
}
|
|
456
342
|
return errors;
|
|
457
343
|
}
|
|
458
|
-
static validateServer(spec,
|
|
344
|
+
static validateServer(spec, options) {
|
|
345
|
+
var _a;
|
|
459
346
|
const errors = [];
|
|
460
347
|
let hasTopLevelServers = false;
|
|
461
348
|
let hasPathLevelServers = false;
|
|
@@ -476,7 +363,7 @@ class Utils {
|
|
|
476
363
|
}
|
|
477
364
|
for (const method in methods) {
|
|
478
365
|
const operationObject = methods[method];
|
|
479
|
-
if (
|
|
366
|
+
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
480
367
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
481
368
|
hasOperationLevelServers = true;
|
|
482
369
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
@@ -519,6 +406,7 @@ class Utils {
|
|
|
519
406
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
520
407
|
}
|
|
521
408
|
if (isRequired && schema.default === undefined) {
|
|
409
|
+
parameter.isRequired = true;
|
|
522
410
|
requiredParams.push(parameter);
|
|
523
411
|
}
|
|
524
412
|
else {
|
|
@@ -563,7 +451,7 @@ class Utils {
|
|
|
563
451
|
param.value = schema.default;
|
|
564
452
|
}
|
|
565
453
|
}
|
|
566
|
-
static parseApiInfo(operationItem,
|
|
454
|
+
static parseApiInfo(operationItem, options) {
|
|
567
455
|
var _a, _b;
|
|
568
456
|
const requiredParams = [];
|
|
569
457
|
const optionalParams = [];
|
|
@@ -577,11 +465,12 @@ class Utils {
|
|
|
577
465
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
578
466
|
};
|
|
579
467
|
const schema = param.schema;
|
|
580
|
-
if (allowMultipleParameters && schema) {
|
|
468
|
+
if (options.allowMultipleParameters && schema) {
|
|
581
469
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
582
470
|
}
|
|
583
471
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
584
472
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
473
|
+
parameter.isRequired = true;
|
|
585
474
|
requiredParams.push(parameter);
|
|
586
475
|
}
|
|
587
476
|
else {
|
|
@@ -595,19 +484,13 @@ class Utils {
|
|
|
595
484
|
const requestJson = requestBody.content["application/json"];
|
|
596
485
|
if (Object.keys(requestJson).length !== 0) {
|
|
597
486
|
const schema = requestJson.schema;
|
|
598
|
-
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", allowMultipleParameters, requestBody.required);
|
|
487
|
+
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
599
488
|
requiredParams.push(...requiredP);
|
|
600
489
|
optionalParams.push(...optionalP);
|
|
601
490
|
}
|
|
602
491
|
}
|
|
603
492
|
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
|
-
}
|
|
493
|
+
const parameters = [...requiredParams, ...optionalParams];
|
|
611
494
|
const command = {
|
|
612
495
|
context: ["compose"],
|
|
613
496
|
type: "query",
|
|
@@ -616,105 +499,534 @@ class Utils {
|
|
|
616
499
|
parameters: parameters,
|
|
617
500
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
618
501
|
};
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
502
|
+
return command;
|
|
503
|
+
}
|
|
504
|
+
static format(str, ...args) {
|
|
505
|
+
let index = 0;
|
|
506
|
+
return str.replace(/%s/g, () => {
|
|
507
|
+
const arg = args[index++];
|
|
508
|
+
return arg !== undefined ? arg : "";
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
static getSafeRegistrationIdEnvName(authName) {
|
|
512
|
+
if (!authName) {
|
|
513
|
+
return "";
|
|
626
514
|
}
|
|
627
|
-
|
|
515
|
+
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
516
|
+
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
517
|
+
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
518
|
+
}
|
|
519
|
+
return safeRegistrationIdEnvName;
|
|
628
520
|
}
|
|
629
|
-
static
|
|
630
|
-
const
|
|
521
|
+
static getServerObject(spec, method, path) {
|
|
522
|
+
const pathObj = spec.paths[path];
|
|
523
|
+
const operationObject = pathObj[method];
|
|
524
|
+
const rootServer = spec.servers && spec.servers[0];
|
|
525
|
+
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
526
|
+
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
527
|
+
const serverUrl = operationServer || methodServer || rootServer;
|
|
528
|
+
return serverUrl;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Copyright (c) Microsoft Corporation.
|
|
533
|
+
class Validator {
|
|
534
|
+
listAPIs() {
|
|
535
|
+
var _a;
|
|
536
|
+
if (this.apiMap) {
|
|
537
|
+
return this.apiMap;
|
|
538
|
+
}
|
|
539
|
+
const paths = this.spec.paths;
|
|
631
540
|
const result = {};
|
|
632
541
|
for (const path in paths) {
|
|
633
542
|
const methods = paths[path];
|
|
634
543
|
for (const method in methods) {
|
|
635
|
-
|
|
636
|
-
if (
|
|
637
|
-
const
|
|
638
|
-
result[`${method.toUpperCase()} ${path}`] =
|
|
544
|
+
const operationObject = methods[method];
|
|
545
|
+
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
546
|
+
const validateResult = this.validateAPI(method, path);
|
|
547
|
+
result[`${method.toUpperCase()} ${path}`] = {
|
|
548
|
+
operation: operationObject,
|
|
549
|
+
isValid: validateResult.isValid,
|
|
550
|
+
reason: validateResult.reason,
|
|
551
|
+
};
|
|
639
552
|
}
|
|
640
553
|
}
|
|
641
554
|
}
|
|
555
|
+
this.apiMap = result;
|
|
642
556
|
return result;
|
|
643
557
|
}
|
|
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,
|
|
558
|
+
validateSpecVersion() {
|
|
559
|
+
const result = { errors: [], warnings: [] };
|
|
560
|
+
if (this.spec.openapi >= "3.1.0") {
|
|
561
|
+
result.errors.push({
|
|
562
|
+
type: ErrorType.SpecVersionNotSupported,
|
|
563
|
+
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
564
|
+
data: this.spec.openapi,
|
|
664
565
|
});
|
|
665
566
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
validateSpecServer() {
|
|
570
|
+
const result = { errors: [], warnings: [] };
|
|
571
|
+
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
572
|
+
result.errors.push(...serverErrors);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
validateSpecNoSupportAPI() {
|
|
576
|
+
const result = { errors: [], warnings: [] };
|
|
577
|
+
const apiMap = this.listAPIs();
|
|
578
|
+
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
579
|
+
if (validAPIs.length === 0) {
|
|
580
|
+
const data = [];
|
|
581
|
+
for (const key in apiMap) {
|
|
582
|
+
const { reason } = apiMap[key];
|
|
583
|
+
const apiInvalidReason = { api: key, reason: reason };
|
|
584
|
+
data.push(apiInvalidReason);
|
|
585
|
+
}
|
|
586
|
+
result.errors.push({
|
|
670
587
|
type: ErrorType.NoSupportedApi,
|
|
671
588
|
content: ConstantString.NoSupportedApi,
|
|
589
|
+
data,
|
|
672
590
|
});
|
|
673
591
|
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
validateSpecOperationId() {
|
|
595
|
+
const result = { errors: [], warnings: [] };
|
|
596
|
+
const apiMap = this.listAPIs();
|
|
674
597
|
// OperationId missing
|
|
675
598
|
const apisMissingOperationId = [];
|
|
676
599
|
for (const key in apiMap) {
|
|
677
|
-
const
|
|
678
|
-
if (!
|
|
600
|
+
const { operation } = apiMap[key];
|
|
601
|
+
if (!operation.operationId) {
|
|
679
602
|
apisMissingOperationId.push(key);
|
|
680
603
|
}
|
|
681
604
|
}
|
|
682
605
|
if (apisMissingOperationId.length > 0) {
|
|
683
|
-
warnings.push({
|
|
606
|
+
result.warnings.push({
|
|
684
607
|
type: WarningType.OperationIdMissing,
|
|
685
608
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
686
609
|
data: apisMissingOperationId,
|
|
687
610
|
});
|
|
688
611
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
validateMethodAndPath(method, path) {
|
|
615
|
+
const result = { isValid: true, reason: [] };
|
|
616
|
+
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
617
|
+
result.isValid = false;
|
|
618
|
+
result.reason.push(ErrorType.MethodNotAllowed);
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
const pathObj = this.spec.paths[path];
|
|
622
|
+
if (!pathObj || !pathObj[method]) {
|
|
623
|
+
result.isValid = false;
|
|
624
|
+
result.reason.push(ErrorType.UrlPathNotExist);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
validateResponse(method, path) {
|
|
630
|
+
const result = { isValid: true, reason: [] };
|
|
631
|
+
const operationObject = this.spec.paths[path][method];
|
|
632
|
+
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
633
|
+
if (this.options.projectType === ProjectType.SME) {
|
|
634
|
+
// only support response body only contains “application/json” content type
|
|
635
|
+
if (multipleMediaType) {
|
|
636
|
+
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
637
|
+
}
|
|
638
|
+
else if (Object.keys(json).length === 0) {
|
|
639
|
+
// response body should not be empty
|
|
640
|
+
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
validateServer(method, path) {
|
|
646
|
+
const result = { isValid: true, reason: [] };
|
|
647
|
+
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
648
|
+
if (!serverObj) {
|
|
649
|
+
// should contain server URL
|
|
650
|
+
result.reason.push(ErrorType.NoServerInformation);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
// server url should be absolute url with https protocol
|
|
654
|
+
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
655
|
+
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
656
|
+
}
|
|
657
|
+
return result;
|
|
658
|
+
}
|
|
659
|
+
validateAuth(method, path) {
|
|
660
|
+
const pathObj = this.spec.paths[path];
|
|
661
|
+
const operationObject = pathObj[method];
|
|
662
|
+
const securities = operationObject.security;
|
|
663
|
+
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
664
|
+
if (authSchemeArray.length === 0) {
|
|
665
|
+
return { isValid: true, reason: [] };
|
|
666
|
+
}
|
|
667
|
+
if (this.options.allowAPIKeyAuth ||
|
|
668
|
+
this.options.allowOauth2 ||
|
|
669
|
+
this.options.allowBearerTokenAuth) {
|
|
670
|
+
// Currently we don't support multiple auth in one operation
|
|
671
|
+
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
672
|
+
return {
|
|
673
|
+
isValid: false,
|
|
674
|
+
reason: [ErrorType.MultipleAuthNotSupported],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
for (const auths of authSchemeArray) {
|
|
678
|
+
if (auths.length === 1) {
|
|
679
|
+
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
680
|
+
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
681
|
+
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
682
|
+
return { isValid: true, reason: [] };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
688
|
+
}
|
|
689
|
+
checkPostBodySchema(schema, isRequired = false) {
|
|
690
|
+
var _a;
|
|
691
|
+
const paramResult = {
|
|
692
|
+
requiredNum: 0,
|
|
693
|
+
optionalNum: 0,
|
|
694
|
+
isValid: true,
|
|
695
|
+
reason: [],
|
|
696
|
+
};
|
|
697
|
+
if (Object.keys(schema).length === 0) {
|
|
698
|
+
return paramResult;
|
|
692
699
|
}
|
|
693
|
-
|
|
694
|
-
|
|
700
|
+
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
701
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
702
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
703
|
+
paramResult.isValid = false;
|
|
704
|
+
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
705
|
+
return paramResult;
|
|
695
706
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
707
|
+
if (schema.type === "string" ||
|
|
708
|
+
schema.type === "integer" ||
|
|
709
|
+
schema.type === "boolean" ||
|
|
710
|
+
schema.type === "number") {
|
|
711
|
+
if (isRequiredWithoutDefault) {
|
|
712
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else if (schema.type === "object") {
|
|
719
|
+
const { properties } = schema;
|
|
720
|
+
for (const property in properties) {
|
|
721
|
+
let isRequired = false;
|
|
722
|
+
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
723
|
+
isRequired = true;
|
|
724
|
+
}
|
|
725
|
+
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
726
|
+
paramResult.requiredNum += result.requiredNum;
|
|
727
|
+
paramResult.optionalNum += result.optionalNum;
|
|
728
|
+
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
729
|
+
paramResult.reason.push(...result.reason);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
if (isRequiredWithoutDefault && !isCopilot) {
|
|
734
|
+
paramResult.isValid = false;
|
|
735
|
+
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return paramResult;
|
|
739
|
+
}
|
|
740
|
+
checkParamSchema(paramObject) {
|
|
741
|
+
const paramResult = {
|
|
742
|
+
requiredNum: 0,
|
|
743
|
+
optionalNum: 0,
|
|
744
|
+
isValid: true,
|
|
745
|
+
reason: [],
|
|
700
746
|
};
|
|
747
|
+
if (!paramObject) {
|
|
748
|
+
return paramResult;
|
|
749
|
+
}
|
|
750
|
+
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
751
|
+
for (let i = 0; i < paramObject.length; i++) {
|
|
752
|
+
const param = paramObject[i];
|
|
753
|
+
const schema = param.schema;
|
|
754
|
+
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
755
|
+
paramResult.isValid = false;
|
|
756
|
+
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
760
|
+
if (isCopilot) {
|
|
761
|
+
if (isRequiredWithoutDefault) {
|
|
762
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
766
|
+
}
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
770
|
+
if (isRequiredWithoutDefault) {
|
|
771
|
+
paramResult.isValid = false;
|
|
772
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (schema.type !== "boolean" &&
|
|
777
|
+
schema.type !== "string" &&
|
|
778
|
+
schema.type !== "number" &&
|
|
779
|
+
schema.type !== "integer") {
|
|
780
|
+
if (isRequiredWithoutDefault) {
|
|
781
|
+
paramResult.isValid = false;
|
|
782
|
+
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
783
|
+
}
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (param.in === "query" || param.in === "path") {
|
|
787
|
+
if (isRequiredWithoutDefault) {
|
|
788
|
+
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return paramResult;
|
|
701
796
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
797
|
+
hasNestedObjectInSchema(schema) {
|
|
798
|
+
if (schema.type === "object") {
|
|
799
|
+
for (const property in schema.properties) {
|
|
800
|
+
const nestedSchema = schema.properties[property];
|
|
801
|
+
if (nestedSchema.type === "object") {
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return false;
|
|
708
807
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Copyright (c) Microsoft Corporation.
|
|
811
|
+
class CopilotValidator extends Validator {
|
|
812
|
+
constructor(spec, options) {
|
|
813
|
+
super();
|
|
814
|
+
this.projectType = ProjectType.Copilot;
|
|
815
|
+
this.options = options;
|
|
816
|
+
this.spec = spec;
|
|
817
|
+
}
|
|
818
|
+
validateSpec() {
|
|
819
|
+
const result = { errors: [], warnings: [] };
|
|
820
|
+
// validate spec version
|
|
821
|
+
let validationResult = this.validateSpecVersion();
|
|
822
|
+
result.errors.push(...validationResult.errors);
|
|
823
|
+
// validate spec server
|
|
824
|
+
validationResult = this.validateSpecServer();
|
|
825
|
+
result.errors.push(...validationResult.errors);
|
|
826
|
+
// validate no supported API
|
|
827
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
828
|
+
result.errors.push(...validationResult.errors);
|
|
829
|
+
// validate operationId missing
|
|
830
|
+
validationResult = this.validateSpecOperationId();
|
|
831
|
+
result.warnings.push(...validationResult.warnings);
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
validateAPI(method, path) {
|
|
835
|
+
const result = { isValid: true, reason: [] };
|
|
836
|
+
method = method.toLocaleLowerCase();
|
|
837
|
+
// validate method and path
|
|
838
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
839
|
+
if (!methodAndPathResult.isValid) {
|
|
840
|
+
return methodAndPathResult;
|
|
712
841
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
842
|
+
const operationObject = this.spec.paths[path][method];
|
|
843
|
+
// validate auth
|
|
844
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
845
|
+
result.reason.push(...authCheckResult.reason);
|
|
846
|
+
// validate operationId
|
|
847
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
848
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
849
|
+
}
|
|
850
|
+
// validate server
|
|
851
|
+
const validateServerResult = this.validateServer(method, path);
|
|
852
|
+
result.reason.push(...validateServerResult.reason);
|
|
853
|
+
// validate response
|
|
854
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
855
|
+
result.reason.push(...validateResponseResult.reason);
|
|
856
|
+
// validate requestBody
|
|
857
|
+
const requestBody = operationObject.requestBody;
|
|
858
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
859
|
+
if (requestJsonBody) {
|
|
860
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
861
|
+
if (requestBodySchema.type !== "object") {
|
|
862
|
+
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
863
|
+
}
|
|
864
|
+
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
865
|
+
result.reason.push(...requestBodyParamResult.reason);
|
|
866
|
+
}
|
|
867
|
+
// validate parameters
|
|
868
|
+
const paramObject = operationObject.parameters;
|
|
869
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
870
|
+
result.reason.push(...paramResult.reason);
|
|
871
|
+
if (result.reason.length > 0) {
|
|
872
|
+
result.isValid = false;
|
|
873
|
+
}
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Copyright (c) Microsoft Corporation.
|
|
879
|
+
class SMEValidator extends Validator {
|
|
880
|
+
constructor(spec, options) {
|
|
881
|
+
super();
|
|
882
|
+
this.projectType = ProjectType.SME;
|
|
883
|
+
this.options = options;
|
|
884
|
+
this.spec = spec;
|
|
885
|
+
}
|
|
886
|
+
validateSpec() {
|
|
887
|
+
const result = { errors: [], warnings: [] };
|
|
888
|
+
// validate spec version
|
|
889
|
+
let validationResult = this.validateSpecVersion();
|
|
890
|
+
result.errors.push(...validationResult.errors);
|
|
891
|
+
// validate spec server
|
|
892
|
+
validationResult = this.validateSpecServer();
|
|
893
|
+
result.errors.push(...validationResult.errors);
|
|
894
|
+
// validate no supported API
|
|
895
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
896
|
+
result.errors.push(...validationResult.errors);
|
|
897
|
+
// validate operationId missing
|
|
898
|
+
if (this.options.allowMissingId) {
|
|
899
|
+
validationResult = this.validateSpecOperationId();
|
|
900
|
+
result.warnings.push(...validationResult.warnings);
|
|
901
|
+
}
|
|
902
|
+
return result;
|
|
903
|
+
}
|
|
904
|
+
validateAPI(method, path) {
|
|
905
|
+
const result = { isValid: true, reason: [] };
|
|
906
|
+
method = method.toLocaleLowerCase();
|
|
907
|
+
// validate method and path
|
|
908
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
909
|
+
if (!methodAndPathResult.isValid) {
|
|
910
|
+
return methodAndPathResult;
|
|
911
|
+
}
|
|
912
|
+
const operationObject = this.spec.paths[path][method];
|
|
913
|
+
// validate auth
|
|
914
|
+
const authCheckResult = this.validateAuth(method, path);
|
|
915
|
+
result.reason.push(...authCheckResult.reason);
|
|
916
|
+
// validate operationId
|
|
917
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
918
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
919
|
+
}
|
|
920
|
+
// validate server
|
|
921
|
+
const validateServerResult = this.validateServer(method, path);
|
|
922
|
+
result.reason.push(...validateServerResult.reason);
|
|
923
|
+
// validate response
|
|
924
|
+
const validateResponseResult = this.validateResponse(method, path);
|
|
925
|
+
result.reason.push(...validateResponseResult.reason);
|
|
926
|
+
let postBodyResult = {
|
|
927
|
+
requiredNum: 0,
|
|
928
|
+
optionalNum: 0,
|
|
929
|
+
isValid: true,
|
|
930
|
+
reason: [],
|
|
931
|
+
};
|
|
932
|
+
// validate requestBody
|
|
933
|
+
const requestBody = operationObject.requestBody;
|
|
934
|
+
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
935
|
+
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
936
|
+
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
937
|
+
}
|
|
938
|
+
if (requestJsonBody) {
|
|
939
|
+
const requestBodySchema = requestJsonBody.schema;
|
|
940
|
+
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
941
|
+
result.reason.push(...postBodyResult.reason);
|
|
942
|
+
}
|
|
943
|
+
// validate parameters
|
|
944
|
+
const paramObject = operationObject.parameters;
|
|
945
|
+
const paramResult = this.checkParamSchema(paramObject);
|
|
946
|
+
result.reason.push(...paramResult.reason);
|
|
947
|
+
// validate total parameters count
|
|
948
|
+
if (paramResult.isValid && postBodyResult.isValid) {
|
|
949
|
+
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
950
|
+
result.reason.push(...paramCountResult.reason);
|
|
951
|
+
}
|
|
952
|
+
if (result.reason.length > 0) {
|
|
953
|
+
result.isValid = false;
|
|
954
|
+
}
|
|
955
|
+
return result;
|
|
956
|
+
}
|
|
957
|
+
validateParamCount(postBodyResult, paramResult) {
|
|
958
|
+
const result = { isValid: true, reason: [] };
|
|
959
|
+
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
960
|
+
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
961
|
+
if (totalRequiredParams > 1) {
|
|
962
|
+
if (!this.options.allowMultipleParameters ||
|
|
963
|
+
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
964
|
+
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
else if (totalParams === 0) {
|
|
968
|
+
result.reason.push(ErrorType.NoParameter);
|
|
969
|
+
}
|
|
970
|
+
return result;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
974
|
+
|
|
975
|
+
// Copyright (c) Microsoft Corporation.
|
|
976
|
+
class TeamsAIValidator extends Validator {
|
|
977
|
+
constructor(spec, options) {
|
|
978
|
+
super();
|
|
979
|
+
this.projectType = ProjectType.TeamsAi;
|
|
980
|
+
this.options = options;
|
|
981
|
+
this.spec = spec;
|
|
982
|
+
}
|
|
983
|
+
validateSpec() {
|
|
984
|
+
const result = { errors: [], warnings: [] };
|
|
985
|
+
// validate spec server
|
|
986
|
+
let validationResult = this.validateSpecServer();
|
|
987
|
+
result.errors.push(...validationResult.errors);
|
|
988
|
+
// validate no supported API
|
|
989
|
+
validationResult = this.validateSpecNoSupportAPI();
|
|
990
|
+
result.errors.push(...validationResult.errors);
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
validateAPI(method, path) {
|
|
994
|
+
const result = { isValid: true, reason: [] };
|
|
995
|
+
method = method.toLocaleLowerCase();
|
|
996
|
+
// validate method and path
|
|
997
|
+
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
998
|
+
if (!methodAndPathResult.isValid) {
|
|
999
|
+
return methodAndPathResult;
|
|
1000
|
+
}
|
|
1001
|
+
const operationObject = this.spec.paths[path][method];
|
|
1002
|
+
// validate operationId
|
|
1003
|
+
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
1004
|
+
result.reason.push(ErrorType.MissingOperationId);
|
|
1005
|
+
}
|
|
1006
|
+
// validate server
|
|
1007
|
+
const validateServerResult = this.validateServer(method, path);
|
|
1008
|
+
result.reason.push(...validateServerResult.reason);
|
|
1009
|
+
if (result.reason.length > 0) {
|
|
1010
|
+
result.isValid = false;
|
|
1011
|
+
}
|
|
1012
|
+
return result;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
class ValidatorFactory {
|
|
1017
|
+
static create(spec, options) {
|
|
1018
|
+
var _a;
|
|
1019
|
+
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
1020
|
+
switch (type) {
|
|
1021
|
+
case ProjectType.SME:
|
|
1022
|
+
return new SMEValidator(spec, options);
|
|
1023
|
+
case ProjectType.Copilot:
|
|
1024
|
+
return new CopilotValidator(spec, options);
|
|
1025
|
+
case ProjectType.TeamsAi:
|
|
1026
|
+
return new TeamsAIValidator(spec, options);
|
|
1027
|
+
default:
|
|
1028
|
+
throw new Error(`Invalid project type: ${type}`);
|
|
716
1029
|
}
|
|
717
|
-
return safeRegistrationIdEnvName;
|
|
718
1030
|
}
|
|
719
1031
|
}
|
|
720
1032
|
|
|
@@ -734,7 +1046,14 @@ class SpecParser {
|
|
|
734
1046
|
allowSwagger: false,
|
|
735
1047
|
allowAPIKeyAuth: false,
|
|
736
1048
|
allowMultipleParameters: false,
|
|
1049
|
+
allowBearerTokenAuth: false,
|
|
737
1050
|
allowOauth2: false,
|
|
1051
|
+
allowMethods: ["get", "post"],
|
|
1052
|
+
allowConversationStarters: false,
|
|
1053
|
+
allowResponseSemantics: false,
|
|
1054
|
+
allowConfirmation: false,
|
|
1055
|
+
projectType: ProjectType.SME,
|
|
1056
|
+
isGptPlugin: false,
|
|
738
1057
|
};
|
|
739
1058
|
this.pathOrSpec = pathOrDoc;
|
|
740
1059
|
this.parser = new SwaggerParser();
|
|
@@ -749,11 +1068,7 @@ class SpecParser {
|
|
|
749
1068
|
try {
|
|
750
1069
|
try {
|
|
751
1070
|
await this.loadSpec();
|
|
752
|
-
await this.parser.validate(this.spec
|
|
753
|
-
validate: {
|
|
754
|
-
schema: false,
|
|
755
|
-
},
|
|
756
|
-
});
|
|
1071
|
+
await this.parser.validate(this.spec);
|
|
757
1072
|
}
|
|
758
1073
|
catch (e) {
|
|
759
1074
|
return {
|
|
@@ -762,16 +1077,46 @@ class SpecParser {
|
|
|
762
1077
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
763
1078
|
};
|
|
764
1079
|
}
|
|
1080
|
+
const errors = [];
|
|
1081
|
+
const warnings = [];
|
|
765
1082
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
766
1083
|
return {
|
|
767
1084
|
status: ValidationStatus.Error,
|
|
768
1085
|
warnings: [],
|
|
769
1086
|
errors: [
|
|
770
|
-
{
|
|
1087
|
+
{
|
|
1088
|
+
type: ErrorType.SwaggerNotSupported,
|
|
1089
|
+
content: ConstantString.SwaggerNotSupported,
|
|
1090
|
+
},
|
|
771
1091
|
],
|
|
772
1092
|
};
|
|
773
1093
|
}
|
|
774
|
-
|
|
1094
|
+
// Remote reference not supported
|
|
1095
|
+
const refPaths = this.parser.$refs.paths();
|
|
1096
|
+
// refPaths [0] is the current spec file path
|
|
1097
|
+
if (refPaths.length > 1) {
|
|
1098
|
+
errors.push({
|
|
1099
|
+
type: ErrorType.RemoteRefNotSupported,
|
|
1100
|
+
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
1101
|
+
data: refPaths,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
const validator = this.getValidator(this.spec);
|
|
1105
|
+
const validationResult = validator.validateSpec();
|
|
1106
|
+
warnings.push(...validationResult.warnings);
|
|
1107
|
+
errors.push(...validationResult.errors);
|
|
1108
|
+
let status = ValidationStatus.Valid;
|
|
1109
|
+
if (warnings.length > 0 && errors.length === 0) {
|
|
1110
|
+
status = ValidationStatus.Warning;
|
|
1111
|
+
}
|
|
1112
|
+
else if (errors.length > 0) {
|
|
1113
|
+
status = ValidationStatus.Error;
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
status: status,
|
|
1117
|
+
warnings: warnings,
|
|
1118
|
+
errors: errors,
|
|
1119
|
+
};
|
|
775
1120
|
}
|
|
776
1121
|
catch (err) {
|
|
777
1122
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
@@ -780,17 +1125,20 @@ class SpecParser {
|
|
|
780
1125
|
async listSupportedAPIInfo() {
|
|
781
1126
|
try {
|
|
782
1127
|
await this.loadSpec();
|
|
783
|
-
const apiMap = this.
|
|
1128
|
+
const apiMap = this.getAPIs(this.spec);
|
|
784
1129
|
const apiInfos = [];
|
|
785
1130
|
for (const key in apiMap) {
|
|
786
|
-
const
|
|
1131
|
+
const { operation, isValid } = apiMap[key];
|
|
1132
|
+
if (!isValid) {
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
787
1135
|
const [method, path] = key.split(" ");
|
|
788
|
-
const operationId =
|
|
1136
|
+
const operationId = operation.operationId;
|
|
789
1137
|
// In Browser environment, this api is by default not support api without operationId
|
|
790
1138
|
if (!operationId) {
|
|
791
1139
|
continue;
|
|
792
1140
|
}
|
|
793
|
-
const
|
|
1141
|
+
const command = Utils.parseApiInfo(operation, this.options);
|
|
794
1142
|
const apiInfo = {
|
|
795
1143
|
method: method,
|
|
796
1144
|
path: path,
|
|
@@ -799,9 +1147,6 @@ class SpecParser {
|
|
|
799
1147
|
parameters: command.parameters,
|
|
800
1148
|
description: command.description,
|
|
801
1149
|
};
|
|
802
|
-
if (warning) {
|
|
803
|
-
apiInfo.warning = warning;
|
|
804
|
-
}
|
|
805
1150
|
apiInfos.push(apiInfo);
|
|
806
1151
|
}
|
|
807
1152
|
return apiInfos;
|
|
@@ -827,6 +1172,17 @@ class SpecParser {
|
|
|
827
1172
|
async getFilteredSpecs(filter, signal) {
|
|
828
1173
|
throw new Error("Method not implemented.");
|
|
829
1174
|
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
1177
|
+
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
1178
|
+
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
1179
|
+
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
1180
|
+
* @param pluginFilePath File path of the api plugin file to generate.
|
|
1181
|
+
*/
|
|
1182
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1183
|
+
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
1184
|
+
throw new Error("Method not implemented.");
|
|
1185
|
+
}
|
|
830
1186
|
/**
|
|
831
1187
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
832
1188
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
@@ -836,7 +1192,7 @@ class SpecParser {
|
|
|
836
1192
|
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
837
1193
|
*/
|
|
838
1194
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
839
|
-
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal
|
|
1195
|
+
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
840
1196
|
throw new Error("Method not implemented.");
|
|
841
1197
|
}
|
|
842
1198
|
async loadSpec() {
|
|
@@ -849,13 +1205,18 @@ class SpecParser {
|
|
|
849
1205
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
850
1206
|
}
|
|
851
1207
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1208
|
+
getAPIs(spec) {
|
|
1209
|
+
const validator = this.getValidator(spec);
|
|
1210
|
+
const apiMap = validator.listAPIs();
|
|
1211
|
+
return apiMap;
|
|
1212
|
+
}
|
|
1213
|
+
getValidator(spec) {
|
|
1214
|
+
if (this.validator) {
|
|
1215
|
+
return this.validator;
|
|
855
1216
|
}
|
|
856
|
-
const
|
|
857
|
-
this.
|
|
858
|
-
return
|
|
1217
|
+
const validator = ValidatorFactory.create(spec, this.options);
|
|
1218
|
+
this.validator = validator;
|
|
1219
|
+
return validator;
|
|
859
1220
|
}
|
|
860
1221
|
}
|
|
861
1222
|
|
|
@@ -863,7 +1224,7 @@ class SpecParser {
|
|
|
863
1224
|
class AdaptiveCardGenerator {
|
|
864
1225
|
static generateAdaptiveCard(operationItem) {
|
|
865
1226
|
try {
|
|
866
|
-
const json = Utils.getResponseJson(operationItem);
|
|
1227
|
+
const { json } = Utils.getResponseJson(operationItem);
|
|
867
1228
|
let cardBody = [];
|
|
868
1229
|
let schema = json.schema;
|
|
869
1230
|
let jsonPath = "$";
|