├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── assembly └── assembly.xml ├── bin └── startup.cmd ├── java └── com │ └── lyl │ └── plugin │ ├── EnvArgs.java │ ├── JmxGenerator.java │ ├── generate │ ├── ExampleGenerator.java │ ├── MyDefaultGenerator.java │ └── XmlExampleGenerator.java │ ├── model │ ├── ParamNode.java │ ├── RequestNode.java │ ├── TagNode.java │ └── VariableNode.java │ ├── parse │ ├── AuthParser.java │ └── SwaggerParser.java │ ├── utils │ └── ModelUtils.java │ └── vo │ └── TemplateParamVO.java └── resources └── templates └── template.jmx /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .project 3 | .classpath 4 | /.evosuite/ 5 | /.settings/ 6 | /.idea/ 7 | /target/ 8 | *.class 9 | *.log 10 | *.bak 11 | *.tmp 12 | *.swp 13 | *.orig 14 | *.iml 15 | out 16 | gen 17 | target 18 | logs 19 | LOG_PATH_IS_UNDEFINEDdaily 20 | LOG_PATH_IS_UNDEFINEDLOG_FILE_IS_UNDEFINED 21 | *.prefs 22 | org.eclipse.wst.common.project.facet.core.xml 23 | .project 24 | .classpath 25 | .factorypath -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | #### V1.1 新增功能介绍 3 | 4 | * 支持通过初始参数值默认生成
5 | 生成jmeter脚本会根据参数的默认值(即取@ApiImplicitParam的defaultValue和@ApiModelProperty的example的值)生成。 6 | 7 | * 支持测试用例生成的顺序
8 | @ApiOperation维护扩展属性{@Extension(name="ext",properties = @ExtensionProperty(name = "sortNo", value = "1"))}),指定sortNo的值,值越小,生成测试用例越靠前。 9 | 10 | *** 11 | 12 | 13 | #### Step 1: 获取工具包 14 | 15 | 1、直接到 [latest stable release](https://github.com/liuyunlong1229/swagger2jmx-plugin/releases).下载 16 | 17 | 2、有maven环境的话,直接在本地执行mvn package生成 18 | 19 | #### Step 2: 解压包后,运行bin目录下的startup.cmd文件,运行前先设置2个参数值 20 | 21 | 【SWAGGER_LOCATION】参数:指定swagger的源,可以是本地文件,或者线上的swagger地址。 22 | 23 | * **方式一:线上swagger** 24 | 25 | ```sh 26 | SWAGGER_LOCATION=http://localhost:18083/v2/api-docs 27 | 28 | ``` 29 | 30 | * **方式二:本地swagger的文件,也就是上面的线上显示的整个大的json内容保存到本地一个文件中**。 31 | 32 | ```sh 33 | SWAGGER_LOCATION=D:/swagger.json 34 | 35 | ``` 36 | 37 | 【JMX_FILE_DIR】参数:指定生成jmeter脚本`auto_test.jmx`生成的位置 38 | 例如保存到D:/jmeter目录下,可以这么设置 39 | 40 | ```sh 41 | JMX_FILE_DIR=D:/jmeter/ 42 | 43 | ``` 44 | #### 效果 45 | ![Image text](https://images.gitee.com/uploads/images/2020/1107/211059_003c5955_1615225.png) 46 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lyl 8 | swagger2jmx-plugin 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.8 13 | 1.8 14 | 2.1.1 15 | 2.0.19 16 | UTF-8 17 | 18 | 19 | 20 | 21 | 22 | io.swagger.core.v3 23 | swagger-core 24 | ${swagger-core-version} 25 | 26 | 27 | io.swagger.parser.v3 28 | swagger-parser 29 | ${swagger-parser-version} 30 | 31 | 32 | 33 | org.freemarker 34 | freemarker 35 | 2.3.23 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-jar-plugin 45 | 2.6 46 | 47 | 48 | false 49 | 50 | true 51 | lib/ 52 | com.lyl.plugin.JmxGenerator 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-assembly-plugin 61 | 62 | 63 | src/main/assembly/assembly.xml 64 | 65 | 66 | 67 | 68 | package 69 | 70 | single 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-surefire-plugin 79 | 80 | true 81 | 82 | 83 | 84 | 85 | swagger2jmx-plugin 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | ${version}-bin 12 | 13 | 14 | 15 | zip 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | 24 | false 25 | lib 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 33 | ${project.basedir} 34 | 35 | 36 | README* 37 | LICENSE* 38 | NOTICE* 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | ${project.basedir}/src/main/bin 51 | bin 52 | 53 | 54 | 55 | 56 | ${project.build.directory} 57 | 58 | 59 | *.jar 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/bin/startup.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Copyright 1999-2018 Alibaba Group Holding Ltd. 3 | rem Licensed under the Apache License, Version 2.0 (the "License"); 4 | rem you may not use this file except in compliance with the License. 5 | rem You may obtain a copy of the License at 6 | rem 7 | rem http://www.apache.org/licenses/LICENSE-2.0 8 | rem 9 | rem Unless required by applicable law or agreed to in writing, software 10 | rem distributed under the License is distributed on an "AS IS" BASIS, 11 | rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | rem See the License for the specific language governing permissions and 13 | rem limitations under the License. 14 | if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better! & EXIT /B 1 15 | set "JAVA=%JAVA_HOME%\bin\java.exe" 16 | 17 | setlocal enabledelayedexpansion 18 | 19 | set BASE_DIR=%~dp0 20 | rem added double quotation marks to avoid the issue caused by the folder names containing spaces. 21 | rem removed the last 5 chars(which means \bin\) to get the base DIR. 22 | set BASE_DIR="%BASE_DIR:~0,-5%" 23 | 24 | rem the swagger address, which can be online or local 25 | set SWAGGER_LOCATION=http://localhost:18083/v2/api-docs 26 | 27 | rem File directory for JMeter script output 28 | set JMX_FILE_DIR=D:/jmeter-script/ 29 | 30 | set SERVER=swagger2jmx-plugin 31 | 32 | rem set exec options 33 | set "EXEC_OPTS=-Dfile.encoding=utf-8" 34 | set "EXEC_OPTS=%EXEC_OPTS% -jar %BASE_DIR%\%SERVER%.jar" 35 | set "EXEC_OPTS=%EXEC_OPTS% --i=%SWAGGER_LOCATION% --o=%JMX_FILE_DIR%" 36 | 37 | set COMMAND="%JAVA%" %EXEC_OPTS% 38 | 39 | rem start generate command 40 | %COMMAND% 41 | 42 | pause -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/EnvArgs.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin; 2 | 3 | /** 4 | * 运行参数对象 5 | * @author yunlong.liu 6 | * @date 2020-11-06 11:35:10 7 | */ 8 | 9 | public class EnvArgs { 10 | 11 | /** 12 | * swagger地址,可以是网上的地址,也可以是本地文件 13 | */ 14 | private String swaggerAddr; 15 | 16 | /** 17 | * jmeter的脚本生成位置 18 | */ 19 | private String fileOutput; 20 | 21 | public String getSwaggerAddr() { 22 | return swaggerAddr; 23 | } 24 | 25 | public void setSwaggerAddr(String swaggerAddr) { 26 | this.swaggerAddr = swaggerAddr; 27 | } 28 | 29 | public String getFileOutput() { 30 | return fileOutput; 31 | } 32 | 33 | public void setFileOutput(String fileOutput) { 34 | this.fileOutput = fileOutput; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/JmxGenerator.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin; 2 | 3 | 4 | import com.lyl.plugin.generate.MyDefaultGenerator; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * 生成器,程序主入口 9 | * @author yunlong.liu 10 | * @date 2020-11-03 19:42:43 11 | */ 12 | 13 | public class JmxGenerator { 14 | 15 | public static void main(String[] args) { 16 | 17 | // generate -i swagger.json -g jmeter 18 | 19 | EnvArgs envArgs= parseArgs(args); 20 | 21 | MyDefaultGenerator myDefaultGenerator=new MyDefaultGenerator(); 22 | try { 23 | myDefaultGenerator.generate(envArgs.getSwaggerAddr(),envArgs.getFileOutput()); 24 | System.out.println("++++++++生成好了 ,文件地址:"+envArgs.getFileOutput()+"/auto_test.jmx"); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | 29 | 30 | } 31 | 32 | 33 | static EnvArgs parseArgs(String [] args){ 34 | 35 | // if(args==null || args.length <2){ 36 | // System.err.println("请通过参数swagger地址和脚本输出目录"); 37 | // System.exit(1); 38 | // } 39 | // "--i=http://localhost:18083/v2/api-docs"; 40 | // "--o=D:/"; 41 | EnvArgs envArgs=new EnvArgs(); 42 | for(int i=0;i examples; 34 | private OpenAPI openAPI; 35 | private Random random; 36 | 37 | public ExampleGenerator(Map examples, OpenAPI openAPI) { 38 | this.examples = examples; 39 | this.openAPI = openAPI; 40 | this.random = new Random((long)"ExampleGenerator".hashCode()); 41 | } 42 | 43 | public List> generateFromResponseSchema(String statusCode, Schema responseSchema, Set producesInfo) { 44 | List> examples = this.generateFromResponseSchema(responseSchema, producesInfo); 45 | if (examples == null) { 46 | return null; 47 | } else { 48 | Iterator var5 = examples.iterator(); 49 | 50 | while(var5.hasNext()) { 51 | Map example = (Map)var5.next(); 52 | example.put("statusCode", statusCode); 53 | } 54 | 55 | return examples; 56 | } 57 | } 58 | 59 | private List> generateFromResponseSchema(Schema responseSchema, Set producesInfo) { 60 | if (responseSchema.getExample() == null && StringUtils.isEmpty(responseSchema.get$ref()) && !ModelUtils.isArraySchema(responseSchema)) { 61 | return null; 62 | } else if (responseSchema.getExample() != null && !(responseSchema.getExample() instanceof Map)) { 63 | return this.generate(responseSchema.getExample(), new ArrayList(producesInfo)); 64 | } else if (ModelUtils.isArraySchema(responseSchema)) { 65 | ArraySchema as = (ArraySchema)responseSchema; 66 | if (as.getItems() != null && StringUtils.isEmpty(as.getItems().get$ref())) { 67 | return this.generate((Map)responseSchema.getExample(), new ArrayList(producesInfo), (Schema)as.getItems()); 68 | } else { 69 | return as.getItems() != null && !StringUtils.isEmpty(as.getItems().get$ref()) ? this.generate((Map)responseSchema.getExample(), new ArrayList(producesInfo), (String)ModelUtils.getSimpleRef(as.getItems().get$ref())) : null; 70 | } 71 | } else { 72 | return StringUtils.isEmpty(responseSchema.get$ref()) ? this.generate((Map)responseSchema.getExample(), new ArrayList(producesInfo), (Schema)responseSchema) : this.generate((Map)responseSchema.getExample(), new ArrayList(producesInfo), (String)ModelUtils.getSimpleRef(responseSchema.get$ref())); 73 | } 74 | } 75 | 76 | public List> generate(Map examples, List mediaTypes, Schema property) { 77 | LOGGER.debug("debugging generate in ExampleGenerator"); 78 | List> output = new ArrayList(); 79 | Set processedModels = new HashSet(); 80 | Iterator var6; 81 | HashMap kv; 82 | if (examples == null) { 83 | if (mediaTypes == null) { 84 | mediaTypes = Collections.singletonList("application/json"); 85 | } 86 | 87 | var6 = mediaTypes.iterator(); 88 | 89 | label51: 90 | while(true) { 91 | while(true) { 92 | if (!var6.hasNext()) { 93 | break label51; 94 | } 95 | 96 | String mediaType = (String)var6.next(); 97 | kv = new HashMap(); 98 | kv.put("contentType", mediaType); 99 | String example; 100 | if (property != null && (mediaType.startsWith("application/json") || mediaType.contains("*/*"))) { 101 | example = Json.pretty(this.resolvePropertyToExample("", mediaType, property, processedModels)); 102 | if (example != null) { 103 | kv.put("example", example); 104 | output.add(kv); 105 | } 106 | } else if (property != null && mediaType.startsWith("application/xml")) { 107 | example = (new XmlExampleGenerator(this.examples)).toXml(property); 108 | if (example != null) { 109 | kv.put("example", example); 110 | output.add(kv); 111 | } 112 | } 113 | } 114 | } 115 | } else { 116 | var6 = examples.entrySet().iterator(); 117 | 118 | while(var6.hasNext()) { 119 | Map.Entry entry = (Map.Entry)var6.next(); 120 | kv = new HashMap(); 121 | kv.put("contentType", entry.getKey()); 122 | kv.put("example", Json.pretty(entry.getValue())); 123 | output.add(kv); 124 | } 125 | } 126 | 127 | if (output.size() == 0) { 128 | Map kv1 = new HashMap(); 129 | kv1.put("output", "none"); 130 | output.add(kv1); 131 | } 132 | 133 | return output; 134 | } 135 | 136 | public List> generate(Map examples, List mediaTypes, String modelName) { 137 | List> output = new ArrayList(); 138 | Set processedModels = new HashSet(); 139 | Iterator var6; 140 | HashMap kv; 141 | if (examples == null) { 142 | if (mediaTypes == null) { 143 | mediaTypes = Collections.singletonList("application/json"); 144 | } 145 | 146 | var6 = mediaTypes.iterator(); 147 | 148 | label53: 149 | while(true) { 150 | while(true) { 151 | if (!var6.hasNext()) { 152 | break label53; 153 | } 154 | 155 | String mediaType = (String)var6.next(); 156 | kv = new HashMap(); 157 | kv.put("contentType", mediaType); 158 | Schema schema; 159 | String example; 160 | if (modelName != null && (mediaType.startsWith("application/json") || mediaType.contains("*/*"))) { 161 | schema = (Schema)this.examples.get(modelName); 162 | if (schema != null) { 163 | example = Json.pretty(this.resolveModelToExample(modelName, mediaType, schema, processedModels)); 164 | if (example != null) { 165 | kv.put("example", example); 166 | output.add(kv); 167 | } 168 | } 169 | } else if (modelName != null && mediaType.startsWith("application/xml")) { 170 | schema = (Schema)this.examples.get(modelName); 171 | example = (new XmlExampleGenerator(this.examples)).toXml(schema, 0, Collections.emptySet()); 172 | if (example != null) { 173 | kv.put("example", example); 174 | output.add(kv); 175 | } 176 | } 177 | } 178 | } 179 | } else { 180 | var6 = examples.entrySet().iterator(); 181 | 182 | while(var6.hasNext()) { 183 | Map.Entry entry = (Map.Entry)var6.next(); 184 | kv = new HashMap(); 185 | kv.put("contentType", entry.getKey()); 186 | kv.put("example", Json.pretty(entry.getValue())); 187 | output.add(kv); 188 | } 189 | } 190 | 191 | if (output.size() == 0) { 192 | Map kv1 = new HashMap(); 193 | kv1.put("output", "none"); 194 | output.add(kv1); 195 | } 196 | 197 | return output; 198 | } 199 | 200 | private List> generate(Object example, List mediaTypes) { 201 | List> output = new ArrayList(); 202 | if (this.examples != null) { 203 | if (mediaTypes == null) { 204 | mediaTypes = Collections.singletonList("application/json"); 205 | } 206 | 207 | Iterator var4 = mediaTypes.iterator(); 208 | 209 | label32: 210 | while(true) { 211 | while(true) { 212 | if (!var4.hasNext()) { 213 | break label32; 214 | } 215 | 216 | String mediaType = (String)var4.next(); 217 | Map kv = new HashMap(); 218 | kv.put("contentType", mediaType); 219 | if (!mediaType.startsWith("application/json") && !mediaType.contains("*/*")) { 220 | if (mediaType.startsWith("application/xml")) { 221 | LOGGER.warn("XML example value of (array/primitive) is not handled at the moment: " + example); 222 | } 223 | } else { 224 | kv.put("example", Json.pretty(example)); 225 | output.add(kv); 226 | } 227 | } 228 | } 229 | } 230 | 231 | if (output.size() == 0) { 232 | Map kv = new HashMap(); 233 | kv.put("output", "none"); 234 | output.add(kv); 235 | } 236 | 237 | return output; 238 | } 239 | 240 | private Object resolvePropertyToExample(String propertyName, String mediaType, Schema property, Set processedModels) { 241 | LOGGER.debug("Resolving example for property {}...", property); 242 | if (property.getExample() != null) { 243 | LOGGER.debug("Example set in openapi spec, returning example: '{}'", property.getExample().toString()); 244 | return property.getExample(); 245 | } else if (ModelUtils.isBooleanSchema(property)) { 246 | Object defaultValue = property.getDefault(); 247 | return defaultValue != null ? defaultValue : Boolean.TRUE; 248 | } else { 249 | if (ModelUtils.isArraySchema(property)) { 250 | Schema innerType = ((ArraySchema)property).getItems(); 251 | if (innerType != null) { 252 | int arrayLength = null == ((ArraySchema)property).getMaxItems() ? 2 : ((ArraySchema)property).getMaxItems(); 253 | arrayLength = Math.min(arrayLength, 5); 254 | Object[] objectProperties = new Object[arrayLength]; 255 | Object objProperty = this.resolvePropertyToExample(propertyName, mediaType, innerType, processedModels); 256 | 257 | for(int i = 0; i < arrayLength; ++i) { 258 | objectProperties[i] = objProperty; 259 | } 260 | 261 | return objectProperties; 262 | } 263 | } else { 264 | if (ModelUtils.isDateSchema(property)) { 265 | return "2000-01-23"; 266 | } 267 | 268 | if (ModelUtils.isDateTimeSchema(property)) { 269 | return "2000-01-23T04:56:07.000+00:00"; 270 | } 271 | 272 | Double min; 273 | Double max; 274 | if (ModelUtils.isNumberSchema(property)) { 275 | min = this.getPropertyValue(property.getMinimum()); 276 | max = this.getPropertyValue(property.getMaximum()); 277 | if (ModelUtils.isFloatSchema(property)) { 278 | return (float)this.randomNumber(min, max); 279 | } 280 | 281 | if (ModelUtils.isDoubleSchema(property)) { 282 | return BigDecimal.valueOf(this.randomNumber(min, max)); 283 | } 284 | 285 | return this.randomNumber(min, max); 286 | } 287 | 288 | if (ModelUtils.isFileSchema(property)) { 289 | return ""; 290 | } 291 | 292 | if (ModelUtils.isIntegerSchema(property)) { 293 | min = this.getPropertyValue(property.getMinimum()); 294 | max = this.getPropertyValue(property.getMaximum()); 295 | if (ModelUtils.isLongSchema(property)) { 296 | return (long)this.randomNumber(min, max); 297 | } 298 | 299 | return (int)this.randomNumber(min, max); 300 | } 301 | 302 | if (ModelUtils.isMapSchema(property)) { 303 | Map mp = new HashMap(); 304 | if (property.getName() != null) { 305 | mp.put(property.getName(), this.resolvePropertyToExample(propertyName, mediaType, ModelUtils.getAdditionalProperties(property), processedModels)); 306 | } else { 307 | mp.put("key", this.resolvePropertyToExample(propertyName, mediaType, ModelUtils.getAdditionalProperties(property), processedModels)); 308 | } 309 | 310 | return mp; 311 | } 312 | 313 | if (ModelUtils.isUUIDSchema(property)) { 314 | return "046b6c7f-0b8a-43b9-b35d-6489e6daee91"; 315 | } 316 | 317 | if (ModelUtils.isURISchema(property)) { 318 | return "https://openapi-generator.tech"; 319 | } 320 | 321 | String simpleName; 322 | if (ModelUtils.isStringSchema(property)) { 323 | LOGGER.debug("String property"); 324 | simpleName = (String)property.getDefault(); 325 | if (simpleName != null && !simpleName.isEmpty()) { 326 | LOGGER.debug("Default value found: '{}'", simpleName); 327 | return simpleName; 328 | } 329 | 330 | List enumValues = property.getEnum(); 331 | if (enumValues != null && !enumValues.isEmpty()) { 332 | LOGGER.debug("Enum value found: '{}'", enumValues.get(0)); 333 | return enumValues.get(0); 334 | } 335 | 336 | String format = property.getFormat(); 337 | if (format == null || !"uri".equals(format) && !"url".equals(format)) { 338 | LOGGER.debug("No values found, using property name " + propertyName + " as example"); 339 | return propertyName; 340 | } 341 | 342 | LOGGER.debug("URI or URL format, without default or enum, generating random one."); 343 | return "http://example.com/aeiou"; 344 | } 345 | 346 | if (!StringUtils.isEmpty(property.get$ref())) { 347 | simpleName = ModelUtils.getSimpleRef(property.get$ref()); 348 | Schema schema = ModelUtils.getSchema(this.openAPI, simpleName); 349 | if (schema == null) { 350 | return "{}"; 351 | } 352 | 353 | return this.resolveModelToExample(simpleName, mediaType, schema, processedModels); 354 | } 355 | 356 | if (ModelUtils.isObjectSchema(property)) { 357 | return "{}"; 358 | } 359 | } 360 | 361 | return ""; 362 | } 363 | } 364 | 365 | private Double getPropertyValue(BigDecimal propertyValue) { 366 | return propertyValue == null ? null : propertyValue.doubleValue(); 367 | } 368 | 369 | private double randomNumber(Double min, Double max) { 370 | if (min != null && max != null) { 371 | double range = max - min; 372 | return this.random.nextDouble() * range + min; 373 | } else if (min != null) { 374 | return this.random.nextDouble() + min; 375 | } else { 376 | return max != null ? this.random.nextDouble() * max : this.random.nextDouble() * 10.0D; 377 | } 378 | } 379 | 380 | private Object resolveModelToExample(String name, String mediaType, Schema schema, Set processedModels) { 381 | if (processedModels.contains(name)) { 382 | return schema.getExample(); 383 | } else { 384 | processedModels.add(name); 385 | Map values = new HashMap(); 386 | LOGGER.debug("Resolving model '{}' to example", name); 387 | if (schema.getExample() != null) { 388 | LOGGER.debug("Using example from spec: {}", schema.getExample()); 389 | return schema.getExample(); 390 | } else if (schema.getProperties() == null) { 391 | return null; 392 | } else { 393 | LOGGER.debug("Creating example from model values"); 394 | Iterator var6 = schema.getProperties().keySet().iterator(); 395 | 396 | while(var6.hasNext()) { 397 | Object propertyName = var6.next(); 398 | Schema property = (Schema)schema.getProperties().get(propertyName.toString()); 399 | values.put(propertyName.toString(), this.resolvePropertyToExample(propertyName.toString(), mediaType, property, processedModels)); 400 | } 401 | 402 | schema.setExample(values); 403 | return schema.getExample(); 404 | } 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/generate/MyDefaultGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) 3 | * Copyright 2018 SmartBear Software 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.lyl.plugin.generate; 19 | 20 | import com.lyl.plugin.model.ParamNode; 21 | import com.lyl.plugin.model.RequestNode; 22 | import com.lyl.plugin.model.TagNode; 23 | import com.lyl.plugin.model.VariableNode; 24 | import com.lyl.plugin.vo.TemplateParamVO; 25 | import com.lyl.plugin.parse.SwaggerParser; 26 | import com.lyl.plugin.utils.ModelUtils; 27 | import freemarker.template.Configuration; 28 | import freemarker.template.Template; 29 | import io.swagger.v3.oas.models.OpenAPI; 30 | import io.swagger.v3.oas.models.Operation; 31 | import io.swagger.v3.oas.models.PathItem; 32 | import io.swagger.v3.oas.models.media.Schema; 33 | import io.swagger.v3.oas.models.parameters.*; 34 | import io.swagger.v3.oas.models.servers.Server; 35 | import io.swagger.v3.oas.models.tags.Tag; 36 | import io.swagger.v3.parser.core.models.SwaggerParseResult; 37 | import org.apache.commons.lang3.StringUtils; 38 | import org.slf4j.Logger; 39 | import org.slf4j.LoggerFactory; 40 | import java.io.*; 41 | import java.net.MalformedURLException; 42 | import java.net.URL; 43 | import java.util.*; 44 | 45 | 46 | @SuppressWarnings("rawtypes") 47 | public class MyDefaultGenerator { 48 | 49 | private static final Logger LOGGER = LoggerFactory.getLogger(MyDefaultGenerator.class); 50 | 51 | private OpenAPI openAPI; 52 | 53 | 54 | public void generate(String swaggerSourceLocation,String outPutDir) throws Exception { 55 | 56 | //step1 :解析swagger内容,生成openApi 57 | SwaggerParseResult swaggerParseResult = new SwaggerParser().readWithInfo(swaggerSourceLocation, null); 58 | this.openAPI = swaggerParseResult.getOpenAPI(); 59 | //step2: 封装模板参数对象 60 | TemplateParamVO templateParamVO = prepareTemplateData(); 61 | //step3: 填充模板 62 | paddingTemplate(templateParamVO,outPutDir); 63 | 64 | 65 | } 66 | 67 | public void paddingTemplate(TemplateParamVO templateParamVO,String outPutDir) throws Exception { 68 | Configuration configuration = new Configuration(Configuration.getVersion()); 69 | configuration.setClassForTemplateLoading(this.getClass(), "/templates"); 70 | configuration.setDefaultEncoding("utf-8"); 71 | Template template = configuration.getTemplate("template.jmx"); 72 | File fileDir= new File(outPutDir); 73 | if(!fileDir.exists()){ 74 | fileDir.mkdir(); 75 | } 76 | Writer out = new FileWriter(new File(outPutDir+"/auto_test.jmx")); 77 | template.process(templateParamVO, out); 78 | out.close(); 79 | } 80 | 81 | 82 | private Map groupTagWithName() { 83 | List tags = openAPI.getTags(); 84 | Map tagNodeMap = new HashMap<>(); 85 | for (int i = 0; i < tags.size(); i++) { 86 | Tag tag = tags.get(i); 87 | TagNode tagNode = new TagNode(); 88 | tagNode.setName(tag.getName()); 89 | tagNode.setDescription(tag.getDescription()); 90 | tagNodeMap.put(tag.getName(), tagNode); 91 | } 92 | 93 | return tagNodeMap; 94 | 95 | } 96 | 97 | public TemplateParamVO prepareTemplateData() { 98 | 99 | 100 | TemplateParamVO templateParamVO = new TemplateParamVO(); 101 | templateParamVO.setTitle(StringUtils.isBlank(openAPI.getInfo().getTitle())?"测试计划":openAPI.getInfo().getTitle()); 102 | templateParamVO.setDescription(openAPI.getInfo().getDescription()); 103 | 104 | 105 | String host= "localhost"; 106 | int port=8080; 107 | if(openAPI.getServers().size()> 0){ 108 | Server server=openAPI.getServers().get(0); 109 | URL serverURL= getServerURL(server); 110 | if(serverURL != null) { 111 | host = serverURL.getHost(); 112 | port = serverURL.getPort(); 113 | } 114 | } 115 | 116 | templateParamVO.setHost(host); 117 | templateParamVO.setPort(port==-1?80:port); 118 | 119 | Map tagNodeMap = groupTagWithName(); 120 | Iterator pathIterator = openAPI.getPaths().keySet().iterator(); 121 | List customVariableList=new ArrayList<>(); 122 | while (pathIterator.hasNext()) { 123 | 124 | String resourcePath = (String) pathIterator.next(); 125 | PathItem path = (PathItem) openAPI.getPaths().get(resourcePath); 126 | RequestNode requestNode = build(resourcePath, path,customVariableList); 127 | Iterator it = requestNode.getTag().iterator(); 128 | while (it.hasNext()) { 129 | String tagName = it.next(); 130 | if (tagNodeMap.get(tagName) != null) { 131 | TagNode tagNode = tagNodeMap.get(tagName); 132 | tagNode.getRequestNodes().add(requestNode); 133 | } 134 | } 135 | } 136 | 137 | 138 | templateParamVO.setCustomVariableList(customVariableList); 139 | 140 | 141 | 142 | for (Map.Entry tagNodeEntry : tagNodeMap.entrySet()) { 143 | if (tagNodeEntry.getValue().getRequestNodes().isEmpty()) { 144 | continue; 145 | } 146 | tagNodeEntry.getValue().getRequestNodes().sort((n1,n2)->{ 147 | return n1.getSortNo()-n2.getSortNo(); 148 | }); 149 | templateParamVO.getTagList().add(tagNodeEntry.getValue()); 150 | } 151 | 152 | return templateParamVO; 153 | } 154 | 155 | 156 | 157 | 158 | public URL getServerURL(Server server) { 159 | String url = server.getUrl(); 160 | if (StringUtils.isNotBlank(url)) { 161 | url = sanitizeUrl(url); 162 | 163 | try { 164 | return new URL(url); 165 | } catch (MalformedURLException var6) { 166 | LOGGER.warn("Not valid URL: {}. Default to {}.", server.getUrl(), "http://localhost"); 167 | } 168 | } 169 | LOGGER.warn("Not Server URL: {}. Default to {}.", server.getUrl(), "http://localhost"); 170 | return null; 171 | } 172 | private String sanitizeUrl(String url) { 173 | if (url != null) { 174 | if (url.startsWith("//")) { 175 | url = "http:" + url; 176 | } else if (url.startsWith("/")) { 177 | url = "http://localhost" + url; 178 | } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { 179 | url = "http://" + url; 180 | } 181 | } 182 | 183 | return url; 184 | } 185 | 186 | 187 | 188 | private RequestNode build(String resourcePath, PathItem path, List customVariableList) { 189 | 190 | RequestNode requestNode = new RequestNode(); 191 | requestNode.setRequestUrl(resourcePath); 192 | 193 | Operation operation = null; 194 | 195 | if (path.getGet() != null) { 196 | operation = path.getGet(); 197 | requestNode.setHttpMethod(PathItem.HttpMethod.GET.name()); 198 | } else if (path.getPost() != null) { 199 | operation = path.getPost(); 200 | requestNode.setHttpMethod(PathItem.HttpMethod.POST.name()); 201 | } else if (path.getDelete() != null) { 202 | operation = path.getDelete(); 203 | requestNode.setHttpMethod(PathItem.HttpMethod.DELETE.name()); 204 | } else if (path.getPut() != null) { 205 | operation = path.getPut(); 206 | requestNode.setHttpMethod(PathItem.HttpMethod.PUT.name()); 207 | } else { 208 | throw new RuntimeException("["+resourcePath+"]非法的请求方式"); 209 | } 210 | 211 | requestNode.setTag(operation.getTags()); 212 | if(StringUtils.isNotBlank(operation.getSummary())){ 213 | String summary= operation.getSummary().replaceAll("&","&"); 214 | requestNode.setOperationName(summary); 215 | }else{ 216 | requestNode.setOperationName(operation.getOperationId()); 217 | } 218 | requestNode.setOperationId(operation.getOperationId()); 219 | List queryParamNodes = new ArrayList<>(); 220 | List headerParamNodes = new ArrayList<>(); 221 | 222 | //获取接口排序号 223 | int sortNo=Optional.ofNullable(operation.getExtensions()).map(ext->(LinkedHashMap)ext.get("x-ext")).map(e->(String)e.get("sortNo")).map(s->Integer.valueOf(s)).orElse(0); 224 | requestNode.setSortNo(sortNo); 225 | 226 | 227 | boolean requestBodyIsEmpty=true; 228 | 229 | if (operation.getRequestBody() != null) { 230 | 231 | RequestBody requestBody = path.getPost().getRequestBody(); 232 | requestBody = ModelUtils.getReferencedRequestBody(this.openAPI, requestBody); 233 | Map schemas = ModelUtils.getSchemas(this.openAPI); 234 | 235 | Schema schema = ModelUtils.getSchemaFromRequestBody(requestBody); 236 | 237 | if(schema.get$ref() !=null){ 238 | String modelTypeNme= ModelUtils.getSimpleRef(schema.get$ref()); 239 | Set contentTypes= getConsumesInfo(openAPI,operation); 240 | List> exampleList = new ExampleGenerator(schemas, this.openAPI).generate((Map) null, new ArrayList(contentTypes), modelTypeNme); 241 | if(exampleList.size()>0){ 242 | Map element= exampleList.get(0); 243 | String example=element.get("example"); 244 | requestNode.setRequestBody(example); 245 | requestBodyIsEmpty=false; 246 | LOGGER.info("生成的body参数==>{}",example); 247 | 248 | } 249 | } 250 | 251 | } 252 | 253 | StringBuilder queryString=new StringBuilder(); 254 | Boolean isFirstParam=true; 255 | List parameterList = operation.getParameters(); 256 | if (parameterList != null && !parameterList.isEmpty()) { 257 | for (Parameter param : parameterList) { 258 | String variableName=operation.getOperationId()+"."+param.getName(); 259 | ParamNode paramNode = new ParamNode(); 260 | if ((param instanceof QueryParameter) || "query".equalsIgnoreCase(param.getIn())) { 261 | if(requestBodyIsEmpty){ 262 | paramNode.setParamName(param.getName()); 263 | paramNode.setParamValue("${"+variableName+"}"); 264 | String defaultValue=param.getSchema().getDefault()==null?null:param.getSchema().getDefault().toString(); 265 | customVariableList.add(new VariableNode(variableName,defaultValue,param.getDescription())); 266 | queryParamNodes.add(paramNode); 267 | }else{ 268 | if(isFirstParam){ 269 | queryString= queryString.append("?"); 270 | isFirstParam=false; 271 | }else { 272 | queryString= queryString.append("&"); 273 | } 274 | queryString= queryString.append(param.getName()).append("=").append(variableName); 275 | customVariableList.add(new VariableNode(variableName,null,param.getDescription())); 276 | 277 | } 278 | 279 | } else if ((param instanceof PathParameter) || "path".equalsIgnoreCase(param.getIn())) { 280 | //paramNode.setParamName(param.getName()); 281 | //queryParamNodes.add(paramNode); 282 | //路径参数没法在jmeter里面设置参数列表 283 | String exp="{"+param.getName()+"}"; 284 | String target="${"+variableName+"}"; 285 | String defaultValue=param.getSchema().getDefault()==null?null:param.getSchema().getDefault().toString(); 286 | requestNode.setRequestUrl(requestNode.getRequestUrl().replace(exp,target)); 287 | customVariableList.add(new VariableNode(variableName,defaultValue,param.getDescription())); 288 | } else if ("body".equalsIgnoreCase(param.getIn())) { 289 | LOGGER.info("生成的body参数"); 290 | } else if ((param instanceof HeaderParameter) || "header".equalsIgnoreCase(param.getIn())) { 291 | paramNode.setParamName(param.getName()); 292 | paramNode.setParamValue("${"+variableName+"}"); 293 | headerParamNodes.add(paramNode); 294 | String defaultValue=param.getSchema().getDefault()==null?null:param.getSchema().getDefault().toString(); 295 | customVariableList.add(new VariableNode(variableName,defaultValue,param.getDescription())); 296 | } 297 | } 298 | 299 | if(StringUtils.isNotBlank(queryString.toString())){ 300 | requestNode.setRequestUrl(requestNode.getRequestUrl()+queryString); 301 | } 302 | 303 | requestNode.setQueryParamNodes(queryParamNodes); 304 | requestNode.setHeaderParamNodes(headerParamNodes); 305 | } 306 | 307 | return requestNode; 308 | } 309 | 310 | public static Set getConsumesInfo (OpenAPI openAPI, Operation operation){ 311 | RequestBody requestBody = ModelUtils.getReferencedRequestBody(openAPI, operation.getRequestBody()); 312 | return requestBody != null && requestBody.getContent() != null && !requestBody.getContent().isEmpty() ? requestBody.getContent().keySet() : Collections.emptySet(); 313 | } 314 | 315 | } 316 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/generate/XmlExampleGenerator.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.generate; 2 | 3 | import com.lyl.plugin.utils.ModelUtils; 4 | import io.swagger.v3.oas.models.media.ArraySchema; 5 | import io.swagger.v3.oas.models.media.Schema; 6 | import io.swagger.v3.oas.models.media.XML; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.*; 12 | 13 | /** 14 | * @author yunlong.liu 15 | * @date 2020-11-05 17:11:12 16 | */ 17 | 18 | public class XmlExampleGenerator { 19 | protected final Logger LOGGER = LoggerFactory.getLogger(XmlExampleGenerator.class); 20 | public static String NEWLINE = "\n"; 21 | public static String TAG_START = "<"; 22 | public static String CLOSE_TAG = ">"; 23 | public static String TAG_END = " examples; 26 | 27 | public XmlExampleGenerator(Map examples) { 28 | this.examples = examples; 29 | if (examples == null) { 30 | this.examples = new HashMap(); 31 | } 32 | } 33 | 34 | public String toXml(Schema schema) { 35 | return toXml(null, schema, 0, Collections.emptySet()); 36 | } 37 | 38 | protected String toXml(Schema schema, int indent, Collection path) { 39 | if (schema == null) return ""; 40 | if (StringUtils.isNotEmpty(schema.get$ref())) { 41 | Schema actualSchema = examples.get(schema.get$ref()); 42 | if (actualSchema != null) { 43 | return modelImplToXml(actualSchema, indent, path); 44 | } 45 | } 46 | return modelImplToXml(schema, indent, path); 47 | } 48 | 49 | protected String modelImplToXml(Schema schema, int indent, Collection path) { 50 | final String modelName = schema.getName(); 51 | if (path.contains(modelName)) { 52 | return EMPTY; 53 | } 54 | final Set selfPath = new HashSet(path); 55 | selfPath.add(modelName); 56 | 57 | StringBuilder sb = new StringBuilder(); 58 | // attributes 59 | Map attributes = new LinkedHashMap(); 60 | Map elements = new LinkedHashMap(); 61 | 62 | String name = modelName; 63 | XML xml = schema.getXml(); 64 | if (xml != null) { 65 | if (xml.getName() != null) { 66 | name = xml.getName(); 67 | } 68 | } 69 | // TODO: map objects will not enter this block 70 | Map properties = schema.getProperties(); 71 | if (properties != null && !properties.isEmpty()) { 72 | for (String pName : properties.keySet()) { 73 | Schema property = properties.get(pName); 74 | if (property != null && property.getXml() != null && property.getXml().getAttribute() != null && property.getXml().getAttribute()) { 75 | attributes.put(pName, property); 76 | } else { 77 | elements.put(pName, property); 78 | } 79 | } 80 | } 81 | 82 | sb.append(indent(indent)).append(TAG_START); 83 | sb.append(name); 84 | for (String pName : attributes.keySet()) { 85 | Schema s = attributes.get(pName); 86 | sb.append(" ").append(pName).append("=").append(quote(toXml(null, s, 0, selfPath))); 87 | } 88 | sb.append(CLOSE_TAG); 89 | sb.append(NEWLINE); 90 | for (String pName : elements.keySet()) { 91 | Schema s = elements.get(pName); 92 | final String asXml = toXml(pName, s, indent + 1, selfPath); 93 | if (StringUtils.isEmpty(asXml)) { 94 | continue; 95 | } 96 | sb.append(asXml); 97 | sb.append(NEWLINE); 98 | } 99 | sb.append(indent(indent)).append(TAG_END).append(name).append(CLOSE_TAG); 100 | 101 | return sb.toString(); 102 | } 103 | 104 | @SuppressWarnings("static-method") 105 | protected String quote(String string) { 106 | return "\"" + string + "\""; 107 | } 108 | 109 | protected String toXml(String name, Schema schema, int indent, Collection path) { 110 | if (schema == null) { 111 | return ""; 112 | } 113 | StringBuilder sb = new StringBuilder(); 114 | 115 | if (ModelUtils.isArraySchema(schema)) { 116 | ArraySchema as = (ArraySchema) schema; 117 | Schema inner = as.getItems(); 118 | boolean wrapped = false; 119 | if (schema.getXml() != null && schema.getXml().getWrapped() != null && schema.getXml().getWrapped()) { 120 | wrapped = true; 121 | } 122 | if (wrapped) { 123 | String prefix = EMPTY; 124 | if (name != null) { 125 | sb.append(indent(indent)); 126 | sb.append(openTag(name)); 127 | prefix = NEWLINE; 128 | } 129 | final String asXml = toXml(name, inner, indent + 1, path); 130 | if (StringUtils.isNotEmpty(asXml)) { 131 | sb.append(prefix).append(asXml); 132 | } 133 | if (name != null) { 134 | sb.append(NEWLINE); 135 | sb.append(indent(indent)); 136 | sb.append(closeTag(name)); 137 | } 138 | } else { 139 | sb.append(toXml(name, inner, indent, path)); 140 | } 141 | } else if (StringUtils.isNotEmpty(schema.get$ref())) { 142 | Schema actualSchema = examples.get(schema.get$ref()); 143 | sb.append(toXml(actualSchema, indent, path)); 144 | } else { 145 | if (name != null) { 146 | sb.append(indent(indent)); 147 | sb.append(openTag(name)); 148 | } 149 | sb.append(getExample(schema)); 150 | if (name != null) { 151 | sb.append(closeTag(name)); 152 | } 153 | } 154 | return sb.toString(); 155 | } 156 | 157 | /** 158 | * Get the example string value for the given schema. 159 | * 160 | * If an example value was not provided in the specification, a default will be generated. 161 | * 162 | * @param schema Schema to get example string for 163 | * @return Example String 164 | */ 165 | protected String getExample(Schema schema) { 166 | if (schema.getExample() != null) { 167 | return schema.getExample().toString(); 168 | } else if (ModelUtils.isDateTimeSchema(schema)) { 169 | return "2000-01-23T04:56:07.000Z"; 170 | } else if (ModelUtils.isDateSchema(schema)) { 171 | return "2000-01-23"; 172 | } else if (ModelUtils.isBooleanSchema(schema)) { 173 | return "true"; 174 | } else if (ModelUtils.isNumberSchema(schema)) { 175 | if (ModelUtils.isFloatSchema(schema)) { // float 176 | return "1.3579"; 177 | } else { // double 178 | return "3.149"; 179 | } 180 | } else if (ModelUtils.isPasswordSchema(schema)) { 181 | return "********"; 182 | } else if (ModelUtils.isUUIDSchema(schema)) { 183 | return "046b6c7f-0b8a-43b9-b35d-6489e6daee91"; 184 | } else if (ModelUtils.isURISchema(schema)) { 185 | return "https://openapi-generator.tech"; 186 | // do these last in case the specific types above are derived from these classes 187 | } else if (ModelUtils.isStringSchema(schema)) { 188 | return "aeiou"; 189 | } else if (ModelUtils.isIntegerSchema(schema)) { 190 | if (ModelUtils.isLongSchema(schema)) { // long 191 | return "123456789"; 192 | } else { //integer 193 | return "123"; 194 | } 195 | } else { 196 | LOGGER.debug("default example value not implemented for {}. Default to UNDEFINED_EXAMPLE_VALUE", schema); 197 | return "UNDEFINED_EXAMPLE_VALUE"; 198 | } 199 | } 200 | 201 | @SuppressWarnings("static-method") 202 | protected String openTag(String name) { 203 | return "<" + name + ">"; 204 | } 205 | 206 | @SuppressWarnings("static-method") 207 | protected String closeTag(String name) { 208 | return ""; 209 | } 210 | 211 | @SuppressWarnings("static-method") 212 | protected String indent(int indent) { 213 | StringBuffer sb = new StringBuffer(); 214 | for (int i = 0; i < indent; i++) { 215 | sb.append(" "); 216 | } 217 | return sb.toString(); 218 | } 219 | } -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/model/ParamNode.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.model; 2 | 3 | /** 4 | * 5 | * 请求的参数节点 6 | * @author yunlong.liu 7 | * @date 2020-11-04 15:22:02 8 | */ 9 | 10 | public class ParamNode { 11 | 12 | /** 13 | * 参数名称 14 | */ 15 | private String paramName; 16 | 17 | /** 18 | * 参数值 19 | */ 20 | private String paramValue; 21 | 22 | /*** 23 | * 参数类型 24 | */ 25 | private String paramType; 26 | 27 | /** 28 | * 参数描述 29 | */ 30 | private String description; 31 | 32 | 33 | public String getParamName() { 34 | return paramName; 35 | } 36 | 37 | public void setParamName(String paramName) { 38 | this.paramName = paramName; 39 | } 40 | 41 | public String getParamValue() { 42 | return paramValue; 43 | } 44 | 45 | public void setParamValue(String paramValue) { 46 | this.paramValue = paramValue; 47 | } 48 | 49 | public String getParamType() { 50 | return paramType; 51 | } 52 | 53 | public void setParamType(String paramType) { 54 | this.paramType = paramType; 55 | } 56 | 57 | public String getDescription() { 58 | return description; 59 | } 60 | 61 | public void setDescription(String description) { 62 | this.description = description; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/model/RequestNode.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * 8 | * 请求节点对应每个http接口 9 | * @author yunlong.liu 10 | * @date 2020-11-04 15:20:13 11 | */ 12 | 13 | public class RequestNode { 14 | 15 | /** 16 | * 接口上面的tag列表 17 | */ 18 | private List tag; 19 | 20 | /** 21 | * 操作名称,swagger声明的@ApiOperation的value值 22 | */ 23 | private String operationName; 24 | 25 | 26 | /** 27 | * 操作id,由接口名称加请求方式构成,如:getByNameUsingPOST 28 | */ 29 | private String operationId; 30 | 31 | 32 | /** 33 | * 接口请求路径 34 | */ 35 | private String requestUrl; 36 | 37 | 38 | /** 39 | * 接口的query类型参数名称列表 40 | */ 41 | private List queryParamNodes=new ArrayList<>(); 42 | 43 | /** 44 | * 接口的header类型参数名称列表 45 | */ 46 | private List headerParamNodes=new ArrayList<>(); 47 | 48 | /** 49 | * 接口的body类型参数结构 50 | */ 51 | private String requestBody =null; 52 | 53 | 54 | /** 55 | * 接口排序号 56 | */ 57 | private int sortNo; 58 | 59 | public String getHttpMethod() { 60 | return httpMethod; 61 | } 62 | 63 | private String httpMethod; 64 | 65 | public String getOperationName() { 66 | return operationName; 67 | } 68 | 69 | public void setOperationName(String operationName) { 70 | this.operationName = operationName; 71 | } 72 | 73 | public String getRequestBody() { 74 | return requestBody; 75 | } 76 | 77 | public void setRequestBody(String requestBody) { 78 | this.requestBody = requestBody; 79 | } 80 | 81 | public String getRequestUrl() { 82 | return requestUrl; 83 | } 84 | 85 | public void setRequestUrl(String requestUrl) { 86 | this.requestUrl = requestUrl; 87 | } 88 | 89 | 90 | public void setHttpMethod(String httpMethod) { 91 | this.httpMethod = httpMethod; 92 | } 93 | 94 | public List getTag() { 95 | return tag; 96 | } 97 | 98 | public void setTag(List tag) { 99 | this.tag = tag; 100 | } 101 | 102 | public List getQueryParamNodes() { 103 | return queryParamNodes; 104 | } 105 | 106 | public void setQueryParamNodes(List queryParamNodes) { 107 | this.queryParamNodes = queryParamNodes; 108 | } 109 | 110 | public List getHeaderParamNodes() { 111 | return headerParamNodes; 112 | } 113 | 114 | public void setHeaderParamNodes(List headerParamNodes) { 115 | this.headerParamNodes = headerParamNodes; 116 | } 117 | 118 | public String getOperationId() { 119 | return operationId; 120 | } 121 | 122 | public void setOperationId(String operationId) { 123 | this.operationId = operationId; 124 | } 125 | 126 | public int getSortNo() { 127 | return sortNo; 128 | } 129 | 130 | public void setSortNo(int sortNo) { 131 | this.sortNo = sortNo; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/model/TagNode.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * tag 节点对应服务的controller 8 | * @author yunlong.liu 9 | * @date 2020-11-04 15:19:48 10 | */ 11 | 12 | public class TagNode { 13 | 14 | /** 15 | * 对应controller名称 16 | */ 17 | private String name = null; 18 | 19 | 20 | /** 21 | * controller的描述 22 | */ 23 | private String description = null; 24 | 25 | /*** 26 | * 请求接口列表 27 | */ 28 | private List requestNodes=new ArrayList<>(); 29 | 30 | 31 | 32 | public List getRequestNodes() { 33 | return requestNodes; 34 | } 35 | 36 | public void setRequestNodes(List requestNodes) { 37 | this.requestNodes = requestNodes; 38 | } 39 | 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | public void setDescription(String description) { 54 | this.description = description; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/model/VariableNode.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.model; 2 | 3 | /** 4 | * 全局变量节点 5 | */ 6 | public class VariableNode { 7 | 8 | /** 9 | * 变量名称 10 | */ 11 | private String name; 12 | 13 | /** 14 | * 变量值 15 | */ 16 | private String value; 17 | 18 | /** 19 | * 变量描述 20 | */ 21 | private String description; 22 | 23 | public VariableNode(String name, String value, String description) { 24 | this.name = name; 25 | this.value = value; 26 | this.description = description; 27 | } 28 | 29 | public VariableNode(){} 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | public String getValue() { 40 | return value; 41 | } 42 | 43 | public void setValue(String value) { 44 | this.value = value; 45 | } 46 | 47 | public String getDescription() { 48 | return description; 49 | } 50 | 51 | public void setDescription(String description) { 52 | this.description = description; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/parse/AuthParser.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.parse; 2 | 3 | import io.swagger.v3.parser.core.models.AuthorizationValue; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URLDecoder; 9 | import java.net.URLEncoder; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static org.apache.commons.lang3.StringUtils.isNotEmpty; 14 | 15 | /** 16 | * @author yunlong.liu 17 | * @date 2020-11-04 15:52:31 18 | */ 19 | 20 | public class AuthParser { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(AuthParser.class); 23 | 24 | public static List parse(String urlEncodedAuthStr) { 25 | List auths = new ArrayList(); 26 | if (isNotEmpty(urlEncodedAuthStr)) { 27 | String[] parts = urlEncodedAuthStr.split(","); 28 | for (String part : parts) { 29 | String[] kvPair = part.split(":"); 30 | if (kvPair.length == 2) { 31 | try { 32 | auths.add(new AuthorizationValue(URLDecoder.decode(kvPair[0], "UTF-8"), URLDecoder.decode(kvPair[1], "UTF-8"), "header")); 33 | } catch (UnsupportedEncodingException e) { 34 | LOGGER.warn(e.getMessage()); 35 | } 36 | } 37 | } 38 | } 39 | return auths; 40 | } 41 | 42 | public static String reconstruct(List authorizationValueList) { 43 | if (authorizationValueList != null) { 44 | StringBuilder b = new StringBuilder(); 45 | for (AuthorizationValue v : authorizationValueList) { 46 | try { 47 | if (b.toString().length() > 0) { 48 | b.append(","); 49 | } 50 | b.append(URLEncoder.encode(v.getKeyName(), "UTF-8")) 51 | .append(":") 52 | .append(URLEncoder.encode(v.getValue(), "UTF-8")); 53 | } catch (Exception e) { 54 | // continue 55 | LOGGER.error(e.getMessage(), e); 56 | } 57 | } 58 | return b.toString(); 59 | } else { 60 | return null; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/parse/SwaggerParser.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.parse; 2 | 3 | import io.swagger.parser.OpenAPIParser; 4 | import io.swagger.v3.parser.core.models.AuthorizationValue; 5 | import io.swagger.v3.parser.core.models.ParseOptions; 6 | import io.swagger.v3.parser.core.models.SwaggerParseResult; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author yunlong.liu 14 | * @date 2020-11-04 15:39:24 15 | */ 16 | 17 | public class SwaggerParser { 18 | 19 | 20 | private static final Logger log= LoggerFactory.getLogger(SwaggerParser.class); 21 | 22 | public SwaggerParseResult readWithInfo(String location,String auth) { 23 | final List authorizationValues = AuthParser.parse(auth); 24 | ParseOptions options = new ParseOptions(); 25 | options.setResolve(true); 26 | SwaggerParseResult result = new OpenAPIParser().readLocation(location, authorizationValues, options); 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/utils/ModelUtils.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.utils; 2 | 3 | import io.swagger.v3.oas.models.OpenAPI; 4 | import io.swagger.v3.oas.models.Operation; 5 | import io.swagger.v3.oas.models.PathItem; 6 | import io.swagger.v3.oas.models.callbacks.Callback; 7 | import io.swagger.v3.oas.models.headers.Header; 8 | import io.swagger.v3.oas.models.media.*; 9 | import io.swagger.v3.oas.models.parameters.Parameter; 10 | import io.swagger.v3.oas.models.parameters.RequestBody; 11 | import io.swagger.v3.oas.models.responses.ApiResponse; 12 | import io.swagger.v3.parser.util.SchemaTypeUtil; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | public class ModelUtils { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(ModelUtils.class); 23 | 24 | private static final String URI_FORMAT = "uri"; 25 | 26 | private static final String generateAliasAsModelKey = "generateAliasAsModel"; 27 | 28 | 29 | 30 | 31 | /** 32 | * Return the list of all schemas in the 'components/schemas' section used in the openAPI specification 33 | * 34 | * @param openAPI specification 35 | * @return schemas a list of used schemas 36 | */ 37 | public static List getAllUsedSchemas(OpenAPI openAPI) { 38 | Map> childrenMap = getChildrenMap(openAPI); 39 | List allUsedSchemas = new ArrayList(); 40 | visitOpenAPI(openAPI, (s, t) -> { 41 | if (s.get$ref() != null) { 42 | String ref = getSimpleRef(s.get$ref()); 43 | if (!allUsedSchemas.contains(ref)) { 44 | allUsedSchemas.add(ref); 45 | } 46 | if (childrenMap.containsKey(ref)) { 47 | for (String child : childrenMap.get(ref)) { 48 | if (!allUsedSchemas.contains(child)) { 49 | allUsedSchemas.add(child); 50 | } 51 | } 52 | } 53 | } 54 | }); 55 | return allUsedSchemas; 56 | } 57 | 58 | /** 59 | * Return the list of unused schemas in the 'components/schemas' section of an openAPI specification 60 | * 61 | * @param openAPI specification 62 | * @return schemas a list of unused schemas 63 | */ 64 | public static List getUnusedSchemas(OpenAPI openAPI) { 65 | final Map> childrenMap; 66 | Map> tmpChildrenMap; 67 | try { 68 | tmpChildrenMap = getChildrenMap(openAPI); 69 | } catch (NullPointerException npe) { 70 | // in rare cases, such as a spec document with only one top-level oneOf schema and multiple referenced schemas, 71 | // the stream used in getChildrenMap will raise an NPE. Rather than modify getChildrenMap which is used by getAllUsedSchemas, 72 | // we'll catch here as a workaround for this edge case. 73 | tmpChildrenMap = new HashMap<>(); 74 | } 75 | 76 | childrenMap = tmpChildrenMap; 77 | List unusedSchemas = new ArrayList(); 78 | 79 | Map schemas = getSchemas(openAPI); 80 | unusedSchemas.addAll(schemas.keySet()); 81 | 82 | visitOpenAPI(openAPI, (s, t) -> { 83 | if (s.get$ref() != null) { 84 | String ref = getSimpleRef(s.get$ref()); 85 | unusedSchemas.remove(ref); 86 | if (childrenMap.containsKey(ref)) { 87 | unusedSchemas.removeAll(childrenMap.get(ref)); 88 | } 89 | } 90 | }); 91 | return unusedSchemas; 92 | } 93 | 94 | /** 95 | * Return the list of schemas in the 'components/schemas' used only in a 'application/x-www-form-urlencoded' or 'multipart/form-data' mime time 96 | * 97 | * @param openAPI specification 98 | * @return schemas a list of schemas 99 | */ 100 | public static List getSchemasUsedOnlyInFormParam(OpenAPI openAPI) { 101 | List schemasUsedInFormParam = new ArrayList(); 102 | List schemasUsedInOtherCases = new ArrayList(); 103 | 104 | visitOpenAPI(openAPI, (s, t) -> { 105 | if (s.get$ref() != null) { 106 | String ref = getSimpleRef(s.get$ref()); 107 | if ("application/x-www-form-urlencoded".equalsIgnoreCase(t) || 108 | "multipart/form-data".equalsIgnoreCase(t)) { 109 | schemasUsedInFormParam.add(ref); 110 | } else { 111 | schemasUsedInOtherCases.add(ref); 112 | } 113 | } 114 | }); 115 | return schemasUsedInFormParam.stream().filter(n -> !schemasUsedInOtherCases.contains(n)).collect(Collectors.toList()); 116 | } 117 | 118 | /** 119 | * Private method used by several methods ({@link #getAllUsedSchemas(OpenAPI)}, 120 | * {@link #getUnusedSchemas(OpenAPI)}, 121 | * {@link #getSchemasUsedOnlyInFormParam(OpenAPI)}, ...) to traverse all paths of an 122 | * OpenAPI instance and call the visitor functional interface when a schema is found. 123 | * 124 | * @param openAPI specification 125 | * @param visitor functional interface (can be defined as a lambda) called each time a schema is found. 126 | */ 127 | private static void visitOpenAPI(OpenAPI openAPI, OpenAPISchemaVisitor visitor) { 128 | Map paths = openAPI.getPaths(); 129 | List visitedSchemas = new ArrayList<>(); 130 | 131 | if (paths != null) { 132 | for (PathItem path : paths.values()) { 133 | visitPathItem(path, openAPI, visitor, visitedSchemas); 134 | } 135 | } 136 | } 137 | 138 | private static void visitPathItem(PathItem pathItem, OpenAPI openAPI, OpenAPISchemaVisitor visitor, List visitedSchemas) { 139 | List allOperations = pathItem.readOperations(); 140 | if (allOperations != null) { 141 | for (Operation operation : allOperations) { 142 | //Params: 143 | visitParameters(openAPI, operation.getParameters(), visitor, visitedSchemas); 144 | 145 | //RequestBody: 146 | RequestBody requestBody = getReferencedRequestBody(openAPI, operation.getRequestBody()); 147 | if (requestBody != null) { 148 | visitContent(openAPI, requestBody.getContent(), visitor, visitedSchemas); 149 | } 150 | 151 | //Responses: 152 | if (operation.getResponses() != null) { 153 | for (ApiResponse r : operation.getResponses().values()) { 154 | ApiResponse apiResponse = getReferencedApiResponse(openAPI, r); 155 | if (apiResponse != null) { 156 | visitContent(openAPI, apiResponse.getContent(), visitor, visitedSchemas); 157 | if (apiResponse.getHeaders() != null) { 158 | for (Map.Entry e : apiResponse.getHeaders().entrySet()) { 159 | Header header = getReferencedHeader(openAPI, e.getValue()); 160 | if (header.getSchema() != null) { 161 | visitSchema(openAPI, header.getSchema(), e.getKey(), visitedSchemas, visitor); 162 | } 163 | visitContent(openAPI, header.getContent(), visitor, visitedSchemas); 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | //Callbacks: 171 | if (operation.getCallbacks() != null) { 172 | for (Callback c : operation.getCallbacks().values()) { 173 | Callback callback = getReferencedCallback(openAPI, c); 174 | if (callback != null) { 175 | for (PathItem p : callback.values()) { 176 | visitPathItem(p, openAPI, visitor, visitedSchemas); 177 | } 178 | } 179 | } 180 | } 181 | } 182 | } 183 | //Params: 184 | visitParameters(openAPI, pathItem.getParameters(), visitor, visitedSchemas); 185 | } 186 | 187 | private static void visitParameters(OpenAPI openAPI, List parameters, OpenAPISchemaVisitor visitor, 188 | List visitedSchemas) { 189 | if (parameters != null) { 190 | for (Parameter p : parameters) { 191 | Parameter parameter = getReferencedParameter(openAPI, p); 192 | if (parameter != null) { 193 | if (parameter.getSchema() != null) { 194 | visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor); 195 | } 196 | visitContent(openAPI, parameter.getContent(), visitor, visitedSchemas); 197 | } else { 198 | LOGGER.warn("Unreferenced parameter(s) found."); 199 | } 200 | } 201 | } 202 | } 203 | 204 | private static void visitContent(OpenAPI openAPI, Content content, OpenAPISchemaVisitor visitor, List visitedSchemas) { 205 | if (content != null) { 206 | for (Map.Entry e : content.entrySet()) { 207 | if (e.getValue().getSchema() != null) { 208 | visitSchema(openAPI, e.getValue().getSchema(), e.getKey(), visitedSchemas, visitor); 209 | } 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * Invoke the specified visitor function for every schema that matches mimeType in the OpenAPI document. 216 | * 217 | * To avoid infinite recursion, referenced schemas are visited only once. When a referenced schema is visited, 218 | * it is added to visitedSchemas. 219 | * 220 | * @param openAPI the OpenAPI document that contains schema objects. 221 | * @param schema the root schema object to be visited. 222 | * @param mimeType the mime type. TODO: does not seem to be used in a meaningful way. 223 | * @param visitedSchemas the list of referenced schemas that have been visited. 224 | * @param visitor the visitor function which is invoked for every visited schema. 225 | */ 226 | private static void visitSchema(OpenAPI openAPI, Schema schema, String mimeType, List visitedSchemas, OpenAPISchemaVisitor visitor) { 227 | visitor.visit(schema, mimeType); 228 | if (schema.get$ref() != null) { 229 | String ref = getSimpleRef(schema.get$ref()); 230 | if (!visitedSchemas.contains(ref)) { 231 | visitedSchemas.add(ref); 232 | Schema referencedSchema = getSchemas(openAPI).get(ref); 233 | if (referencedSchema != null) { 234 | visitSchema(openAPI, referencedSchema, mimeType, visitedSchemas, visitor); 235 | } 236 | } 237 | } 238 | if (schema instanceof ComposedSchema) { 239 | List oneOf = ((ComposedSchema) schema).getOneOf(); 240 | if (oneOf != null) { 241 | for (Schema s : oneOf) { 242 | visitSchema(openAPI, s, mimeType, visitedSchemas, visitor); 243 | } 244 | } 245 | List allOf = ((ComposedSchema) schema).getAllOf(); 246 | if (allOf != null) { 247 | for (Schema s : allOf) { 248 | visitSchema(openAPI, s, mimeType, visitedSchemas, visitor); 249 | } 250 | } 251 | List anyOf = ((ComposedSchema) schema).getAnyOf(); 252 | if (anyOf != null) { 253 | for (Schema s : anyOf) { 254 | visitSchema(openAPI, s, mimeType, visitedSchemas, visitor); 255 | } 256 | } 257 | } else if (schema instanceof ArraySchema) { 258 | Schema itemsSchema = ((ArraySchema) schema).getItems(); 259 | if (itemsSchema != null) { 260 | visitSchema(openAPI, itemsSchema, mimeType, visitedSchemas, visitor); 261 | } 262 | } else if (isMapSchema(schema)) { 263 | Object additionalProperties = schema.getAdditionalProperties(); 264 | if (additionalProperties instanceof Schema) { 265 | visitSchema(openAPI, (Schema) additionalProperties, mimeType, visitedSchemas, visitor); 266 | } 267 | } 268 | if (schema.getNot() != null) { 269 | visitSchema(openAPI, schema.getNot(), mimeType, visitedSchemas, visitor); 270 | } 271 | Map properties = schema.getProperties(); 272 | if (properties != null) { 273 | for (Schema property : properties.values()) { 274 | visitSchema(openAPI, property, mimeType, visitedSchemas, visitor); 275 | } 276 | } 277 | } 278 | 279 | @FunctionalInterface 280 | private static interface OpenAPISchemaVisitor { 281 | 282 | public void visit(Schema schema, String mimeType); 283 | } 284 | 285 | public static String getSimpleRef(String ref) { 286 | if (ref.startsWith("#/components/")) { 287 | ref = ref.substring(ref.lastIndexOf("/") + 1); 288 | } else if (ref.startsWith("#/definitions/")) { 289 | ref = ref.substring(ref.lastIndexOf("/") + 1); 290 | } else { 291 | LOGGER.warn("Failed to get the schema name: {}", ref); 292 | //throw new RuntimeException("Failed to get the schema: " + ref); 293 | return null; 294 | 295 | } 296 | 297 | return ref; 298 | } 299 | 300 | /** 301 | * Return true if the specified schema is an object with a fixed number of properties. 302 | * 303 | * A ObjectSchema differs from an MapSchema in the following way: 304 | * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties. 305 | * - A MapSchema is an object that can be extended with an arbitrary set of properties. 306 | * The payload may include dynamic properties. 307 | * 308 | * For example, an OpenAPI schema is considered an ObjectSchema in the following scenarios: 309 | * 310 | * type: object 311 | * additionalProperties: false 312 | * properties: 313 | * name: 314 | * type: string 315 | * address: 316 | * type: string 317 | * 318 | * @param schema the OAS schema 319 | * @return true if the specified schema is an Object schema. 320 | */ 321 | public static boolean isObjectSchema(Schema schema) { 322 | if (schema instanceof ObjectSchema) { 323 | return true; 324 | } 325 | 326 | // must not be a map 327 | if (SchemaTypeUtil.OBJECT_TYPE.equals(schema.getType()) && !(schema instanceof MapSchema)) { 328 | return true; 329 | } 330 | 331 | // must have at least one property 332 | if (schema.getType() == null && schema.getProperties() != null && !schema.getProperties().isEmpty()) { 333 | return true; 334 | } 335 | return false; 336 | } 337 | 338 | /** 339 | * Return true if the specified schema is composed, i.e. if it uses 340 | * 'oneOf', 'anyOf' or 'allOf'. 341 | * 342 | * @param schema the OAS schema 343 | * @return true if the specified schema is a Composed schema. 344 | */ 345 | public static boolean isComposedSchema(Schema schema) { 346 | if (schema instanceof ComposedSchema) { 347 | return true; 348 | } 349 | return false; 350 | } 351 | 352 | /** 353 | * Return true if the specified 'schema' is an object that can be extended with additional properties. 354 | * Additional properties means a Schema should support all explicitly defined properties plus any 355 | * undeclared properties. 356 | * 357 | * A MapSchema differs from an ObjectSchema in the following way: 358 | * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties. 359 | * - A MapSchema is an object that can be extended with an arbitrary set of properties. 360 | * The payload may include dynamic properties. 361 | * 362 | * Note that isMapSchema returns true for a composed schema (allOf, anyOf, oneOf) that also defines 363 | * additionalproperties. 364 | * 365 | * For example, an OpenAPI schema is considered a MapSchema in the following scenarios: 366 | * 367 | * type: object 368 | * additionalProperties: true 369 | * 370 | * type: object 371 | * additionalProperties: 372 | * type: object 373 | * properties: 374 | * code: 375 | * type: integer 376 | * 377 | * allOf: 378 | * - $ref: '#/components/schemas/Class1' 379 | * - $ref: '#/components/schemas/Class2' 380 | * additionalProperties: true 381 | * 382 | * @param schema the OAS schema 383 | * @return true if the specified schema is a Map schema. 384 | */ 385 | public static boolean isMapSchema(Schema schema) { 386 | if (schema instanceof MapSchema) { 387 | return true; 388 | } 389 | 390 | if (schema == null) { 391 | return false; 392 | } 393 | 394 | if (schema.getAdditionalProperties() instanceof Schema) { 395 | return true; 396 | } 397 | 398 | if (schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties()) { 399 | return true; 400 | } 401 | 402 | return false; 403 | } 404 | 405 | /** 406 | * Return true if the specified schema is an array of items. 407 | * 408 | * @param schema the OAS schema 409 | * @return true if the specified schema is an Array schema. 410 | */ 411 | public static boolean isArraySchema(Schema schema) { 412 | return (schema instanceof ArraySchema); 413 | } 414 | 415 | public static boolean isSet(Schema schema) { 416 | return ModelUtils.isArraySchema(schema) && Boolean.TRUE.equals(schema.getUniqueItems()); 417 | } 418 | 419 | public static boolean isStringSchema(Schema schema) { 420 | if (schema instanceof StringSchema || SchemaTypeUtil.STRING_TYPE.equals(schema.getType())) { 421 | return true; 422 | } 423 | return false; 424 | } 425 | 426 | public static boolean isIntegerSchema(Schema schema) { 427 | if (schema instanceof IntegerSchema) { 428 | return true; 429 | } 430 | if (SchemaTypeUtil.INTEGER_TYPE.equals(schema.getType())) { 431 | return true; 432 | } 433 | return false; 434 | } 435 | 436 | public static boolean isShortSchema(Schema schema) { 437 | if (SchemaTypeUtil.INTEGER_TYPE.equals(schema.getType()) // type: integer 438 | && SchemaTypeUtil.INTEGER32_FORMAT.equals(schema.getFormat())) { // format: short (int32) 439 | return true; 440 | } 441 | return false; 442 | } 443 | 444 | public static boolean isLongSchema(Schema schema) { 445 | if (SchemaTypeUtil.INTEGER_TYPE.equals(schema.getType()) // type: integer 446 | && SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) { // format: long (int64) 447 | return true; 448 | } 449 | return false; 450 | } 451 | 452 | public static boolean isBooleanSchema(Schema schema) { 453 | if (schema instanceof BooleanSchema) { 454 | return true; 455 | } 456 | if (SchemaTypeUtil.BOOLEAN_TYPE.equals(schema.getType())) { 457 | return true; 458 | } 459 | return false; 460 | } 461 | 462 | public static boolean isNumberSchema(Schema schema) { 463 | if (schema instanceof NumberSchema) { 464 | return true; 465 | } 466 | if (SchemaTypeUtil.NUMBER_TYPE.equals(schema.getType())) { 467 | return true; 468 | } 469 | return false; 470 | } 471 | 472 | public static boolean isFloatSchema(Schema schema) { 473 | if (SchemaTypeUtil.NUMBER_TYPE.equals(schema.getType()) 474 | && SchemaTypeUtil.FLOAT_FORMAT.equals(schema.getFormat())) { // format: float 475 | return true; 476 | } 477 | return false; 478 | } 479 | 480 | public static boolean isDoubleSchema(Schema schema) { 481 | if (SchemaTypeUtil.NUMBER_TYPE.equals(schema.getType()) 482 | && SchemaTypeUtil.DOUBLE_FORMAT.equals(schema.getFormat())) { // format: double 483 | return true; 484 | } 485 | return false; 486 | } 487 | 488 | public static boolean isDateSchema(Schema schema) { 489 | if (schema instanceof DateSchema) { 490 | return true; 491 | } 492 | 493 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 494 | && SchemaTypeUtil.DATE_FORMAT.equals(schema.getFormat())) { // format: date 495 | return true; 496 | } 497 | return false; 498 | } 499 | 500 | public static boolean isDateTimeSchema(Schema schema) { 501 | if (schema instanceof DateTimeSchema) { 502 | return true; 503 | } 504 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 505 | && SchemaTypeUtil.DATE_TIME_FORMAT.equals(schema.getFormat())) { // format: date-time 506 | return true; 507 | } 508 | return false; 509 | } 510 | 511 | public static boolean isPasswordSchema(Schema schema) { 512 | if (schema instanceof PasswordSchema) { 513 | return true; 514 | } 515 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 516 | && SchemaTypeUtil.PASSWORD_FORMAT.equals(schema.getFormat())) { // double 517 | return true; 518 | } 519 | return false; 520 | } 521 | 522 | public static boolean isByteArraySchema(Schema schema) { 523 | if (schema instanceof ByteArraySchema) { 524 | return true; 525 | } 526 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 527 | && SchemaTypeUtil.BYTE_FORMAT.equals(schema.getFormat())) { // format: byte 528 | return true; 529 | } 530 | return false; 531 | } 532 | 533 | public static boolean isBinarySchema(Schema schema) { 534 | if (schema instanceof BinarySchema) { 535 | return true; 536 | } 537 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 538 | && SchemaTypeUtil.BINARY_FORMAT.equals(schema.getFormat())) { // format: binary 539 | return true; 540 | } 541 | return false; 542 | } 543 | 544 | public static boolean isFileSchema(Schema schema) { 545 | if (schema instanceof FileSchema) { 546 | return true; 547 | } 548 | // file type in oas2 mapped to binary in oas3 549 | return isBinarySchema(schema); 550 | } 551 | 552 | public static boolean isUUIDSchema(Schema schema) { 553 | if (schema instanceof UUIDSchema) { 554 | return true; 555 | } 556 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 557 | && SchemaTypeUtil.UUID_FORMAT.equals(schema.getFormat())) { // format: uuid 558 | return true; 559 | } 560 | return false; 561 | } 562 | 563 | public static boolean isURISchema(Schema schema) { 564 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 565 | && URI_FORMAT.equals(schema.getFormat())) { // format: uri 566 | return true; 567 | } 568 | return false; 569 | } 570 | 571 | public static boolean isEmailSchema(Schema schema) { 572 | if (schema instanceof EmailSchema) { 573 | return true; 574 | } 575 | if (SchemaTypeUtil.STRING_TYPE.equals(schema.getType()) 576 | && SchemaTypeUtil.EMAIL_FORMAT.equals(schema.getFormat())) { // format: email 577 | return true; 578 | } 579 | return false; 580 | } 581 | 582 | /** 583 | * Check to see if the schema is a model with at least one property. 584 | * 585 | * @param schema potentially containing a '$ref' 586 | * @return true if it's a model with at least one properties 587 | */ 588 | public static boolean isModel(Schema schema) { 589 | if (schema == null) { 590 | // TODO: Is this message necessary? A null schema is not a model, so the result is correct. 591 | LOGGER.error("Schema cannot be null in isModel check"); 592 | return false; 593 | } 594 | 595 | // has at least one property 596 | if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { 597 | return true; 598 | } 599 | 600 | // composed schema is a model 601 | return schema instanceof ComposedSchema; 602 | } 603 | 604 | /** 605 | * Return true if the schema value can be any type, i.e. it can be 606 | * the null value, integer, number, string, object or array. 607 | * One use case is when the "type" attribute in the OAS schema is unspecified. 608 | * 609 | * Examples: 610 | * 611 | * arbitraryTypeValue: 612 | * description: This is an arbitrary type schema. 613 | * It is not a free-form object. 614 | * The value can be any type except the 'null' value. 615 | * arbitraryTypeNullableValue: 616 | * description: This is an arbitrary type schema. 617 | * It is not a free-form object. 618 | * The value can be any type, including the 'null' value. 619 | * nullable: true 620 | * 621 | * @param schema the OAS schema. 622 | * @return true if the schema value can be an arbitrary type. 623 | */ 624 | public static boolean isAnyTypeSchema(Schema schema) { 625 | if (schema == null) { 626 | LOGGER.error("Schema cannot be null in isAnyTypeSchema check"); 627 | return false; 628 | } 629 | 630 | if (isFreeFormObject(schema)) { 631 | // make sure it's not free form object 632 | return false; 633 | } 634 | 635 | if (schema.getClass().equals(Schema.class) && schema.get$ref() == null && schema.getType() == null && 636 | (schema.getProperties() == null || schema.getProperties().isEmpty()) && 637 | schema.getAdditionalProperties() == null && schema.getNot() == null && 638 | schema.getEnum() == null) { 639 | return true; 640 | // If and when type arrays are supported in a future OAS specification, 641 | // we could return true if the type array includes all possible JSON schema types. 642 | } 643 | return false; 644 | } 645 | 646 | /** 647 | * Check to see if the schema is a free form object. 648 | * 649 | * A free form object is an object (i.e. 'type: object' in a OAS document) that: 650 | * 1) Does not define properties, and 651 | * 2) Is not a composed schema (no anyOf, oneOf, allOf), and 652 | * 3) additionalproperties is not defined, or additionalproperties: true, or additionalproperties: {}. 653 | * 654 | * Examples: 655 | * 656 | * components: 657 | * schemas: 658 | * arbitraryObject: 659 | * type: object 660 | * description: This is a free-form object. 661 | * The value must be a map of strings to values. The value cannot be 'null'. 662 | * It cannot be array, string, integer, number. 663 | * arbitraryNullableObject: 664 | * type: object 665 | * description: This is a free-form object. 666 | * The value must be a map of strings to values. The value can be 'null', 667 | * It cannot be array, string, integer, number. 668 | * nullable: true 669 | * arbitraryTypeValue: 670 | * description: This is NOT a free-form object. 671 | * The value can be any type except the 'null' value. 672 | * 673 | * @param schema potentially containing a '$ref' 674 | * @return true if it's a free-form object 675 | */ 676 | public static boolean isFreeFormObject(Schema schema) { 677 | if (schema == null) { 678 | // TODO: Is this message necessary? A null schema is not a free-form object, so the result is correct. 679 | LOGGER.error("Schema cannot be null in isFreeFormObject check"); 680 | return false; 681 | } 682 | 683 | // not free-form if allOf, anyOf, oneOf is not empty 684 | if (schema instanceof ComposedSchema) { 685 | ComposedSchema cs = (ComposedSchema) schema; 686 | List interfaces = getInterfaces(cs); 687 | if (interfaces != null && !interfaces.isEmpty()) { 688 | return false; 689 | } 690 | } 691 | 692 | // has at least one property 693 | if ("object".equals(schema.getType())) { 694 | // no properties 695 | if ((schema.getProperties() == null || schema.getProperties().isEmpty())) { 696 | Schema addlProps = getAdditionalProperties(schema); 697 | // additionalProperties not defined 698 | if (addlProps == null) { 699 | return true; 700 | } else { 701 | if (addlProps instanceof ObjectSchema) { 702 | ObjectSchema objSchema = (ObjectSchema) addlProps; 703 | // additionalProperties defined as {} 704 | if (objSchema.getProperties() == null || objSchema.getProperties().isEmpty()) { 705 | return true; 706 | } 707 | } else if (addlProps instanceof Schema) { 708 | // additionalProperties defined as {} 709 | if (addlProps.getType() == null && (addlProps.getProperties() == null || addlProps.getProperties().isEmpty())) { 710 | return true; 711 | } 712 | } 713 | } 714 | } 715 | } 716 | 717 | return false; 718 | } 719 | 720 | /** 721 | * If a Schema contains a reference to another Schema with '$ref', returns the referenced Schema if it is found or the actual Schema in the other cases. 722 | * 723 | * @param openAPI specification being checked 724 | * @param schema potentially containing a '$ref' 725 | * @return schema without '$ref' 726 | */ 727 | public static Schema getReferencedSchema(OpenAPI openAPI, Schema schema) { 728 | if (schema != null && StringUtils.isNotEmpty(schema.get$ref())) { 729 | String name = getSimpleRef(schema.get$ref()); 730 | Schema referencedSchema = getSchema(openAPI, name); 731 | if (referencedSchema != null) { 732 | return referencedSchema; 733 | } 734 | } 735 | return schema; 736 | } 737 | 738 | public static Schema getSchema(OpenAPI openAPI, String name) { 739 | if (name == null) { 740 | return null; 741 | } 742 | 743 | return getSchemas(openAPI).get(name); 744 | } 745 | 746 | /** 747 | * Return a Map of the schemas defined under /components/schemas in the OAS document. 748 | * The returned Map only includes the direct children of /components/schemas in the OAS document; the Map 749 | * does not include inlined schemas. 750 | * 751 | * @param openAPI the OpenAPI document. 752 | * @return a map of schemas in the OAS document. 753 | */ 754 | public static Map getSchemas(OpenAPI openAPI) { 755 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getSchemas() != null) { 756 | return openAPI.getComponents().getSchemas(); 757 | } 758 | return Collections.emptyMap(); 759 | } 760 | 761 | /** 762 | * Return the list of all schemas in the 'components/schemas' section of an openAPI specification, 763 | * including inlined schemas and children of composed schemas. 764 | * 765 | * @param openAPI OpenAPI document 766 | * @return a list of schemas 767 | */ 768 | public static List getAllSchemas(OpenAPI openAPI) { 769 | List allSchemas = new ArrayList(); 770 | List refSchemas = new ArrayList(); 771 | getSchemas(openAPI).forEach((key, schema) -> { 772 | // Invoke visitSchema to recursively visit all schema objects, included inlined and composed schemas. 773 | // Use the OpenAPISchemaVisitor visitor function 774 | visitSchema(openAPI, schema, null, refSchemas, (s, mimetype) -> { 775 | allSchemas.add(s); 776 | }); 777 | }); 778 | return allSchemas; 779 | } 780 | 781 | /** 782 | * If a RequestBody contains a reference to an other RequestBody with '$ref', returns the referenced RequestBody if it is found or the actual RequestBody in the other cases. 783 | * 784 | * @param openAPI specification being checked 785 | * @param requestBody potentially containing a '$ref' 786 | * @return requestBody without '$ref' 787 | */ 788 | public static RequestBody getReferencedRequestBody(OpenAPI openAPI, RequestBody requestBody) { 789 | if (requestBody != null && StringUtils.isNotEmpty(requestBody.get$ref())) { 790 | String name = getSimpleRef(requestBody.get$ref()); 791 | RequestBody referencedRequestBody = getRequestBody(openAPI, name); 792 | if (referencedRequestBody != null) { 793 | return referencedRequestBody; 794 | } 795 | } 796 | return requestBody; 797 | } 798 | 799 | public static RequestBody getRequestBody(OpenAPI openAPI, String name) { 800 | if (name == null) { 801 | return null; 802 | } 803 | 804 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getRequestBodies() != null) { 805 | return openAPI.getComponents().getRequestBodies().get(name); 806 | } 807 | return null; 808 | } 809 | 810 | /** 811 | * If a ApiResponse contains a reference to an other ApiResponse with '$ref', returns the referenced ApiResponse if it is found or the actual ApiResponse in the other cases. 812 | * 813 | * @param openAPI specification being checked 814 | * @param apiResponse potentially containing a '$ref' 815 | * @return apiResponse without '$ref' 816 | */ 817 | public static ApiResponse getReferencedApiResponse(OpenAPI openAPI, ApiResponse apiResponse) { 818 | if (apiResponse != null && StringUtils.isNotEmpty(apiResponse.get$ref())) { 819 | String name = getSimpleRef(apiResponse.get$ref()); 820 | ApiResponse referencedApiResponse = getApiResponse(openAPI, name); 821 | if (referencedApiResponse != null) { 822 | return referencedApiResponse; 823 | } 824 | } 825 | return apiResponse; 826 | } 827 | 828 | public static ApiResponse getApiResponse(OpenAPI openAPI, String name) { 829 | if (name == null) { 830 | return null; 831 | } 832 | 833 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getResponses() != null) { 834 | return openAPI.getComponents().getResponses().get(name); 835 | } 836 | return null; 837 | } 838 | 839 | /** 840 | * If a Parameter contains a reference to an other Parameter with '$ref', returns the referenced Parameter if it is found or the actual Parameter in the other cases. 841 | * 842 | * @param openAPI specification being checked 843 | * @param parameter potentially containing a '$ref' 844 | * @return parameter without '$ref' 845 | */ 846 | public static Parameter getReferencedParameter(OpenAPI openAPI, Parameter parameter) { 847 | if (parameter != null && StringUtils.isNotEmpty(parameter.get$ref())) { 848 | String name = getSimpleRef(parameter.get$ref()); 849 | Parameter referencedParameter = getParameter(openAPI, name); 850 | if (referencedParameter != null) { 851 | return referencedParameter; 852 | } 853 | } 854 | return parameter; 855 | } 856 | 857 | public static Parameter getParameter(OpenAPI openAPI, String name) { 858 | if (name == null) { 859 | return null; 860 | } 861 | 862 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getParameters() != null) { 863 | return openAPI.getComponents().getParameters().get(name); 864 | } 865 | return null; 866 | } 867 | 868 | /** 869 | * If a Callback contains a reference to an other Callback with '$ref', returns the referenced Callback if it is found or the actual Callback in the other cases. 870 | * 871 | * @param openAPI specification being checked 872 | * @param callback potentially containing a '$ref' 873 | * @return callback without '$ref' 874 | */ 875 | public static Callback getReferencedCallback(OpenAPI openAPI, Callback callback) { 876 | if (callback != null && StringUtils.isNotEmpty(callback.get$ref())) { 877 | String name = getSimpleRef(callback.get$ref()); 878 | Callback referencedCallback = getCallback(openAPI, name); 879 | if (referencedCallback != null) { 880 | return referencedCallback; 881 | } 882 | } 883 | return callback; 884 | } 885 | 886 | public static Callback getCallback(OpenAPI openAPI, String name) { 887 | if (name == null) { 888 | return null; 889 | } 890 | 891 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getCallbacks() != null) { 892 | return openAPI.getComponents().getCallbacks().get(name); 893 | } 894 | return null; 895 | } 896 | 897 | /** 898 | * Return the first defined Schema for a RequestBody 899 | * 900 | * @param requestBody request body of the operation 901 | * @return firstSchema 902 | */ 903 | public static Schema getSchemaFromRequestBody(RequestBody requestBody) { 904 | return getSchemaFromContent(requestBody.getContent()); 905 | } 906 | 907 | /** 908 | * Return the first defined Schema for a ApiResponse 909 | * 910 | * @param response api response of the operation 911 | * @return firstSchema 912 | */ 913 | public static Schema getSchemaFromResponse(ApiResponse response) { 914 | return getSchemaFromContent(response.getContent()); 915 | } 916 | 917 | /** 918 | * Return the first Schema from a specified OAS 'content' section. 919 | * 920 | * For example, given the following OAS, this method returns the schema 921 | * for the 'application/json' content type because it is listed first in the OAS. 922 | * 923 | * responses: 924 | * '200': 925 | * content: 926 | * application/json: 927 | * schema: 928 | * $ref: '#/components/schemas/XYZ' 929 | * application/xml: 930 | * ... 931 | * 932 | * @param content a 'content' section in the OAS specification. 933 | * @return the Schema. 934 | */ 935 | private static Schema getSchemaFromContent(Content content) { 936 | if (content == null || content.isEmpty()) { 937 | return null; 938 | } 939 | Map.Entry entry = content.entrySet().iterator().next(); 940 | if (content.size() > 1) { 941 | // Other content types are currently ignored by codegen. If you see this warning, 942 | // reorder the OAS spec to put the desired content type first. 943 | LOGGER.warn("Multiple schemas found in the OAS 'content' section, returning only the first one ({})", 944 | entry.getKey()); 945 | } 946 | return entry.getValue().getSchema(); 947 | } 948 | 949 | 950 | public static Schema getAdditionalProperties(Schema schema) { 951 | if (schema.getAdditionalProperties() instanceof Schema) { 952 | return (Schema) schema.getAdditionalProperties(); 953 | } 954 | if (schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties()) { 955 | return new ObjectSchema(); 956 | } 957 | return null; 958 | } 959 | 960 | public static Header getReferencedHeader(OpenAPI openAPI, Header header) { 961 | if (header != null && StringUtils.isNotEmpty(header.get$ref())) { 962 | String name = getSimpleRef(header.get$ref()); 963 | Header referencedheader = getHeader(openAPI, name); 964 | if (referencedheader != null) { 965 | return referencedheader; 966 | } 967 | } 968 | return header; 969 | } 970 | 971 | public static Header getHeader(OpenAPI openAPI, String name) { 972 | if (name == null) { 973 | return null; 974 | } 975 | 976 | if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getHeaders() != null) { 977 | return openAPI.getComponents().getHeaders().get(name); 978 | } 979 | return null; 980 | } 981 | 982 | public static Map> getChildrenMap(OpenAPI openAPI) { 983 | Map allSchemas = getSchemas(openAPI); 984 | 985 | Map>> groupedByParent = allSchemas.entrySet().stream() 986 | .filter(entry -> isComposedSchema(entry.getValue())) 987 | .filter(entry -> getParentName((ComposedSchema) entry.getValue(), allSchemas)!=null) 988 | .collect(Collectors.groupingBy(entry -> getParentName((ComposedSchema) entry.getValue(), allSchemas))); 989 | 990 | return groupedByParent.entrySet().stream() 991 | .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().stream().map(e -> e.getKey()).collect(Collectors.toList()))); 992 | } 993 | 994 | 995 | /** 996 | * Get the interfaces from the schema (composed) 997 | * 998 | * @param composed schema (alias or direct reference) 999 | * @return a list of schema defined in allOf, anyOf or oneOf 1000 | */ 1001 | public static List getInterfaces(ComposedSchema composed) { 1002 | if (composed.getAllOf() != null && !composed.getAllOf().isEmpty()) { 1003 | return composed.getAllOf(); 1004 | } else if (composed.getAnyOf() != null && !composed.getAnyOf().isEmpty()) { 1005 | return composed.getAnyOf(); 1006 | } else if (composed.getOneOf() != null && !composed.getOneOf().isEmpty()) { 1007 | return composed.getOneOf(); 1008 | } else { 1009 | return Collections.emptyList(); 1010 | } 1011 | } 1012 | 1013 | /** 1014 | * Get the parent model name from the composed schema (allOf, anyOf, oneOf). 1015 | * It traverses the OAS model (possibly resolving $ref) to determine schemas 1016 | * that specify a determinator. 1017 | * If there are multiple elements in the composed schema and it is not clear 1018 | * which one should be the parent, return null. 1019 | * 1020 | * For example, given the following OAS spec, the parent of 'Dog' is Animal 1021 | * because 'Animal' specifies a discriminator. 1022 | * 1023 | * animal: 1024 | * type: object 1025 | * discriminator: 1026 | * propertyName: type 1027 | * properties: 1028 | * type: string 1029 | * 1030 | * dog: 1031 | * allOf: 1032 | * - $ref: '#/components/schemas/animal' 1033 | * - type: object 1034 | * properties: 1035 | * breed: string 1036 | * 1037 | * @param composedSchema schema (alias or direct reference) 1038 | * @param allSchemas all schemas 1039 | * @return the name of the parent model 1040 | */ 1041 | public static String getParentName(ComposedSchema composedSchema, Map allSchemas) { 1042 | List interfaces = getInterfaces(composedSchema); 1043 | int nullSchemaChildrenCount = 0; 1044 | boolean hasAmbiguousParents = false; 1045 | List refedWithoutDiscriminator = new ArrayList<>(); 1046 | 1047 | if (interfaces != null && !interfaces.isEmpty()) { 1048 | for (Schema schema : interfaces) { 1049 | // get the actual schema 1050 | if (StringUtils.isNotEmpty(schema.get$ref())) { 1051 | String parentName = getSimpleRef(schema.get$ref()); 1052 | Schema s = allSchemas.get(parentName); 1053 | if (s == null) { 1054 | LOGGER.error("Failed to obtain schema from {}", parentName); 1055 | return "UNKNOWN_PARENT_NAME"; 1056 | } else if (hasOrInheritsDiscriminator(s, allSchemas)) { 1057 | // discriminator.propertyName is used 1058 | return parentName; 1059 | } else { 1060 | // not a parent since discriminator.propertyName is not set 1061 | hasAmbiguousParents = true; 1062 | refedWithoutDiscriminator.add(parentName); 1063 | } 1064 | } else { 1065 | // not a ref, doing nothing, except counting the number of times the 'null' type 1066 | // is listed as composed element. 1067 | if (ModelUtils.isNullType(schema)) { 1068 | // If there are two interfaces, and one of them is the 'null' type, 1069 | // then the parent is obvious and there is no need to warn about specifying 1070 | // a determinator. 1071 | nullSchemaChildrenCount++; 1072 | } 1073 | } 1074 | } 1075 | if (refedWithoutDiscriminator.size() == 1 && nullSchemaChildrenCount == 1) { 1076 | // One schema is a $ref and the other is the 'null' type, so the parent is obvious. 1077 | // In this particular case there is no need to specify a discriminator. 1078 | hasAmbiguousParents = false; 1079 | } 1080 | } 1081 | 1082 | // parent name only makes sense when there is a single obvious parent 1083 | if (refedWithoutDiscriminator.size() == 1) { 1084 | if (hasAmbiguousParents) { 1085 | LOGGER.warn("[deprecated] inheritance without use of 'discriminator.propertyName' is deprecated " + 1086 | "and will be removed in a future release. Generating model for composed schema name: {}. Title: {}", 1087 | composedSchema.getName(), composedSchema.getTitle()); 1088 | } 1089 | return refedWithoutDiscriminator.get(0); 1090 | } 1091 | 1092 | return null; 1093 | } 1094 | 1095 | /** 1096 | * Get the list of parent model names from the schemas (allOf, anyOf, oneOf). 1097 | * 1098 | * @param composedSchema schema (alias or direct reference) 1099 | * @param allSchemas all schemas 1100 | * @param includeAncestors if true, include the indirect ancestors in the return value. If false, return the direct parents. 1101 | * @return the name of the parent model 1102 | */ 1103 | public static List getAllParentsName(ComposedSchema composedSchema, Map allSchemas, boolean includeAncestors) { 1104 | List interfaces = getInterfaces(composedSchema); 1105 | List names = new ArrayList(); 1106 | 1107 | if (interfaces != null && !interfaces.isEmpty()) { 1108 | for (Schema schema : interfaces) { 1109 | // get the actual schema 1110 | if (StringUtils.isNotEmpty(schema.get$ref())) { 1111 | String parentName = getSimpleRef(schema.get$ref()); 1112 | Schema s = allSchemas.get(parentName); 1113 | if (s == null) { 1114 | LOGGER.error("Failed to obtain schema from {}", parentName); 1115 | names.add("UNKNOWN_PARENT_NAME"); 1116 | } else if (hasOrInheritsDiscriminator(s, allSchemas)) { 1117 | // discriminator.propertyName is used 1118 | names.add(parentName); 1119 | if (includeAncestors && s instanceof ComposedSchema) { 1120 | names.addAll(getAllParentsName((ComposedSchema) s, allSchemas, true)); 1121 | } 1122 | } else { 1123 | // not a parent since discriminator.propertyName is not set 1124 | } 1125 | } else { 1126 | // not a ref, doing nothing 1127 | } 1128 | } 1129 | } 1130 | 1131 | // ensure `allParents` always includes `parent` 1132 | // this is more robust than keeping logic in getParentName() and getAllParentsName() in sync 1133 | String parentName = getParentName(composedSchema, allSchemas); 1134 | if (parentName != null && !names.contains(parentName)) { 1135 | names.add(parentName); 1136 | } 1137 | 1138 | return names; 1139 | } 1140 | 1141 | private static boolean hasOrInheritsDiscriminator(Schema schema, Map allSchemas) { 1142 | if (schema.getDiscriminator() != null && StringUtils.isNotEmpty(schema.getDiscriminator().getPropertyName())) { 1143 | return true; 1144 | } 1145 | else if (StringUtils.isNotEmpty(schema.get$ref())) { 1146 | String parentName = getSimpleRef(schema.get$ref()); 1147 | Schema s = allSchemas.get(parentName); 1148 | if (s != null) { 1149 | return hasOrInheritsDiscriminator(s, allSchemas); 1150 | } else { 1151 | LOGGER.error("Failed to obtain schema from {}", parentName); 1152 | } 1153 | } else if (schema instanceof ComposedSchema) { 1154 | final ComposedSchema composed = (ComposedSchema) schema; 1155 | final List interfaces = getInterfaces(composed); 1156 | for (Schema i : interfaces) { 1157 | if (hasOrInheritsDiscriminator(i, allSchemas)) { 1158 | return true; 1159 | } 1160 | } 1161 | } 1162 | return false; 1163 | } 1164 | 1165 | /** 1166 | * Return true if the 'nullable' attribute is set to true in the schema, i.e. if the value 1167 | * of the property can be the null value. 1168 | * 1169 | * In addition, if the OAS document is 3.1 or above, isNullable returns true if the input 1170 | * schema is a 'oneOf' composed document with at most two children, and one of the children 1171 | * is the 'null' type. 1172 | * 1173 | * The caller is responsible for resolving schema references before invoking isNullable. 1174 | * If the input schema is a $ref and the referenced schema has 'nullable: true', this method 1175 | * returns false (because the nullable attribute is defined in the referenced schema). 1176 | * 1177 | * The 'nullable' attribute was introduced in OAS 3.0. 1178 | * The 'nullable' attribute is deprecated in OAS 3.1. In a OAS 3.1 document, the preferred way 1179 | * to specify nullable properties is to use the 'null' type. 1180 | * 1181 | * @param schema the OAS schema. 1182 | * @return true if the schema is nullable. 1183 | */ 1184 | public static boolean isNullable(Schema schema) { 1185 | if (schema == null) { 1186 | return false; 1187 | } 1188 | 1189 | if (Boolean.TRUE.equals(schema.getNullable())) { 1190 | return true; 1191 | } 1192 | 1193 | if (schema.getExtensions() != null && schema.getExtensions().get("x-nullable") != null) { 1194 | return Boolean.valueOf(schema.getExtensions().get("x-nullable").toString()); 1195 | } 1196 | // In OAS 3.1, the recommended way to define a nullable property or object is to use oneOf. 1197 | if (schema instanceof ComposedSchema) { 1198 | return isNullableComposedSchema(((ComposedSchema) schema)); 1199 | } 1200 | return false; 1201 | } 1202 | 1203 | /** 1204 | * Return true if the specified composed schema is 'oneOf', contains one or two elements, 1205 | * and at least one of the elements is the 'null' type. 1206 | * 1207 | * The 'null' type is supported in OAS 3.1 and above. 1208 | * In the example below, the 'OptionalOrder' can have the null value because the 'null' 1209 | * type is one of the elements under 'oneOf'. 1210 | * 1211 | * OptionalOrder: 1212 | * oneOf: 1213 | * - type: 'null' 1214 | * - $ref: '#/components/schemas/Order' 1215 | * 1216 | * @param schema the OAS composed schema. 1217 | * @return true if the composed schema is nullable. 1218 | */ 1219 | public static boolean isNullableComposedSchema(ComposedSchema schema) { 1220 | List oneOf = schema.getOneOf(); 1221 | if (oneOf != null && oneOf.size() <= 2) { 1222 | for (Schema s : oneOf) { 1223 | if (isNullType(s)) { 1224 | return true; 1225 | } 1226 | } 1227 | } 1228 | return false; 1229 | } 1230 | 1231 | /** 1232 | * isNullType returns true if the input schema is the 'null' type. 1233 | * 1234 | * The 'null' type is supported in OAS 3.1 and above. It is not supported 1235 | * in OAS 2.0 and OAS 3.0.x. 1236 | * 1237 | * For example, the "null" type could be used to specify that a value must 1238 | * either be null or a specified type: 1239 | * 1240 | * OptionalOrder: 1241 | * oneOf: 1242 | * - type: 'null' 1243 | * - $ref: '#/components/schemas/Order' 1244 | * 1245 | * @param schema the OpenAPI schema 1246 | * @return true if the schema is the 'null' type 1247 | */ 1248 | public static boolean isNullType(Schema schema) { 1249 | if ("null".equals(schema.getType())) { 1250 | return true; 1251 | } 1252 | return false; 1253 | } 1254 | } 1255 | -------------------------------------------------------------------------------- /src/main/java/com/lyl/plugin/vo/TemplateParamVO.java: -------------------------------------------------------------------------------- 1 | package com.lyl.plugin.vo; 2 | 3 | import com.lyl.plugin.model.ParamNode; 4 | import com.lyl.plugin.model.TagNode; 5 | import com.lyl.plugin.model.VariableNode; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | * 模板参数对象VO 13 | * 14 | * @author yunlong.liu 15 | * @date 2020-11-04 15:16:56 16 | */ 17 | 18 | public class TemplateParamVO { 19 | 20 | /** 21 | * 对应服务的标题 22 | */ 23 | private String title; 24 | 25 | /** 26 | * 服务的描述 27 | */ 28 | private String description; 29 | 30 | /** 31 | * 服务server的主机地址 32 | */ 33 | private String host; 34 | 35 | /** 36 | * 服务server的端口 37 | */ 38 | private int port; 39 | 40 | /** 41 | * tag列表 42 | */ 43 | private List tagList=new ArrayList<>(); 44 | 45 | /** 46 | * 自定义参数变量 47 | */ 48 | private List customVariableList=new ArrayList<>(); 49 | 50 | 51 | 52 | public List getTagList() { 53 | return tagList; 54 | } 55 | 56 | public void setTagList(List tagList) { 57 | this.tagList = tagList; 58 | } 59 | 60 | public String getTitle() { 61 | return title; 62 | } 63 | 64 | public void setTitle(String title) { 65 | this.title = title; 66 | } 67 | 68 | public String getDescription() { 69 | return description; 70 | } 71 | 72 | public void setDescription(String description) { 73 | this.description = description; 74 | } 75 | 76 | public void setHost(String host) { 77 | this.host = host; 78 | } 79 | 80 | public void setPort(int port) { 81 | this.port = port; 82 | } 83 | 84 | public String getHost() { 85 | return host; 86 | } 87 | 88 | public int getPort() { 89 | return port; 90 | } 91 | 92 | public List getCustomVariableList() { 93 | return customVariableList; 94 | } 95 | 96 | public void setCustomVariableList(List customVariableList) { 97 | this.customVariableList = customVariableList; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/resources/templates/template.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${description!} 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | continue 18 | 19 | false 20 | 1 21 | 22 | 1 23 | 1 24 | 1603853091000 25 | 1603853091000 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | host 37 | = 38 | ${host!} 39 | 40 | 41 | 42 | port 43 | = 44 | ${port?c} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Content-Type 57 | application/json 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ${r"${host}"} 68 | ${r"${port}"} 69 | 70 | 71 | 72 | 6 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | <#list customVariableList as cv> 83 | 84 | ${cv.name} 85 | ${cv.value!} 86 | = 87 | ${cv.description!} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | <#list tagList as tag> 99 | 100 | 101 | 102 | 103 | 104 | <#list tag.requestNodes as req> 105 | 106 | <#if (req.requestBody)??> 107 | true 108 | 109 | 110 | 111 | false 112 | ${req.requestBody!} 113 | = 114 | 115 | 116 | 117 | <#else> 118 | 119 | 120 | <#list req.queryParamNodes as qp> 121 | 122 | false 123 | = 124 | true 125 | ${qp.paramName} 126 | ${qp.paramValue} 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ${req.requestUrl} 139 | ${req.httpMethod} 140 | true 141 | false 142 | true 143 | false 144 | 145 | false 146 | 147 | ${req.operationId} 148 | 149 | 150 | 151 | 152 | <#list req.headerParamNodes as hp> 153 | 154 | ${hp.paramName} 155 | ${hp.paramValue} 156 | 157 | 158 | 159 | 接口header管理器描述 160 | 161 | 162 | 163 | 164 | 165 | $.statusCode 166 | 0 167 | true 168 | false 169 | false 170 | true 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | false 182 | 183 | saveConfig 184 | 185 | 186 | true 187 | true 188 | true 189 | 190 | true 191 | true 192 | true 193 | true 194 | false 195 | true 196 | true 197 | false 198 | false 199 | false 200 | true 201 | false 202 | false 203 | false 204 | true 205 | 0 206 | true 207 | true 208 | true 209 | true 210 | true 211 | true 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | true 221 | 222 | 223 | 224 | 225 | 226 | --------------------------------------------------------------------------------