├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .java-version ├── CHANGELOG.md ├── LICENSE ├── README.md ├── annotations ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── jrcodeza │ ├── Header.java │ ├── OpenApiExample.java │ ├── OpenApiExamples.java │ ├── OpenApiIgnore.java │ ├── Response.java │ └── Responses.java ├── client-generator-plugin ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── jrcodeza │ └── client │ └── generator │ └── plugin │ └── GenerateClientFromSchemaMojo.java ├── client-generator ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── spring │ │ └── openapi │ │ └── client │ │ └── generator │ │ ├── ClientGeneratorUtils.java │ │ ├── OpenApiClientGenerator.java │ │ ├── OperationData.java │ │ └── ResourceInterfaceGenerator.java │ └── test │ ├── java │ └── org │ │ └── spring │ │ └── openapi │ │ └── client │ │ └── generator │ │ └── OpenApiClientGeneratorTest.java │ └── resources │ └── input_example.json ├── pom.xml ├── schema-generator-plugin ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── jrcodeza │ │ └── schema │ │ └── generator │ │ └── plugin │ │ └── GenerateOpenApiSchemaMojo.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── jrcodeza │ │ └── schema │ │ └── generator │ │ └── plugin │ │ ├── GenerateOpenApiSchemaMojoTest.java │ │ ├── controller │ │ └── MojoTestController.java │ │ ├── example │ │ └── TestExampleResolver.java │ │ ├── interceptor │ │ ├── TestOperationInterceptor.java │ │ └── TestSchemaFieldInterceptor.java │ │ └── model │ │ └── OpenApiTestModel.java │ └── resources │ └── unit │ └── generate-open-api-standard │ └── pom.xml ├── schema-generator ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── jrcodeza │ │ └── schema │ │ └── generator │ │ ├── ComponentSchemaTransformer.java │ │ ├── OpenAPIGenerator.java │ │ ├── OperationsTransformer.java │ │ ├── config │ │ ├── OpenApiGeneratorConfig.java │ │ └── builder │ │ │ └── OpenApiGeneratorConfigBuilder.java │ │ ├── filters │ │ ├── OperationFilter.java │ │ ├── OperationParameterFilter.java │ │ └── SchemaFieldFilter.java │ │ ├── interceptors │ │ ├── OperationInterceptor.java │ │ ├── OperationParameterInterceptor.java │ │ ├── RequestBodyInterceptor.java │ │ ├── SchemaFieldInterceptor.java │ │ ├── SchemaInterceptor.java │ │ └── examples │ │ │ ├── OpenApiExampleResolver.java │ │ │ └── OperationParameterExampleInterceptor.java │ │ ├── model │ │ ├── CustomComposedSchema.java │ │ ├── Header.java │ │ └── InheritanceInfo.java │ │ └── util │ │ ├── CommonConstants.java │ │ ├── GeneratorUtils.java │ │ └── SchemaGeneratorHelper.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── jrcodeza │ │ └── schema │ │ └── generator │ │ ├── OpenAPIGeneratorTest.java │ │ ├── base │ │ └── Entity.java │ │ ├── controller │ │ ├── CarController.java │ │ ├── ControllerToBeIgnored.java │ │ ├── DummyController.java │ │ └── ListController.java │ │ ├── domain │ │ ├── Car.java │ │ ├── CarType.java │ │ ├── Customer.java │ │ ├── CustomerInventory.java │ │ ├── Entity.java │ │ ├── Laptop.java │ │ ├── OptionsClass.java │ │ ├── Order.java │ │ ├── Product.java │ │ ├── Van.java │ │ └── dummy │ │ │ ├── ArrayDummy.java │ │ │ ├── ListDummy.java │ │ │ └── ValidationDummy.java │ │ ├── filters │ │ ├── TestOperationFilter.java │ │ ├── TestOperationParameterFilter.java │ │ └── TestSchemaFieldFilter.java │ │ ├── interceptors │ │ ├── TestOperationInterceptor.java │ │ ├── TestOperationParameterInterceptor.java │ │ ├── TestRequestBodyInterceptor.java │ │ ├── TestSchemaFieldInterceptor.java │ │ └── TestSchemaInterceptor.java │ │ └── util │ │ └── TestUtils.java │ └── resources │ ├── expected_example_openapi.json │ ├── expected_filtered_openapi.json │ └── expected_standard_openapi.json └── schema-v2-generator ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── jrcodeza │ └── schema │ └── v2 │ └── generator │ ├── ComponentSchemaTransformer.java │ ├── OpenAPIV2Generator.java │ ├── OpenApiTransformer.java │ ├── OperationsTransformer.java │ ├── config │ ├── CompatibilityMode.java │ ├── OpenApiV2GeneratorConfig.java │ └── builder │ │ └── OpenApiV2GeneratorConfigBuilder.java │ ├── interceptors │ ├── OperationInterceptor.java │ ├── OperationParameterInterceptor.java │ ├── RequestBodyInterceptor.java │ ├── SchemaFieldInterceptor.java │ └── SchemaInterceptor.java │ ├── model │ ├── CustomBodyParameter.java │ ├── CustomQueryParameter.java │ ├── CustomSchema.java │ ├── GenerationContext.java │ ├── Header.java │ └── InheritanceInfo.java │ └── util │ ├── CommonConstants.java │ └── GeneratorUtils.java └── test ├── java └── com │ └── github │ └── jrcodeza │ └── schema │ └── v2 │ └── generator │ ├── OpenAPIV2GeneratorTest.java │ ├── base │ └── Entity.java │ ├── controller │ ├── CarController.java │ ├── ControllerToBeIgnored.java │ ├── DummyController.java │ └── ListController.java │ ├── domain │ ├── Car.java │ ├── CarType.java │ ├── Customer.java │ ├── CustomerInventory.java │ ├── Entity.java │ ├── Laptop.java │ ├── OptionsClass.java │ ├── Order.java │ ├── Product.java │ ├── Van.java │ └── dummy │ │ ├── ArrayDummy.java │ │ ├── ListDummy.java │ │ └── ValidationDummy.java │ ├── interceptors │ ├── TestOperationInterceptor.java │ ├── TestOperationParameterInterceptor.java │ ├── TestRequestBodyInterceptor.java │ ├── TestSchemaFieldInterceptor.java │ └── TestSchemaInterceptor.java │ └── util │ └── TestUtils.java └── resources ├── expected_v2_openapi.json └── expected_v2_openapi_nswag.json /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Maven 17 | run: mvn -B package --file pom.xml 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | target/* 25 | /*/target 26 | .idea/* 27 | *.iml 28 | /*/*.iml 29 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## 1.4.6 4 | 5 | #44 https://github.com/jrcodeza/spring-openapi/issues/44 6 | #47 https://github.com/jrcodeza/spring-openapi/issues/47 7 | 8 | ## 1.4.5 9 | #41 https://github.com/jrcodeza/spring-openapi/issues/41 10 | #42 https://github.com/jrcodeza/spring-openapi/issues/42 11 | #45 https://github.com/jrcodeza/spring-openapi/issues/45 12 | 13 | ## 1.4.4 14 | OperationId generation improvement 15 | Use latest jackson-databing 2.9.10.3 16 | 17 | ## 1.4.3 18 | #38 https://github.com/jrcodeza/spring-openapi/issues/38 19 | 20 | ## 1.4.2 21 | #34 https://github.com/jrcodeza/spring-openapi/issues/34 22 | #36 https://github.com/jrcodeza/spring-openapi/issues/36 23 | 24 | ## 1.4.1 25 | Bugfixes. 26 | 27 | ## 1.4.0 28 | Added support for OpenAPI version 2 spec generation. For now only 29 | runtime generation (no maven plugin). 30 | 31 | ## 1.3.0 32 | https://github.com/jrcodeza/spring-openapi/issues/22 33 | 34 | https://github.com/jrcodeza/spring-openapi/issues/21 35 | 36 | https://github.com/jrcodeza/spring-openapi/issues/19 37 | 38 | ## 1.2.1 39 | 40 | https://github.com/jrcodeza/spring-openapi/issues/21 41 | 42 | https://github.com/jrcodeza/spring-openapi/issues/19 43 | 44 | Has same features as 1.3.0 but doesn't track (traverse) inheritance hierarchy 45 | over all java 'extends' using allOf. If inheritance hierarchy does not contain 46 | JsonSubTypes annotation (discriminator) then all parent properties are copied 47 | into the current class and no allOf approach is used. AllOf approach is used 48 | only for inheritance hierarchies where JsonSubTypes annotation is present 49 | (on some class). 50 | 51 | This is done because if generated spec has too big inheritance depth, then 52 | swagger ui might have problems to interpret this (follow $refs - I've got 53 | stack overflow and other problems). Oas-ui interpreter 54 | (https://github.com/vahanito/oas-ui) does not have this problem. 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 jrcode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-openapi (OpenAPI 3 generator) 2 | ![Java CI](https://github.com/jrcodeza/spring-openapi/workflows/Java%20CI/badge.svg) 3 | 4 | Spring Boot OpenAPI 3 generator. It scans provided packages 5 | (model and controller) and generates based on reflection, javax validation 6 | and spring annotations the OpenAPI 3 json. It is able to handle also 7 | inheritance using OpenAPI 3 discriminator possibilities. The inheritance 8 | generation is achieved using jackson annotations. 9 | 10 | Generator allows you to also intercept schema, schema field, operation 11 | and operation parameter process, so you can include your own behavior 12 | to enrich already mapped sections (e.g. include own annotation behavior 13 | to descriptions etc.). 14 | 15 | So far there is core generator functionality 16 | **spring-openapi-schema-generator** and maven plugin 17 | **spring-openapi-schema-generator-plugin** available.Soon there will be 18 | also java client generator finalized, because current tooling is still 19 | not able to generate client with full OpenAPI 3 discriminator features 20 | (jackson annotations). 21 | 22 | There is also short article describing usage of spring-openapi generator here: 23 | https://medium.com/@remenec.jakub/openapi-3-spec-and-client-generation-on-java-spring-application-38a9ba5a2932. 24 | 25 | ## Current version 26 | **1.4.6** 27 | 28 | Now generator supports also OpenAPI version 2 generation. 29 | 30 | Release notes: https://github.com/jrcodeza/spring-openapi/blob/master/CHANGELOG.md 31 | 32 | 1.2.1 - to ensure compatiblity with swagger ui for bigger inheritance structures. 33 | This version models inheritance using allOf only if discriminator (JsonSubTypes) is found 34 | in inheritance hierarchy. If not, it takes all attributes from parent classes 35 | and adds them to the current one. See tests for more info. 36 | 37 | ## Example project 38 | You can check this repository https://github.com/jrcodeza/spring-openapi-example for an example 39 | usage. It shows spec to code and also code to spec generation using maven plugins. It contains 40 | simple REST resources, model (including inheritance), interceptors and examples resolver. 41 | 42 | ## OpenAPI UI interpreter 43 | If you need to display OpenAPI v3 spec in browser you can have a look also on **oas-ui** 44 | plugin https://github.com/vahanito/oas-ui (which was developed together with spring-openapi) . 45 | It supports searching of components and resources. Correctly displays inheritance also 46 | with discriminator info. You can use it in react as plugin or also as standalone bundle js. 47 | 48 | ## Annotations 49 | ### @OpenApiExample 50 | You can define it on class, method, parameter or field. There are 2 main use cases: 51 | 52 | - Providing the example value **in place** 53 | ```java 54 | @OpenApiExample(name = "StandardExample", value = "standardExampleValue") 55 | ``` 56 | - providing **key** to example. You have to manage that key is handled by examples resolver 57 | ```java 58 | @OpenApiExample(name = "KeyExample", key = "KEY") 59 | ``` 60 | Example resolver is a simple interface: 61 | ```java 62 | public interface OpenApiExampleResolver { 63 | 64 | String resolveExample(String exampleKey); 65 | 66 | } 67 | ``` 68 | The **exampleKey** parameter is a key for which example value should be provided by resolver. It can be 69 | for example a file name with example request - then goal of the resolver is to read file content and provide 70 | it. 71 | 72 | It's possible to define more examples for one element by wrapping @OpenApiExample annotation to **@OpenApiExamples** 73 | one. Name of the example becomes important in this case, because it will be stored to spec as example name. 74 | 75 | ### @OpenApiIgnore 76 | Element annotated with this annotation will not be generated to spec. 77 | 78 | ### @Response 79 | Sometimes you need to specify method (operation) response in your own way. It can 80 | be necessary when you have several return statuses or response types. Also when 81 | you want to specify response headers. For this purpose you can use this annotation. 82 | 83 | It's also possible to wrap multiple @Response annotations to **@Responses** annotations. E.g.: 84 | ```java 85 | @Responses({ 86 | @Response(responseCode = 201, description = "Created", responseBody = ValidationDummy.class, 87 | headers = @Header(name = "SomeHeader", description = "TestHeader")), 88 | @Response(responseCode = 200, description = "Replaced", responseBody = ValidationDummy.class) 89 | }) 90 | ``` 91 | @Header annotation is also custom spring-openapi annotation. 92 | 93 | ## Generate spec from code 94 | ### Runtime usage 95 | Include dependency 96 | 97 | ```java 98 | 99 | com.github.jrcodeza 100 | spring-openapi-schema-generator 101 | 1.4.6 102 | 103 | ``` 104 | 105 | Instantiate **OpenAPIGenerator** 106 | 107 | ```java 108 | OpenAPIGenerator openAPIGenerator = new OpenAPIGenerator( 109 | singletonList("org.spring.openapi.schema.generator.plugin.model.*"), 110 | singletonList("org.spring.openapi.schema.generator.plugin.controller.*"), 111 | createTestInfo() 112 | ); 113 | ``` 114 | The first parameter is model package and the second is controller package. It 115 | is possible to define multiple packages for each. The third parameter 116 | is OpenAPI basic info like project title, description, version etc. It is 117 | required in order to create valid OpenAPI 3 output. 118 | Additionally you can define interceptors 119 | * **OperationInterceptor** - executed after controller method is mapped 120 | * **OperationParameterInterceptor** - executed after each method parameter is mapped 121 | * **RequestBodyInterceptor** - executed after RequestBody annotated parameter of a method is mapped 122 | * **SchemaFieldInterceptor** - executed after class field is mapped 123 | * **SchemaInterceptor** - executed after each class is mapped 124 | 125 | You can add these interceptors either to constructor after info parameter or 126 | by calling add + interceptorName method on OpenAPIGenerator class. E.g.: 127 | 128 | ```java 129 | openAPIGenerator.addOperationInterceptor(operationInterceptor); 130 | openAPIGenerator.addOperationParameterInterceptor(operationParameterInterceptor); 131 | openAPIGenerator.addRequestBodyInterceptor(requestBodyInterceptor); 132 | openAPIGenerator.addSchemaFieldInterceptor(schemaFieldInterceptor); 133 | openAPIGenerator.addSchemaInterceptor(schemaInterceptor); 134 | ``` 135 | 136 | You can define your own interceptor as follows: 137 | ```java 138 | package com.github.jrcodeza.schema.generator.plugin.interceptors; 139 | 140 | import java.lang.reflect.Method; 141 | 142 | import com.github.jrcodeza.schema.generator.interceptors.OperationInterceptor; 143 | 144 | import io.swagger.v3.oas.models.Operation; 145 | 146 | public class TestOperationInterceptor implements OperationInterceptor { 147 | 148 | @Override 149 | public void intercept(Method method, Operation transformedOperation) { 150 | transformedOperation.setSummary("Interceptor summary"); 151 | } 152 | 153 | } 154 | ``` 155 | 156 | It's possible to define also global headers which are applicable for all resources. 157 | ```java 158 | openAPIGenerator.addGlobalHeader("Test-Global-Header", "Some desc", false); 159 | ``` 160 | 161 | OpenAPIGenerator method **generate** can also take OpenApiGeneratorConfig as parameter. 162 | In this config you can define if generation of examples is enabled. You can also define 163 | example resolver, which can be useful if for example you have bigger examples of POST body requests 164 | stored in files. You can define it as follows: 165 | 166 | ```java 167 | OpenApiGeneratorConfigBuilder.defaultConfig() 168 | .withGenerateExamples(true) 169 | .withOpenApiExampleResolver(createExampleResolver()) 170 | .build() 171 | ``` 172 | 173 | Finally when you want to **generate OpenAPI 3 spec** you have to execute 174 | generate method on OpenAPIGenerator instance. 175 | ```java 176 | OpenAPI openAPI = openAPIGenerator.generate(); 177 | ``` 178 | and you will receive OpenAPI object (from swagger API) which can be serialized 179 | to string like this 180 | ```java 181 | ObjectMapper objectMapper = new ObjectMapper(); 182 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 183 | try { 184 | String generated = objectMapper.writeValueAsString(openAPI); 185 | JSONAssert.assertEquals(getResourceFileAsString(), generated, false); 186 | } catch (JsonProcessingException | JSONException e) { 187 | e.printStackTrace(); 188 | } 189 | ``` 190 | 191 | ### Maven plugin usage 192 | Maven plugin wraps before mentioned functionality into maven plugin. 193 | 194 | ```xml 195 | 196 | 197 | 198 | com.github.jrcodeza 199 | spring-openapi-schema-generator-plugin 200 | 201 | Test title 202 | Test description 203 | 1.0.0-TEST 204 | 205 | org.spring.openapi.schema.generator.test.model.* 206 | 207 | 208 | org.spring.openapi.schema.generator.test.controller.* 209 | 210 | target/openapi 211 | 212 | com.github.jrcodeza.schema.generator.plugin.interceptor.TestSchemaFieldInterceptor 213 | 214 | 215 | com.github.jrcodeza.schema.generator.plugin.interceptor.TestOperationInterceptor 216 | 217 | true 218 | com.github.jrcodeza.schema.generator.plugin.example.TestExampleResolver 219 | 220 | 221 | 222 | 223 | ``` 224 | 225 | You can see that title, description and version are the OpenAPI info parameters. 226 | Then you have to define also modelPackages and controllerPackages. Additionally 227 | you should define also outputDirectory, in our case target/openapi will be the target folder 228 | where the swagger.json will be saved. 229 | 230 | From 1.2.0 version it is also possible to define all interceptors also in plugin. Additionally there 231 | is also option to turn on/off generateExamples and of course specify openApiExamplesResolver. 232 | 233 | ## Generate client from spec 234 | 235 | ### Runtime usage 236 | Include dependency 237 | 238 | ```java 239 | 240 | com.github.jrcodeza 241 | spring-openapi-client-generator 242 | 1.4.6 243 | 244 | ``` 245 | 246 | Use generator in code 247 | 248 | ```java 249 | new OpenApiClientGenerator().generateClient( 250 | "target.package", 251 | "pathToOpenApi3Spec", 252 | "targetFolder", // e.g. /target/generatedSources/oas 253 | true); // should generate interface 254 | ``` 255 | 256 | ### Maven plugin usage 257 | 258 | 1. Include dependency 259 | ```java 260 | 261 | com.github.jrcodeza 262 | spring-openapi-client-generator-plugin 263 | 1.4.6 264 | 265 | ``` 266 | 267 | 2. Add plugin to maven plugins section 268 | ```xml 269 | 270 | com.github.jrcodeza 271 | spring-openapi-client-generator-plugin 272 | 273 | 274 | 275 | generateClientFromOpenApi 276 | 277 | 278 | test.package 279 | target/generated-sources 280 | src/main/resources/oas3.json 281 | true 282 | true 283 | 284 | 285 | 286 | 287 | ``` 288 | **generateResourceInterface** option allows you to turn on/off generation of interface 289 | with methods that represent operations of the API. 290 | 291 | **generateDiscriminatorProperty** allows you to turn on/off functionality where 292 | plugin generates the discriminator property explicitly to class (setting it visible=true in jackson). 293 | This is handy when you need to see the discriminator property inside the class 294 | and being able to access it using setter or getter. 295 | 296 | ## Contributions 297 | Pull requests are welcome. If you would like to collaborate more feel free to contact 298 | me on remenec.jakub@gmail.com . 299 | 300 | ## License 301 | License for this tool is GNU GPL V3. 302 | -------------------------------------------------------------------------------- /annotations/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-annotations 11 | 12 | 1.4.11-SNAPSHOT 13 | jar 14 | 15 | Spring Open API - Annotations 16 | 17 | 18 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/Header.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Header { 11 | 12 | String name() default ""; 13 | 14 | String description() default ""; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/OpenApiExample.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface OpenApiExample { 11 | 12 | String name() default ""; 13 | String description() default ""; 14 | /** 15 | * If not filled in then key has to be filled in. CustomExampleResolver will be invoked with key 16 | * value as parameter. 17 | * @return 18 | */ 19 | String value() default ""; 20 | String key() default ""; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/OpenApiExamples.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface OpenApiExamples { 11 | 12 | OpenApiExample[] value(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/OpenApiIgnore.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface OpenApiIgnore { 11 | } 12 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/Response.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Response { 11 | 12 | int responseCode(); 13 | 14 | String description(); 15 | 16 | Class responseBody() default Void.class; 17 | 18 | Header[] headers() default {}; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /annotations/src/main/java/com/github/jrcodeza/Responses.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Responses { 11 | 12 | Response[] value(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /client-generator-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-client-generator-plugin 11 | 12 | 1.4.11-SNAPSHOT 13 | maven-plugin 14 | 15 | Spring Open API - Client generator plugin 16 | 17 | 18 | 19 | com.github.jrcodeza 20 | spring-openapi-client-generator 21 | ${project.version} 22 | 23 | 24 | 25 | org.apache.maven.plugin-tools 26 | maven-plugin-annotations 27 | provided 28 | 29 | 30 | org.apache.maven 31 | maven-plugin-api 32 | 33 | 34 | org.apache.maven 35 | maven-core 36 | 37 | 38 | org.apache.maven 39 | maven-artifact 40 | provided 41 | 42 | 43 | org.apache.maven 44 | maven-compat 45 | 46 | 47 | 48 | org.apache.maven.plugin-testing 49 | maven-plugin-testing-harness 50 | test 51 | 52 | 53 | junit 54 | junit 55 | test 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 65 | 8 66 | 8 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-plugin-plugin 72 | 3.5 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /client-generator-plugin/src/main/java/com/github/jrcodeza/client/generator/plugin/GenerateClientFromSchemaMojo.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.client.generator.plugin; 2 | 3 | import org.apache.commons.lang3.BooleanUtils; 4 | import org.apache.maven.plugin.AbstractMojo; 5 | import org.apache.maven.plugins.annotations.LifecyclePhase; 6 | import org.apache.maven.plugins.annotations.Mojo; 7 | import org.apache.maven.plugins.annotations.Parameter; 8 | import org.apache.maven.project.MavenProject; 9 | import org.spring.openapi.client.generator.OpenApiClientGenerator; 10 | 11 | @Mojo(name = "generateClientFromOpenApi", defaultPhase = LifecyclePhase.GENERATE_SOURCES) 12 | public class GenerateClientFromSchemaMojo extends AbstractMojo { 13 | 14 | @Parameter 15 | private String outputPackage; 16 | 17 | @Parameter 18 | private String outputPath; 19 | 20 | @Parameter 21 | private String schemaPath; 22 | 23 | @Parameter 24 | private Boolean generateResourceInterface; 25 | 26 | @Parameter 27 | private Boolean generateDiscriminatorProperty; 28 | 29 | @Parameter(defaultValue = "${project}") 30 | private MavenProject project; 31 | 32 | @Override 33 | public void execute() { 34 | new OpenApiClientGenerator().generateClient(outputPackage, schemaPath, outputPath, 35 | BooleanUtils.isTrue(generateResourceInterface), 36 | BooleanUtils.isTrue(generateDiscriminatorProperty) 37 | ); 38 | project.addCompileSourceRoot(outputPath); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client-generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-client-generator 11 | 12 | 1.4.11-SNAPSHOT 13 | jar 14 | 15 | Spring Open API - Client generator 16 | 17 | 18 | 19 | org.springframework 20 | spring-core 21 | 22 | 23 | org.springframework 24 | spring-context 25 | 26 | 27 | 28 | org.apache.commons 29 | commons-collections4 30 | 31 | 32 | 33 | io.swagger.core.v3 34 | swagger-models 35 | 36 | 37 | io.swagger.core.v3 38 | swagger-annotations 39 | 40 | 41 | io.swagger.core.v3 42 | swagger-integration 43 | 44 | 45 | 46 | com.squareup 47 | javapoet 48 | 49 | 50 | 51 | com.fasterxml.jackson.core 52 | jackson-databind 53 | 54 | 55 | com.fasterxml.jackson.core 56 | jackson-annotations 57 | 58 | 59 | 60 | javax.validation 61 | validation-api 62 | 63 | 64 | 65 | org.apache.logging.log4j 66 | log4j-api 67 | 68 | 69 | org.apache.logging.log4j 70 | log4j-core 71 | 72 | 73 | org.apache.logging.log4j 74 | log4j-slf4j-impl 75 | 76 | 77 | 78 | org.skyscreamer 79 | jsonassert 80 | test 81 | 82 | 83 | commons-io 84 | commons-io 85 | test 86 | 87 | 88 | junit 89 | junit 90 | 4.12 91 | test 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-compiler-plugin 100 | 101 | 8 102 | 8 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /client-generator/src/main/java/org/spring/openapi/client/generator/ClientGeneratorUtils.java: -------------------------------------------------------------------------------- 1 | package org.spring.openapi.client.generator; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | import com.squareup.javapoet.ClassName; 10 | import com.squareup.javapoet.JavaFile; 11 | import com.squareup.javapoet.TypeSpec; 12 | 13 | import org.apache.commons.collections4.CollectionUtils; 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import io.swagger.v3.oas.models.media.ComposedSchema; 17 | import io.swagger.v3.oas.models.media.Schema; 18 | 19 | import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase; 20 | 21 | public final class ClientGeneratorUtils { 22 | 23 | public static final String JAVA_LANG_PKG = "java.lang"; 24 | public static final String JAVA_TIME_PKG = "java.time"; 25 | 26 | public static void buildTypeSpec(String targetPackage, TypeSpec.Builder typeSpecBuilder, String outputPath) { 27 | try { 28 | JavaFile.builder(targetPackage, typeSpecBuilder.build()) 29 | .build() 30 | .writeTo(new File(outputPath)); 31 | } catch (IOException e) { 32 | e.printStackTrace(); // TODO 33 | } 34 | } 35 | 36 | private ClientGeneratorUtils() { 37 | throw new AssertionError(); 38 | } 39 | 40 | public static String getNameFromRef(String ref) { 41 | return ref.replace("#/components/schemas/", ""); 42 | } 43 | 44 | public static ClassName getStringGenericClassName(Schema genericSchema) { 45 | if (genericSchema.getFormat() == null) { 46 | return ClassName.get(JAVA_LANG_PKG, "String"); 47 | } else if (equalsIgnoreCase(genericSchema.getFormat(), "date")) { 48 | return ClassName.get(JAVA_TIME_PKG, "LocalDate"); 49 | } else if (equalsIgnoreCase(genericSchema.getFormat(), "date-time")) { 50 | return ClassName.get(JAVA_TIME_PKG, "LocalDateTime"); 51 | } 52 | throw new IllegalArgumentException("Error parsing string based property"); 53 | } 54 | 55 | public static ClassName getNumberGenericClassName(Schema genericSchema) { 56 | if (genericSchema.getFormat() == null || StringUtils.equalsIgnoreCase(genericSchema.getFormat(), "int32")) { 57 | return ClassName.get(JAVA_LANG_PKG, "Integer"); 58 | } else if (StringUtils.equalsIgnoreCase(genericSchema.getFormat(), "int64")) { 59 | return ClassName.get(JAVA_LANG_PKG, "Long"); 60 | } else if (StringUtils.equalsIgnoreCase(genericSchema.getFormat(), "float")) { 61 | return ClassName.get(JAVA_LANG_PKG, "Float"); 62 | } else if (StringUtils.equalsIgnoreCase(genericSchema.getFormat(), "double")) { 63 | return ClassName.get(JAVA_LANG_PKG, "Double"); 64 | } else { 65 | return ClassName.get(JAVA_LANG_PKG, "Integer"); 66 | } 67 | } 68 | 69 | public static String determineParentClassNameUsingDiscriminator(Schema innerSchema, String fieldName, Map allComponents) { 70 | Set> discriminatorEntries = innerSchema.getDiscriminator().getMapping().entrySet(); 71 | if (CollectionUtils.isEmpty(discriminatorEntries)) { 72 | throw new IllegalArgumentException("Discriminator needs to have at least one value defined. Field: " + fieldName); 73 | } 74 | return determineParentClassName(discriminatorEntries.iterator().next().getValue(), allComponents); 75 | } 76 | 77 | public static String determineParentClassName(String childClassToFind, Map allComponents) { 78 | Schema childClass = allComponents.get(childClassToFind); 79 | if (childClass instanceof ComposedSchema) { 80 | ComposedSchema childClassComposed = (ComposedSchema) childClass; 81 | if (CollectionUtils.isNotEmpty(childClassComposed.getAllOf())) { 82 | String parentClassRef = childClassComposed.getAllOf().get(0).get$ref(); 83 | if (parentClassRef == null) { 84 | throw new IllegalArgumentException("Unsupported inheritance model. AllOf $ref for parent class has to be defined"); 85 | } 86 | return getNameFromRef(parentClassRef); 87 | } 88 | } 89 | throw new IllegalArgumentException("Unsupported inheritance model for " + (childClass == null ? "null" : childClass.getName())); 90 | } 91 | 92 | public static String determineParentClassNameUsingOneOf(Schema innerSchema, String fieldName, Map allComponents) { 93 | if (!(innerSchema instanceof ComposedSchema)) { 94 | throw new IllegalArgumentException("To determine class name using allOf schema has to be Composed"); 95 | } 96 | List allOf = ((ComposedSchema) innerSchema).getAllOf(); 97 | String refToOneOf = allOf.get(0).get$ref(); 98 | if (refToOneOf == null) { 99 | throw new IllegalArgumentException("OneOf entry needs to have defined $ref. Field: " + fieldName); 100 | } 101 | return determineParentClassName(getNameFromRef(refToOneOf), allComponents); 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /client-generator/src/main/java/org/spring/openapi/client/generator/OperationData.java: -------------------------------------------------------------------------------- 1 | package org.spring.openapi.client.generator; 2 | 3 | import io.swagger.v3.oas.models.Operation; 4 | 5 | public class OperationData { 6 | 7 | private final Operation operation; 8 | private final String url; 9 | 10 | public OperationData(Operation operation, String url) { 11 | this.operation = operation; 12 | this.url = url; 13 | } 14 | 15 | public Operation getOperation() { 16 | return operation; 17 | } 18 | 19 | public String getUrl() { 20 | return url; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client-generator/src/main/java/org/spring/openapi/client/generator/ResourceInterfaceGenerator.java: -------------------------------------------------------------------------------- 1 | package org.spring.openapi.client.generator; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.Set; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | import javax.lang.model.element.Modifier; 15 | import javax.validation.constraints.DecimalMax; 16 | import javax.validation.constraints.DecimalMin; 17 | 18 | import com.squareup.javapoet.AnnotationSpec; 19 | import com.squareup.javapoet.ClassName; 20 | import com.squareup.javapoet.MethodSpec; 21 | import com.squareup.javapoet.ParameterSpec; 22 | import com.squareup.javapoet.ParameterizedTypeName; 23 | import com.squareup.javapoet.TypeName; 24 | import com.squareup.javapoet.TypeSpec; 25 | 26 | import org.apache.commons.collections4.CollectionUtils; 27 | import org.apache.commons.lang3.StringUtils; 28 | 29 | import io.swagger.v3.oas.models.Operation; 30 | import io.swagger.v3.oas.models.PathItem; 31 | import io.swagger.v3.oas.models.Paths; 32 | import io.swagger.v3.oas.models.media.ArraySchema; 33 | import io.swagger.v3.oas.models.media.ComposedSchema; 34 | import io.swagger.v3.oas.models.media.MediaType; 35 | import io.swagger.v3.oas.models.media.Schema; 36 | import io.swagger.v3.oas.models.responses.ApiResponse; 37 | 38 | import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase; 39 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.JAVA_LANG_PKG; 40 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.JAVA_TIME_PKG; 41 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.buildTypeSpec; 42 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.determineParentClassName; 43 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.determineParentClassNameUsingOneOf; 44 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.getNameFromRef; 45 | import static org.spring.openapi.client.generator.ClientGeneratorUtils.getNumberGenericClassName; 46 | 47 | public class ResourceInterfaceGenerator { 48 | 49 | private String targetPackage; 50 | 51 | private final Map allComponents; 52 | 53 | public ResourceInterfaceGenerator(Map allComponents) { 54 | this.allComponents = allComponents; 55 | } 56 | 57 | public void generateResourceInterface(Paths paths, String targetPackage, String outputPath) { 58 | this.targetPackage = targetPackage; 59 | Map> resourceMap = new HashMap<>(); 60 | paths.entrySet().forEach(pathItemEntry -> addToResourceMap(pathItemEntry, resourceMap)); 61 | resourceMap.entrySet().forEach(resource -> createInterface(resource, targetPackage, outputPath)); 62 | } 63 | 64 | private void createInterface(Map.Entry> resource, String targetPackage, String outputPath) { 65 | TypeSpec.Builder typeSpecBuilder = TypeSpec.interfaceBuilder(resource.getKey()) 66 | .addModifiers(Modifier.PUBLIC); 67 | resource.getValue().forEach(operationData -> typeSpecBuilder.addMethod(createMethod(operationData))); 68 | buildTypeSpec(targetPackage + ".operations", typeSpecBuilder, outputPath); 69 | } 70 | 71 | private MethodSpec createMethod(OperationData operationData) { 72 | Operation operation = operationData.getOperation(); 73 | MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(getMethodName(operation)) 74 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); 75 | if (operation.getDescription() != null) { 76 | methodSpecBuilder.addJavadoc(operation.getDescription()); 77 | } 78 | if (operation.getParameters() != null) { 79 | operation.getParameters() 80 | .forEach(parameter -> methodSpecBuilder.addParameter( 81 | parseProperties(formatParameterName(parameter.getName()), parameter.getSchema()).build() 82 | )); 83 | } 84 | if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) { 85 | LinkedHashMap mediaTypes = operation.getRequestBody().getContent(); 86 | methodSpecBuilder.addParameter(parseProperties("requestBody", mediaTypes.entrySet().iterator().next().getValue().getSchema()).build()); 87 | } 88 | if (operation.getResponses() == null || CollectionUtils.isEmpty(operation.getResponses().entrySet())) { 89 | methodSpecBuilder.returns(TypeName.VOID); 90 | } else { 91 | ApiResponse apiResponse = operation.getResponses().entrySet().stream() 92 | .filter(responseEntry -> StringUtils.startsWith(responseEntry.getKey(), "2")) // HTTP 20x 93 | .findFirst() 94 | .map(Map.Entry::getValue) 95 | .orElse(null); 96 | if (apiResponse != null && apiResponse.getContent() != null && !apiResponse.getContent().isEmpty()) { 97 | MediaType mediaType = apiResponse.getContent().entrySet().iterator().next().getValue(); 98 | if (mediaType.getSchema() != null) { 99 | methodSpecBuilder.returns(determineTypeName(mediaType.getSchema())); 100 | return methodSpecBuilder.build(); 101 | } 102 | } 103 | methodSpecBuilder.returns(TypeName.VOID); 104 | } 105 | 106 | return methodSpecBuilder.build(); 107 | } 108 | 109 | private String getMethodName(Operation operation) { 110 | String operationIdPrefix = ""; 111 | if (CollectionUtils.isNotEmpty(operation.getTags())) { 112 | operationIdPrefix = StringUtils.uncapitalize(toResourceInterfaceName(operation.getTags().get(0))); 113 | } 114 | return StringUtils.uncapitalize(operation.getOperationId()).replaceFirst(operationIdPrefix, ""); 115 | } 116 | 117 | private String formatParameterName(String name) { 118 | String result = Stream.of(name.split("-")) 119 | .map(StringUtils::capitalize) 120 | .collect(Collectors.joining()); 121 | return StringUtils.uncapitalize(result); 122 | } 123 | 124 | private void addToResourceMap(Map.Entry pathItemEntry, Map> resourceMap) { 125 | String url = pathItemEntry.getKey(); 126 | String resourceName = getResourceName(pathItemEntry.getValue()); 127 | PathItem pathItem = pathItemEntry.getValue(); 128 | Stream.of(pathItem.getPost(), pathItem.getPatch(), pathItem.getPut(), pathItem.getHead(), pathItem.getOptions(), pathItem.getGet(), pathItem.getDelete()) 129 | .filter(Objects::nonNull) 130 | .forEach(operation -> { 131 | if (resourceMap.containsKey(resourceName)) { 132 | resourceMap.get(resourceName).add(new OperationData(operation, url)); 133 | } else { 134 | List operations = new ArrayList<>(); 135 | operations.add(new OperationData(operation, url)); 136 | resourceMap.put(resourceName, operations); 137 | } 138 | }); 139 | } 140 | 141 | private String getResourceName(PathItem value) { 142 | return Stream.of(value.getPost(), value.getPatch(), value.getPut(), value.getHead(), value.getOptions(), value.getGet(), value.getDelete()) 143 | .filter(Objects::nonNull) 144 | .filter(operation -> CollectionUtils.isNotEmpty(operation.getTags())) 145 | .findFirst() 146 | .map(operation -> toResourceInterfaceName(operation.getTags().get(0))) 147 | .orElse(null); 148 | } 149 | 150 | private String toResourceInterfaceName(String operationTag) { 151 | return Stream.of(operationTag.split("-")) 152 | .map(StringUtils::capitalize) 153 | .collect(Collectors.joining()); 154 | } 155 | 156 | private ParameterSpec.Builder parseProperties(String parameterName, Schema parameterSchema) { 157 | if (parameterSchema.getType() != null) { 158 | return parseTypeBasedSchema(parameterName, parameterSchema); 159 | } else if (parameterSchema.get$ref() != null) { 160 | // simple no inheritance 161 | return createSimpleParameterSpec(null, getNameFromRef(parameterSchema.get$ref()), parameterName); 162 | } else if (parameterSchema instanceof ComposedSchema && CollectionUtils.isNotEmpty(((ComposedSchema) parameterSchema).getAllOf())) { 163 | return createSimpleParameterSpec(null, determineParentClassNameUsingOneOf(parameterSchema, parameterName, allComponents), parameterName); 164 | } else { 165 | throw new IllegalArgumentException("Incorrect schema. One of [type, $ref, discriminator+oneOf] has to be defined in property schema"); 166 | } 167 | } 168 | 169 | private ParameterSpec.Builder parseTypeBasedSchema(String parameterName, Schema innerSchema) { 170 | if (equalsIgnoreCase(innerSchema.getType(), "string")) { 171 | return ParameterSpec.builder(getStringGenericClassName(innerSchema), parameterName); 172 | } else if (equalsIgnoreCase(innerSchema.getType(), "integer") || equalsIgnoreCase(innerSchema.getType(), "number")) { 173 | return getNumberBasedSchemaParameter(parameterName, innerSchema); 174 | } else if (equalsIgnoreCase(innerSchema.getType(), "boolean")) { 175 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Boolean", parameterName); 176 | } else if (equalsIgnoreCase(innerSchema.getType(), "array") && innerSchema instanceof ArraySchema) { 177 | ArraySchema arraySchema = (ArraySchema) innerSchema; 178 | ParameterizedTypeName listParameterizedTypeName = ParameterizedTypeName.get( 179 | ClassName.get("java.util", "List"), 180 | determineTypeName(arraySchema.getItems()) 181 | ); 182 | return ParameterSpec.builder(listParameterizedTypeName, parameterName); 183 | } else if (equalsIgnoreCase(innerSchema.getType(), "object") && isFile(innerSchema.getProperties())) { 184 | return ParameterSpec.builder(ClassName.get(File.class), parameterName); 185 | } 186 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Object", parameterName); 187 | } 188 | 189 | private ClassName determineTypeName(Schema schema) { 190 | if (equalsIgnoreCase(schema.getType(), "string") && !StringUtils.equalsIgnoreCase(schema.getFormat(), "binary")) { 191 | return getStringGenericClassName(schema); 192 | } else if (equalsIgnoreCase(schema.getType(), "integer") || equalsIgnoreCase(schema.getType(), "number")) { 193 | return getNumberGenericClassName(schema); 194 | } else if (equalsIgnoreCase(schema.getType(), "boolean")) { 195 | return ClassName.get(JAVA_LANG_PKG, "Boolean"); 196 | } else if (schema.get$ref() != null) { 197 | return ClassName.bestGuess(targetPackage + "." + getNameFromRef(schema.get$ref())); 198 | } else if (schema instanceof ComposedSchema && CollectionUtils.isNotEmpty(((ComposedSchema) schema).getAllOf())) { 199 | return ClassName.bestGuess(targetPackage + "." + determineParentClassNameUsingOneOf(schema, "innerArray", allComponents)); 200 | } else if (schema.getDiscriminator() != null) { 201 | return ClassName.bestGuess(targetPackage + "." + determineParentClassNameUsingDiscriminator(schema, "innerArray")); 202 | } else if ((equalsIgnoreCase(schema.getType(), "object") || equalsIgnoreCase(schema.getType(), "string")) && isFile(schema.getProperties())) { 203 | return ClassName.get(File.class); 204 | } 205 | return ClassName.get(JAVA_LANG_PKG, "Object"); 206 | } 207 | 208 | private boolean isFile(Map properties) { 209 | if (properties == null || properties.isEmpty()) { 210 | return false; 211 | } 212 | return properties.entrySet().stream() 213 | .map(Map.Entry::getValue) 214 | .anyMatch(schema -> equalsIgnoreCase(schema.getFormat(), "binary")); 215 | } 216 | 217 | private ClassName getStringGenericClassName(Schema genericSchema) { 218 | if (genericSchema.getFormat() == null) { 219 | return ClassName.get(JAVA_LANG_PKG, "String"); 220 | } else if (equalsIgnoreCase(genericSchema.getFormat(), "date")) { 221 | return ClassName.get(JAVA_TIME_PKG, "LocalDate"); 222 | } else if (equalsIgnoreCase(genericSchema.getFormat(), "date-time")) { 223 | return ClassName.get(JAVA_TIME_PKG, "LocalDateTime"); 224 | } 225 | throw new IllegalArgumentException("Error parsing string based property"); 226 | } 227 | 228 | private String determineParentClassNameUsingDiscriminator(Schema innerSchema, String fieldName) { 229 | Set> discriminatorEntries = innerSchema.getDiscriminator().getMapping().entrySet(); 230 | if (CollectionUtils.isEmpty(discriminatorEntries)) { 231 | throw new IllegalArgumentException("Discriminator needs to have at least one value defined. Field: " + fieldName); 232 | } 233 | return determineParentClassName(discriminatorEntries.iterator().next().getValue(), allComponents); 234 | } 235 | 236 | private ParameterSpec.Builder getNumberBasedSchemaParameter(String fieldName, Schema innerSchema) { 237 | ParameterSpec.Builder fieldBuilder = createNumberBasedParameterWithFormat(fieldName, innerSchema); 238 | if (innerSchema.getMinimum() != null) { 239 | fieldBuilder.addAnnotation(AnnotationSpec.builder(DecimalMin.class) 240 | .addMember("value", "$S", innerSchema.getMinimum().toString()) 241 | .build() 242 | ); 243 | } 244 | if (innerSchema.getMaximum() != null) { 245 | fieldBuilder.addAnnotation(AnnotationSpec.builder(DecimalMax.class) 246 | .addMember("value", "$S", innerSchema.getMaximum().toString()) 247 | .build() 248 | ); 249 | } 250 | return fieldBuilder; 251 | } 252 | 253 | private ParameterSpec.Builder createNumberBasedParameterWithFormat(String fieldName, Schema innerSchema) { 254 | if (innerSchema.getFormat() == null || StringUtils.equalsIgnoreCase(innerSchema.getFormat(), "int32")) { 255 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Integer", fieldName); 256 | } else if (StringUtils.equalsIgnoreCase(innerSchema.getFormat(), "int64")) { 257 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Long", fieldName); 258 | } else if (StringUtils.equalsIgnoreCase(innerSchema.getFormat(), "float")) { 259 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Float", fieldName); 260 | } else if (StringUtils.equalsIgnoreCase(innerSchema.getFormat(), "double")) { 261 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Double", fieldName); 262 | } else { 263 | return createSimpleParameterSpec(JAVA_LANG_PKG, "Integer", fieldName); 264 | } 265 | } 266 | 267 | private ParameterSpec.Builder createSimpleParameterSpec(String packageName, String className, String parameterName) { 268 | ClassName simpleFieldClassName = packageName == null ? ClassName.bestGuess(targetPackage + "." + className) : ClassName.get(packageName, className); 269 | return ParameterSpec.builder(simpleFieldClassName, parameterName); 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /client-generator/src/test/java/org/spring/openapi/client/generator/OpenApiClientGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package org.spring.openapi.client.generator; 2 | 3 | import org.junit.Test; 4 | 5 | public class OpenApiClientGeneratorTest { 6 | 7 | @Test 8 | public void testClientGenerated() { 9 | new OpenApiClientGenerator().generateClient( 10 | "test.openapi", 11 | "src/test/resources/input_example.json", 12 | "target/openapi", 13 | true, 14 | true); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | pom 10 | 11 | Spring Open API 12 | OpenAPI 3 generator library to generate OpenAPI 3 json spec from Java + Spring annotated classes. 13 | https://github.com/jremenec/spring-openapi 14 | 15 | 16 | 17 | GNU General Public License v3.0 18 | https://www.gnu.org/licenses/gpl-3.0.en.html 19 | repo 20 | GNU General Public License v3.0 21 | 22 | 23 | 24 | 25 | 26 | jremenec 27 | Jakub Remenec 28 | remenec.jakub@gmail.com 29 | https://github.com/jremenec 30 | 31 | 32 | 33 | 34 | annotations 35 | schema-generator 36 | schema-v2-generator 37 | client-generator-plugin 38 | client-generator 39 | schema-generator-plugin 40 | 41 | 42 | 43 | 3.3.9 44 | 3.5.1 45 | 2.0.8 46 | 4.8.43 47 | 2.10.0.pr1 48 | 3.3.0 49 | 4.12 50 | 2.0.1.Final 51 | 2.9.9 52 | 2.6.1 53 | 1.5.0 54 | 2.6 55 | 5.1.9.RELEASE 56 | 1.11.1 57 | 4.4 58 | 1.6.0 59 | 3.9 60 | 2.4.0 61 | 1.7 62 | 1.7 63 | 64 | 65 | 66 | 67 | 68 | org.springframework 69 | spring-core 70 | ${spring-core.version} 71 | 72 | 73 | org.springframework 74 | spring-context 75 | ${spring-core.version} 76 | 77 | 78 | org.springframework 79 | spring-web 80 | ${spring-core.version} 81 | 82 | 83 | 84 | org.apache.commons 85 | commons-collections4 86 | ${commons-collections4.version} 87 | 88 | 89 | 90 | org.apache.maven.plugin-tools 91 | maven-plugin-annotations 92 | 3.5 93 | provided 94 | 95 | 96 | org.apache.maven 97 | maven-plugin-api 98 | ${maven.version} 99 | 100 | 101 | org.apache.maven 102 | maven-core 103 | ${maven.version} 104 | 105 | 106 | org.apache.maven 107 | maven-artifact 108 | ${maven.version} 109 | provided 110 | 111 | 112 | org.apache.maven 113 | maven-compat 114 | ${maven.version} 115 | 116 | 117 | 118 | io.swagger.core.v3 119 | swagger-models 120 | ${swagger-api.version} 121 | 122 | 123 | io.swagger.core.v3 124 | swagger-annotations 125 | ${swagger-api.version} 126 | 127 | 128 | io.swagger.core.v3 129 | swagger-integration 130 | ${swagger-api.version} 131 | 132 | 133 | 134 | io.swagger 135 | swagger-models 136 | ${swagger-v2-api.version} 137 | 138 | 139 | io.swagger 140 | swagger-annotations 141 | ${swagger-v2-api.version} 142 | 143 | 144 | 145 | com.squareup 146 | javapoet 147 | ${javapoet.version} 148 | 149 | 150 | 151 | com.fasterxml.jackson.core 152 | jackson-databind 153 | ${jackson-databind.version} 154 | 155 | 156 | 157 | javax.validation 158 | validation-api 159 | ${validation-api.version} 160 | 161 | 162 | 163 | com.fasterxml.jackson.core 164 | jackson-annotations 165 | ${jackson-annotations.version} 166 | 167 | 168 | 169 | org.apache.logging.log4j 170 | log4j-api 171 | ${log4j-api.version} 172 | 173 | 174 | org.apache.logging.log4j 175 | log4j-core 176 | ${log4j-api.version} 177 | 178 | 179 | org.apache.logging.log4j 180 | log4j-slf4j-impl 181 | ${log4j-api.version} 182 | 183 | 184 | 185 | org.apache.commons 186 | commons-lang3 187 | ${commons-lang3.version} 188 | 189 | 190 | 191 | com.jayway.jsonpath 192 | json-path 193 | ${json-path.version} 194 | 195 | 196 | 197 | org.skyscreamer 198 | jsonassert 199 | ${jsonassert.version} 200 | test 201 | 202 | 203 | commons-io 204 | commons-io 205 | ${commons-io.version} 206 | test 207 | 208 | 209 | org.apache.maven.plugin-testing 210 | maven-plugin-testing-harness 211 | ${maven-plugin-testing-harness.version} 212 | test 213 | 214 | 215 | junit 216 | junit 217 | ${junit.version} 218 | test 219 | 220 | 221 | 222 | 223 | 224 | scm:git:git://github.com/jremenec/spring-openapi 225 | scm:git:git@github.com:jremenec/spring-openapi.git 226 | https://github.com/jremenec/spring-openapi 227 | spring-openapi-1.4.9 228 | 229 | 230 | 231 | 232 | ossrh 233 | https://oss.sonatype.org/content/repositories/snapshots 234 | 235 | 236 | ossrh 237 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | release-sign-artifacts 246 | 247 | 248 | performRelease 249 | true 250 | 251 | 252 | 253 | 254 | 255 | maven-deploy-plugin 256 | 2.8.2 257 | 258 | 259 | default-deploy 260 | deploy 261 | 262 | deploy 263 | 264 | 265 | 266 | 267 | 268 | org.apache.maven.plugins 269 | maven-release-plugin 270 | 2.5.3 271 | 272 | true 273 | false 274 | forked-path 275 | -Dgpg.passphrase=${gpg.passphrase} 276 | 277 | 278 | 279 | org.apache.maven.scm 280 | maven-scm-provider-gitexe 281 | 1.9.5 282 | 283 | 284 | 285 | 286 | org.sonatype.plugins 287 | nexus-staging-maven-plugin 288 | 1.6.7 289 | true 290 | 291 | ossrh 292 | https://oss.sonatype.org/ 293 | true 294 | 295 | 296 | 297 | org.apache.maven.plugins 298 | maven-source-plugin 299 | 3.0.1 300 | 301 | 302 | attach-sources 303 | 304 | jar 305 | 306 | 307 | 308 | 309 | 310 | org.apache.maven.plugins 311 | maven-gpg-plugin 312 | 1.6 313 | 314 | 315 | sign-artifacts 316 | verify 317 | 318 | sign 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /schema-generator-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-schema-generator-plugin 11 | 12 | 1.4.11-SNAPSHOT 13 | maven-plugin 14 | 15 | Spring Open API - Schema generator plugin 16 | 17 | 18 | 19 | com.github.jrcodeza 20 | spring-openapi-schema-generator 21 | ${project.version} 22 | 23 | 24 | 25 | org.apache.maven.plugin-tools 26 | maven-plugin-annotations 27 | provided 28 | 29 | 30 | org.apache.maven 31 | maven-plugin-api 32 | 33 | 34 | org.apache.maven 35 | maven-core 36 | 37 | 38 | org.apache.maven 39 | maven-artifact 40 | provided 41 | 42 | 43 | org.apache.maven 44 | maven-compat 45 | 46 | 47 | 48 | com.fasterxml.jackson.core 49 | jackson-databind 50 | 51 | 52 | com.fasterxml.jackson.core 53 | jackson-annotations 54 | 55 | 56 | 57 | javax.validation 58 | validation-api 59 | 60 | 61 | 62 | io.swagger.core.v3 63 | swagger-annotations 64 | test 65 | 66 | 67 | javax.validation 68 | validation-api 69 | test 70 | 71 | 72 | org.apache.maven.plugin-testing 73 | maven-plugin-testing-harness 74 | test 75 | 76 | 77 | junit 78 | junit 79 | test 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-compiler-plugin 88 | 89 | 8 90 | 8 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-plugin-plugin 96 | 3.5 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/main/java/com/github/jrcodeza/schema/generator/plugin/GenerateOpenApiSchemaMojo.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import com.github.jrcodeza.schema.generator.OpenAPIGenerator; 11 | import com.github.jrcodeza.schema.generator.config.OpenApiGeneratorConfig; 12 | import com.github.jrcodeza.schema.generator.config.builder.OpenApiGeneratorConfigBuilder; 13 | import com.github.jrcodeza.schema.generator.filters.OperationFilter; 14 | import com.github.jrcodeza.schema.generator.filters.OperationParameterFilter; 15 | import com.github.jrcodeza.schema.generator.filters.SchemaFieldFilter; 16 | import com.github.jrcodeza.schema.generator.interceptors.OperationInterceptor; 17 | import com.github.jrcodeza.schema.generator.interceptors.OperationParameterInterceptor; 18 | import com.github.jrcodeza.schema.generator.interceptors.RequestBodyInterceptor; 19 | import com.github.jrcodeza.schema.generator.interceptors.SchemaFieldInterceptor; 20 | import com.github.jrcodeza.schema.generator.interceptors.SchemaInterceptor; 21 | import com.github.jrcodeza.schema.generator.interceptors.examples.OpenApiExampleResolver; 22 | 23 | import org.apache.commons.lang3.BooleanUtils; 24 | import org.apache.commons.lang3.StringUtils; 25 | import org.apache.maven.plugin.AbstractMojo; 26 | import org.apache.maven.plugins.annotations.LifecyclePhase; 27 | import org.apache.maven.plugins.annotations.Mojo; 28 | import org.apache.maven.plugins.annotations.Parameter; 29 | 30 | import io.swagger.v3.oas.models.OpenAPI; 31 | import io.swagger.v3.oas.models.info.Info; 32 | 33 | import static java.util.Arrays.asList; 34 | 35 | @Mojo(name = "generateOpenApi", defaultPhase = LifecyclePhase.INSTALL) 36 | public class GenerateOpenApiSchemaMojo extends AbstractMojo { 37 | 38 | @Parameter(required = true) 39 | private String title; 40 | 41 | @Parameter 42 | private String description; 43 | 44 | @Parameter(required = true) 45 | private String version; 46 | 47 | @Parameter(required = true) 48 | private String[] modelPackages; 49 | 50 | @Parameter(required = true) 51 | private String[] controllerBasePackages; 52 | 53 | @Parameter(required = true) 54 | private String outputDirectory; 55 | 56 | @Parameter 57 | private List schemaInterceptors; 58 | 59 | @Parameter 60 | private List schemaFieldInterceptors; 61 | 62 | @Parameter 63 | private List operationParameterInterceptors; 64 | 65 | @Parameter 66 | private List operationInterceptors; 67 | 68 | @Parameter 69 | private List requestBodyInterceptors; 70 | 71 | @Parameter 72 | private String operationFilter; 73 | 74 | @Parameter 75 | private String operationParameterFilter; 76 | 77 | @Parameter 78 | private String schemaFieldFilter; 79 | 80 | @Parameter 81 | private Boolean generateExamples; 82 | 83 | @Parameter 84 | private String openApiExamplesResolver; 85 | 86 | public void execute() { 87 | OpenAPIGenerator openApiGenerator = new OpenAPIGenerator( 88 | asList(modelPackages), asList(controllerBasePackages), createInfoFromParameters(), 89 | parseInputInterceptors(schemaInterceptors, SchemaInterceptor.class), 90 | parseInputInterceptors(schemaFieldInterceptors, SchemaFieldInterceptor.class), 91 | parseInputInterceptors(operationParameterInterceptors, OperationParameterInterceptor.class), 92 | parseInputInterceptors(operationInterceptors, OperationInterceptor.class), 93 | parseInputInterceptors(requestBodyInterceptors, RequestBodyInterceptor.class), 94 | parseInputFilter(operationFilter, OperationFilter.class), 95 | parseInputFilter(operationParameterFilter, OperationParameterFilter.class), 96 | parseInputFilter(schemaFieldFilter, SchemaFieldFilter.class) 97 | ); 98 | 99 | OpenApiGeneratorConfig openApiGeneratorConfig = OpenApiGeneratorConfigBuilder.defaultConfig().build(); 100 | if (BooleanUtils.isTrue(generateExamples)) { 101 | openApiGeneratorConfig.setGenerateExamples(true); 102 | if (StringUtils.isNotBlank(openApiExamplesResolver)) { 103 | openApiGeneratorConfig.setOpenApiExampleResolver(instantiateClass(openApiExamplesResolver, OpenApiExampleResolver.class)); 104 | } 105 | } 106 | OpenAPI openAPI = openApiGenerator.generate(openApiGeneratorConfig); 107 | 108 | ObjectMapper objectMapper = new ObjectMapper(); 109 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 110 | 111 | try { 112 | if (!new File(outputDirectory).mkdirs()) { 113 | getLog().error(String.format("Error creating directories for path [%s]", outputDirectory)); 114 | return; 115 | } 116 | objectMapper.writeValue(new File(outputDirectory + "/swagger.json"), openAPI); 117 | } catch (IOException e) { 118 | getLog().error("Cannot serialize generated OpenAPI spec", e); 119 | } 120 | } 121 | 122 | private T parseInputFilter(String className, Class clazz) { 123 | if (StringUtils.isEmpty(className)) { 124 | return null; 125 | } 126 | 127 | return instantiateClass(className, clazz); 128 | } 129 | 130 | private List parseInputInterceptors(List classNames, Class clazz) { 131 | if (classNames == null || classNames.isEmpty()) { 132 | return new ArrayList<>(); 133 | } 134 | List result = new ArrayList<>(); 135 | for (String className : classNames) { 136 | result.add(instantiateClass(className, clazz)); 137 | } 138 | 139 | return result; 140 | } 141 | 142 | private T instantiateClass(String className, Class superClass) { 143 | try { 144 | Class inputClass = Class.forName(className); 145 | if (superClass.isAssignableFrom(inputClass)) { 146 | return (T) inputClass.newInstance(); 147 | } else { 148 | getLog().error(String.format("Incorrect class type = [%s]. Expected is = [%s]", className, superClass.getName())); 149 | } 150 | } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) { 151 | getLog().error(e.getMessage()); 152 | System.exit(1); 153 | } 154 | return null; 155 | } 156 | 157 | private Info createInfoFromParameters() { 158 | Info info = new Info(); 159 | info.setTitle(title); 160 | info.setDescription(description); 161 | info.setVersion(version); 162 | return info; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/GenerateOpenApiSchemaMojoTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin; 2 | 3 | import java.io.File; 4 | 5 | import org.apache.maven.plugin.testing.AbstractMojoTestCase; 6 | import org.junit.Test; 7 | import com.github.jrcodeza.schema.generator.plugin.GenerateOpenApiSchemaMojo; 8 | 9 | public class GenerateOpenApiSchemaMojoTest extends AbstractMojoTestCase { 10 | 11 | @Override 12 | protected void setUp() throws Exception { 13 | super.setUp(); 14 | } 15 | 16 | @Test 17 | public void testStandardScenario() throws Exception { 18 | File testPom = new File(getBasedir(), "src/test/resources/unit/generate-open-api-standard/pom.xml"); 19 | GenerateOpenApiSchemaMojo mojo = (GenerateOpenApiSchemaMojo) lookupMojo("generateOpenApi", testPom); 20 | mojo.execute(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/controller/MojoTestController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin.controller; 2 | 3 | import com.github.jrcodeza.schema.generator.plugin.model.OpenApiTestModel; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping("mojoTest") 11 | public class MojoTestController { 12 | 13 | @GetMapping 14 | private OpenApiTestModel getMojoTest(@RequestParam String mojoTestParam) { 15 | return null; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/example/TestExampleResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin.example; 2 | 3 | import com.github.jrcodeza.schema.generator.interceptors.examples.OpenApiExampleResolver; 4 | 5 | public class TestExampleResolver implements OpenApiExampleResolver { 6 | 7 | @Override 8 | public String resolveExample(String exampleKey) { 9 | return "Resolved example"; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/interceptor/TestOperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin.interceptor; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import com.github.jrcodeza.schema.generator.interceptors.OperationInterceptor; 6 | 7 | import io.swagger.v3.oas.models.Operation; 8 | 9 | public class TestOperationInterceptor implements OperationInterceptor { 10 | 11 | @Override 12 | public void intercept(Method method, Operation transformedOperation) { 13 | transformedOperation.setSummary("Interceptor summary"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/interceptor/TestSchemaFieldInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin.interceptor; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import com.github.jrcodeza.schema.generator.interceptors.SchemaFieldInterceptor; 6 | 7 | import io.swagger.v3.oas.models.media.Schema; 8 | 9 | public class TestSchemaFieldInterceptor implements SchemaFieldInterceptor { 10 | 11 | @Override 12 | public void intercept(Class clazz, Field field, Schema transformedFieldSchema) { 13 | transformedFieldSchema.setDescription("Schema field description"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/java/com/github/jrcodeza/schema/generator/plugin/model/OpenApiTestModel.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.plugin.model; 2 | 3 | import com.github.jrcodeza.OpenApiExample; 4 | 5 | public class OpenApiTestModel { 6 | 7 | @OpenApiExample(name = "StandardExample", value = "standardExampleValue") 8 | private String attribute; 9 | 10 | @OpenApiExample(name = "KeyExample", key = "KEY") 11 | private String keyExample; 12 | 13 | public String getAttribute() { 14 | return attribute; 15 | } 16 | 17 | public void setAttribute(String attribute) { 18 | this.attribute = attribute; 19 | } 20 | 21 | public String getKeyExample() { 22 | return keyExample; 23 | } 24 | 25 | public void setKeyExample(String keyExample) { 26 | this.keyExample = keyExample; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /schema-generator-plugin/src/test/resources/unit/generate-open-api-standard/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | org.apache.maven.plugin.my.unit 7 | generate-open-api-schema-mojo-test 8 | 1.0-SNAPSHOT 9 | jar 10 | Test GenerateOpenApiSchemaMojo Standard 11 | 12 | 13 | 14 | junit 15 | junit 16 | 3.8.1 17 | test 18 | 19 | 20 | 21 | 22 | 23 | 24 | com.github.jrcodeza 25 | spring-openapi-schema-generator-plugin 26 | 27 | Test title 28 | Test description 29 | 1.0.0-TEST 30 | 31 | com.github.jrcodeza.schema.generator.plugin.model.* 32 | 33 | 34 | com.github.jrcodeza.schema.generator.plugin.controller.* 35 | 36 | target/openapi 37 | 38 | com.github.jrcodeza.schema.generator.plugin.interceptor.TestSchemaFieldInterceptor 39 | 40 | 41 | com.github.jrcodeza.schema.generator.plugin.interceptor.TestOperationInterceptor 42 | 43 | true 44 | com.github.jrcodeza.schema.generator.plugin.example.TestExampleResolver 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /schema-generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-schema-generator 11 | 12 | 1.4.11-SNAPSHOT 13 | jar 14 | 15 | Spring Open API - Schema generator 16 | 17 | 18 | 19 | com.github.jrcodeza 20 | spring-openapi-annotations 21 | ${project.version} 22 | 23 | 24 | 25 | org.springframework 26 | spring-core 27 | 28 | 29 | org.springframework 30 | spring-context 31 | 32 | 33 | org.springframework 34 | spring-web 35 | 36 | 37 | 38 | io.swagger.core.v3 39 | swagger-models 40 | 41 | 42 | io.swagger.core.v3 43 | swagger-annotations 44 | 45 | 46 | io.swagger.core.v3 47 | swagger-integration 48 | 49 | 50 | 51 | com.fasterxml.jackson.core 52 | jackson-databind 53 | 54 | 55 | com.fasterxml.jackson.core 56 | jackson-annotations 57 | 58 | 59 | 60 | javax.validation 61 | validation-api 62 | 63 | 64 | 65 | org.apache.logging.log4j 66 | log4j-api 67 | 68 | 69 | org.apache.logging.log4j 70 | log4j-core 71 | 72 | 73 | org.apache.logging.log4j 74 | log4j-slf4j-impl 75 | 76 | 77 | 78 | org.skyscreamer 79 | jsonassert 80 | 81 | 82 | commons-io 83 | commons-io 84 | 85 | 86 | junit 87 | junit 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-compiler-plugin 96 | 97 | 8 98 | 8 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/ComponentSchemaTransformer.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator; 2 | 3 | import com.github.jrcodeza.schema.generator.filters.SchemaFieldFilter; 4 | import com.github.jrcodeza.schema.generator.interceptors.SchemaFieldInterceptor; 5 | import com.github.jrcodeza.schema.generator.model.CustomComposedSchema; 6 | import com.github.jrcodeza.schema.generator.model.InheritanceInfo; 7 | import com.github.jrcodeza.schema.generator.util.SchemaGeneratorHelper; 8 | import io.swagger.v3.oas.models.media.Discriminator; 9 | import io.swagger.v3.oas.models.media.ObjectSchema; 10 | import io.swagger.v3.oas.models.media.Schema; 11 | import io.swagger.v3.oas.models.media.StringSchema; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.springframework.util.ReflectionUtils; 14 | 15 | import javax.validation.constraints.NotNull; 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.ParameterizedType; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | import java.util.stream.Collectors; 27 | import java.util.stream.Stream; 28 | 29 | import static com.github.jrcodeza.schema.generator.util.CommonConstants.COMPONENT_REF_PREFIX; 30 | import static com.github.jrcodeza.schema.generator.util.GeneratorUtils.shouldBeIgnored; 31 | 32 | 33 | public class ComponentSchemaTransformer { 34 | 35 | private final List schemaFieldInterceptors; 36 | private AtomicReference schemaFieldFilter; 37 | private final SchemaGeneratorHelper schemaGeneratorHelper; 38 | 39 | public ComponentSchemaTransformer(List schemaFieldInterceptors, 40 | AtomicReference schemaFieldFilter, 41 | SchemaGeneratorHelper schemaGeneratorHelper) { 42 | this.schemaFieldInterceptors = schemaFieldInterceptors; 43 | this.schemaFieldFilter = schemaFieldFilter; 44 | this.schemaGeneratorHelper = schemaGeneratorHelper; 45 | } 46 | 47 | public Schema transformSimpleSchema(Class clazz, Map inheritanceMap) { 48 | if (clazz.isEnum()) { 49 | return schemaGeneratorHelper.createEnumSchema(clazz.getEnumConstants()); 50 | } 51 | List requiredFields = new ArrayList<>(); 52 | 53 | Schema schema = new Schema<>(); 54 | schema.setType("object"); 55 | schema.setProperties(getClassProperties(clazz, requiredFields)); 56 | schemaGeneratorHelper.enrichWithTypeAnnotations(schema, clazz.getDeclaredAnnotations()); 57 | 58 | updateRequiredFields(schema, requiredFields); 59 | 60 | if (inheritanceMap.containsKey(clazz.getName())) { 61 | Discriminator discriminator = createDiscriminator(inheritanceMap.get(clazz.getName())); 62 | schema.setDiscriminator(discriminator); 63 | enrichWithDiscriminatorProperty(schema, discriminator); 64 | } 65 | if (clazz.getSuperclass() != null) { 66 | return traverseAndAddProperties(schema, inheritanceMap, clazz.getSuperclass(), clazz); 67 | } 68 | return schema; 69 | } 70 | 71 | private Discriminator createDiscriminator(InheritanceInfo inheritanceInfo) { 72 | Map discriminatorTypeMapping = inheritanceInfo.getDiscriminatorClassMap().entrySet() 73 | .stream() 74 | .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); 75 | 76 | Discriminator discriminator = new Discriminator(); 77 | discriminator.setPropertyName(inheritanceInfo.getDiscriminatorFieldName()); 78 | discriminator.setMapping(discriminatorTypeMapping); 79 | return discriminator; 80 | } 81 | 82 | private void updateRequiredFields(Schema schema, List requiredFields) { 83 | if (requiredFields == null || requiredFields.isEmpty()) { 84 | return; 85 | } 86 | if (schema.getRequired() == null) { 87 | schema.setRequired(requiredFields); 88 | return; 89 | } 90 | schema.getRequired().addAll(requiredFields); 91 | } 92 | 93 | private void updateSchemaProperties(Schema schema, String propertyName, Schema propertyValue) { 94 | if (StringUtils.isBlank(propertyName) || propertyValue == null) { 95 | return; 96 | } 97 | if (schema.getProperties() == null) { 98 | schema.setProperties(new HashMap<>()); 99 | } 100 | schema.getProperties().put(propertyName, propertyValue); 101 | } 102 | 103 | private void enrichWithDiscriminatorProperty(Schema schema, Discriminator discriminator) { 104 | if (schema != null && !schema.getProperties().containsKey(discriminator.getPropertyName())) { 105 | List discriminatorTypeRequiredProperty = new ArrayList<>(); 106 | discriminatorTypeRequiredProperty.add(discriminator.getPropertyName()); 107 | 108 | updateSchemaProperties(schema, discriminator.getPropertyName(), new StringSchema()); 109 | updateRequiredFields(schema, discriminatorTypeRequiredProperty); 110 | } 111 | } 112 | 113 | private Schema traverseAndAddProperties(Schema schema, Map inheritanceMap, Class superclass, Class actualClass) { 114 | if (!schemaGeneratorHelper.isInPackagesToBeScanned(superclass)) { 115 | // adding properties from parent classes is present due to swagger ui bug, after using different ui 116 | // this becomes relevant only for third party packages 117 | List requiredFields = new ArrayList<>(); 118 | schema.getProperties().putAll(getClassProperties(superclass, requiredFields)); 119 | updateRequiredFields(schema, requiredFields); 120 | if (superclass.getSuperclass() != null && !"java.lang".equals(superclass.getSuperclass().getPackage().getName())) { 121 | return traverseAndAddProperties(schema, inheritanceMap, superclass.getSuperclass(), superclass); 122 | } 123 | return schema; 124 | } else { 125 | Schema parentClassSchema = new Schema<>(); 126 | parentClassSchema.set$ref(COMPONENT_REF_PREFIX + superclass.getSimpleName()); 127 | 128 | CustomComposedSchema composedSchema = new CustomComposedSchema(); 129 | enrichWithAdditionalProperties(composedSchema, inheritanceMap, superclass.getName(), actualClass.getSimpleName()); 130 | composedSchema.setAllOf(Arrays.asList(parentClassSchema, schema)); 131 | composedSchema.setDescription(schema.getDescription()); 132 | return composedSchema; 133 | } 134 | } 135 | 136 | private void enrichWithAdditionalProperties(CustomComposedSchema customComposedSchema, Map inheritanceInfoMap, String superClassName, 137 | String actualClassName) { 138 | if (inheritanceInfoMap.containsKey(superClassName)) { 139 | Map discriminatorClassMap = inheritanceInfoMap.get(superClassName).getDiscriminatorClassMap(); 140 | if (discriminatorClassMap.containsKey(actualClassName)) { 141 | customComposedSchema.setDiscriminatorValue(discriminatorClassMap.get(actualClassName)); 142 | } 143 | } 144 | } 145 | 146 | private Map getClassProperties(Class clazz, List requiredFields) { 147 | Map classPropertyMap = new HashMap<>(); 148 | ReflectionUtils.doWithLocalFields(clazz, 149 | field -> getFieldSchema(clazz, field, requiredFields).ifPresent(schema -> { 150 | schemaFieldInterceptors.forEach(modelClassFieldInterceptor -> modelClassFieldInterceptor.intercept(clazz, field, schema)); 151 | classPropertyMap.put(field.getName(), schema); 152 | }) 153 | ); 154 | return classPropertyMap; 155 | } 156 | 157 | private Optional getFieldSchema(Class clazz, Field field, List requiredFields) { 158 | if (shouldIgnoreField(clazz, field)) { 159 | return Optional.empty(); 160 | } 161 | 162 | Class typeSignature = field.getType(); 163 | Annotation[] annotations = field.getAnnotations(); 164 | if (isRequired(annotations)) { 165 | requiredFields.add(field.getName()); 166 | } 167 | 168 | if (typeSignature.isPrimitive()) { 169 | return createBaseTypeSchema(field, requiredFields, annotations); 170 | } else if (typeSignature.isArray()) { 171 | return createArrayTypeSchema(typeSignature, annotations); 172 | } else if (StringUtils.equalsIgnoreCase(typeSignature.getName(), "java.lang.Object")) { 173 | ObjectSchema objectSchema = new ObjectSchema(); 174 | objectSchema.setName(field.getName()); 175 | return Optional.of(objectSchema); 176 | } else if (typeSignature.isAssignableFrom(List.class)) { 177 | if (field.getGenericType() instanceof ParameterizedType) { 178 | Class listGenericParameter = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; 179 | return Optional.of(schemaGeneratorHelper.parseArraySignature(listGenericParameter, annotations)); 180 | } 181 | return Optional.empty(); 182 | } else { 183 | return createClassRefSchema(typeSignature, annotations); 184 | } 185 | } 186 | 187 | private boolean shouldIgnoreField(Class clazz, Field field) { 188 | if (shouldBeIgnored(field)) { 189 | return true; 190 | } 191 | 192 | return schemaFieldFilter.get() != null && schemaFieldFilter.get().shouldIgnore(clazz, field); 193 | } 194 | 195 | private Optional createClassRefSchema(Class typeClass, Annotation[] annotations) { 196 | Schema schema = schemaGeneratorHelper.parseClassRefTypeSignature(typeClass, annotations); 197 | schemaGeneratorHelper.enrichWithTypeAnnotations(schema, annotations); 198 | return Optional.ofNullable(schema); 199 | } 200 | 201 | private Optional createArrayTypeSchema(Class typeSignature, Annotation[] annotations) { 202 | Class arrayComponentType = typeSignature.getComponentType(); 203 | Schema schema = schemaGeneratorHelper.parseArraySignature(arrayComponentType, annotations); 204 | schemaGeneratorHelper.enrichWithTypeAnnotations(schema, annotations); 205 | return Optional.ofNullable(schema); 206 | } 207 | 208 | private Optional createBaseTypeSchema(Field field, List requiredFields, Annotation[] annotations) { 209 | if (!requiredFields.contains(field.getName())) { 210 | requiredFields.add(field.getName()); 211 | } 212 | Schema schema = schemaGeneratorHelper.parseBaseTypeSignature(field.getType(), annotations); 213 | schemaGeneratorHelper.enrichWithTypeAnnotations(schema, annotations); 214 | return Optional.ofNullable(schema); 215 | } 216 | 217 | private boolean isRequired(Annotation[] annotations) { 218 | return Stream.of(annotations).anyMatch(annotation -> annotation instanceof NotNull); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/config/OpenApiGeneratorConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.config; 2 | 3 | import org.springframework.core.env.Environment; 4 | 5 | import com.github.jrcodeza.schema.generator.interceptors.examples.OpenApiExampleResolver; 6 | 7 | public class OpenApiGeneratorConfig { 8 | 9 | private boolean generateExamples; 10 | 11 | private OpenApiExampleResolver openApiExampleResolver; 12 | 13 | private Environment environment; 14 | 15 | public boolean isGenerateExamples() { 16 | return generateExamples; 17 | } 18 | 19 | public void setGenerateExamples(boolean generateExamples) { 20 | this.generateExamples = generateExamples; 21 | } 22 | 23 | public OpenApiExampleResolver getOpenApiExampleResolver() { 24 | return openApiExampleResolver; 25 | } 26 | 27 | public void setOpenApiExampleResolver(OpenApiExampleResolver openApiExampleResolver) { 28 | this.openApiExampleResolver = openApiExampleResolver; 29 | } 30 | 31 | public Environment getEnvironment() { 32 | return environment; 33 | } 34 | 35 | public void setEnvironment(Environment environment) { 36 | this.environment = environment; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/config/builder/OpenApiGeneratorConfigBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.config.builder; 2 | 3 | import org.springframework.core.env.Environment; 4 | 5 | import com.github.jrcodeza.schema.generator.config.OpenApiGeneratorConfig; 6 | import com.github.jrcodeza.schema.generator.interceptors.examples.OpenApiExampleResolver; 7 | 8 | public final class OpenApiGeneratorConfigBuilder { 9 | 10 | private OpenApiGeneratorConfig openApiGeneratorConfig; 11 | 12 | private OpenApiGeneratorConfigBuilder() { 13 | openApiGeneratorConfig = new OpenApiGeneratorConfig(); 14 | } 15 | 16 | public static OpenApiGeneratorConfigBuilder defaultConfig() { 17 | return new OpenApiGeneratorConfigBuilder(); 18 | } 19 | 20 | public OpenApiGeneratorConfigBuilder withGenerateExamples(boolean generateExamples) { 21 | openApiGeneratorConfig.setGenerateExamples(generateExamples); 22 | return this; 23 | } 24 | 25 | public OpenApiGeneratorConfigBuilder withOpenApiExampleResolver(OpenApiExampleResolver openApiExampleResolver) { 26 | openApiGeneratorConfig.setOpenApiExampleResolver(openApiExampleResolver); 27 | return this; 28 | } 29 | 30 | public OpenApiGeneratorConfigBuilder withEnvironment(Environment environment) { 31 | openApiGeneratorConfig.setEnvironment(environment); 32 | return this; 33 | } 34 | 35 | public OpenApiGeneratorConfig build() { 36 | return openApiGeneratorConfig; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/filters/OperationFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public interface OperationFilter { 6 | boolean shouldIgnore(Method method); 7 | } 8 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/filters/OperationParameterFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | public interface OperationParameterFilter { 7 | boolean shouldIgnore(Method method, Parameter parameter, String parameterName); 8 | } 9 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/filters/SchemaFieldFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public interface SchemaFieldFilter { 6 | boolean shouldIgnore(Class clazz, Field field); 7 | } 8 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/OperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import io.swagger.v3.oas.models.Operation; 6 | 7 | public interface OperationInterceptor { 8 | 9 | void intercept(Method method, Operation transformedOperation); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/OperationParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | public interface OperationParameterInterceptor { 7 | 8 | void intercept(Method method, Parameter parameter, String parameterName, 9 | io.swagger.v3.oas.models.parameters.Parameter transformedParameter); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/RequestBodyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | import io.swagger.v3.oas.models.parameters.RequestBody; 7 | 8 | public interface RequestBodyInterceptor { 9 | 10 | void intercept(Method method, Parameter parameter, String parameterName, RequestBody transformedRequestBody); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/SchemaFieldInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import io.swagger.v3.oas.models.media.Schema; 6 | 7 | public interface SchemaFieldInterceptor { 8 | 9 | void intercept(Class clazz, Field field, Schema transformedFieldSchema); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/SchemaInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import io.swagger.v3.oas.models.media.Schema; 4 | 5 | public interface SchemaInterceptor { 6 | 7 | void intercept(Class clazz, Schema transformedSchema); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/examples/OpenApiExampleResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors.examples; 2 | 3 | public interface OpenApiExampleResolver { 4 | 5 | String resolveExample(String exampleKey); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/interceptors/examples/OperationParameterExampleInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors.examples; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.AnnotatedElement; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Parameter; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.LinkedHashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.UUID; 15 | import java.util.function.Consumer; 16 | import java.util.stream.Stream; 17 | 18 | import com.github.jrcodeza.OpenApiExample; 19 | import com.github.jrcodeza.OpenApiExamples; 20 | import com.github.jrcodeza.schema.generator.interceptors.OperationParameterInterceptor; 21 | import com.github.jrcodeza.schema.generator.interceptors.RequestBodyInterceptor; 22 | import com.github.jrcodeza.schema.generator.interceptors.SchemaFieldInterceptor; 23 | import com.github.jrcodeza.schema.generator.interceptors.SchemaInterceptor; 24 | 25 | import org.apache.commons.lang3.StringUtils; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import io.swagger.v3.oas.models.examples.Example; 30 | import io.swagger.v3.oas.models.media.MediaType; 31 | import io.swagger.v3.oas.models.media.Schema; 32 | import io.swagger.v3.oas.models.parameters.RequestBody; 33 | 34 | import static org.apache.commons.lang3.StringUtils.defaultString; 35 | 36 | public class OperationParameterExampleInterceptor implements OperationParameterInterceptor, RequestBodyInterceptor, SchemaFieldInterceptor, SchemaInterceptor { 37 | 38 | private static Logger logger = LoggerFactory.getLogger(OperationParameterExampleInterceptor.class); 39 | 40 | private final OpenApiExampleResolver openApiExampleResolver; 41 | 42 | public OperationParameterExampleInterceptor(OpenApiExampleResolver openApiExampleResolver) { 43 | this.openApiExampleResolver = openApiExampleResolver; 44 | } 45 | 46 | @Override 47 | public void intercept(Method method, Parameter parameter, String parameterName, io.swagger.v3.oas.models.parameters.Parameter transformedParameter) { 48 | updateExamples(parameter, transformedParameter::setExample, transformedParameter::setExamples); 49 | } 50 | 51 | @Override 52 | public void intercept(Method method, Parameter parameter, String parameterName, RequestBody transformedRequestBody) { 53 | MediaType requestBodyMediaType = getRequestBodyMediaType(transformedRequestBody); 54 | if (requestBodyMediaType == null) { 55 | return; 56 | } 57 | updateExamples(parameter, requestBodyMediaType::setExample, requestBodyMediaType::setExamples); 58 | } 59 | 60 | @Override 61 | public void intercept(Class clazz, Field field, Schema transformedFieldSchema) { 62 | updateExamples(field, transformedFieldSchema::setExample, stringExampleMap -> transformedFieldSchema.setExample(getFirstExampleFromMap(stringExampleMap))); 63 | } 64 | 65 | @Override 66 | public void intercept(Class clazz, Schema transformedSchema) { 67 | updateExamples(clazz, transformedSchema::setExample, stringExampleMap -> transformedSchema.setExample(getFirstExampleFromMap(stringExampleMap))); 68 | } 69 | 70 | private MediaType getRequestBodyMediaType(RequestBody transformedRequestBody) { 71 | if (transformedRequestBody == null || transformedRequestBody.getContent() == null || transformedRequestBody.getContent().isEmpty()) { 72 | return null; 73 | } 74 | Object object = ((LinkedHashMap) transformedRequestBody.getContent()).values().iterator().next(); 75 | if (object instanceof MediaType) { 76 | return (MediaType) object; 77 | } 78 | return null; 79 | } 80 | 81 | private Example getFirstExampleFromMap(Map exampleMap) { 82 | if (exampleMap == null || exampleMap.isEmpty()) { 83 | return null; 84 | } 85 | return exampleMap.entrySet().iterator().next().getValue(); 86 | } 87 | 88 | private void updateExamples(AnnotatedElement annotatedElement, Consumer exampleUpdater, Consumer> examplesUpdater) { 89 | List openApiExamples = extractExampleAnnotations(annotatedElement.getAnnotations()); 90 | if (openApiExamples.isEmpty()) { 91 | return; 92 | } 93 | 94 | if (openApiExamples.size() == 1) { 95 | Example example = createExample(openApiExamples.get(0)); 96 | if (example != null) { 97 | exampleUpdater.accept(example); 98 | } 99 | } else { 100 | Map exampleMap = new HashMap<>(); 101 | openApiExamples.forEach(openApiExample -> { 102 | Example example = createExample(openApiExample); 103 | if (example != null) { 104 | exampleMap.put(defaultString(openApiExample.name(), UUID.randomUUID().toString()), createExample(openApiExample)); 105 | } 106 | }); 107 | examplesUpdater.accept(exampleMap); 108 | } 109 | } 110 | 111 | private Example createExample(OpenApiExample openApiExample) { 112 | if (StringUtils.isBlank(openApiExample.value())) { 113 | if (StringUtils.isBlank(openApiExample.key())) { 114 | logger.warn("None of [OpenApiExample.key,OpenApiExample.value] was defined."); 115 | return null; 116 | } else if (openApiExampleResolver == null) { 117 | logger.warn("OpenApiExample.key was defined but no OpenApiExampleResolver was found."); 118 | return null; 119 | } else { 120 | return createExample(openApiExampleResolver.resolveExample(openApiExample.key()), openApiExample.description()); 121 | } 122 | } else { 123 | return createExample(openApiExample.value(), openApiExample.description()); 124 | } 125 | } 126 | 127 | private Example createExample(String resolveExample, String description) { 128 | Example example = new Example(); 129 | example.setValue(resolveExample); 130 | example.setDescription(StringUtils.defaultIfBlank(description, null)); 131 | return example; 132 | } 133 | 134 | private List extractExampleAnnotations(Annotation[] annotations) { 135 | OpenApiExamples openApiExamplesAnnotation = getAnnotation(annotations, OpenApiExamples.class) ; 136 | if (openApiExamplesAnnotation == null) { 137 | OpenApiExample openApiExample = getAnnotation(annotations, OpenApiExample.class); 138 | if (openApiExample == null) { 139 | return Collections.emptyList(); 140 | } 141 | return Collections.singletonList(openApiExample); 142 | } else { 143 | return Arrays.asList(openApiExamplesAnnotation.value()); 144 | } 145 | } 146 | 147 | private T getAnnotation(Annotation[] annotations, Class clazz) { 148 | return (T) Stream.of(annotations) 149 | .filter(annotation -> annotation.annotationType().equals(clazz)) 150 | .findFirst() 151 | .orElse(null); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/model/CustomComposedSchema.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import io.swagger.v3.oas.models.media.ComposedSchema; 6 | 7 | public class CustomComposedSchema extends ComposedSchema { 8 | 9 | public static final String X_DISCRIMINATOR_VALUE = "x-discriminator-value"; 10 | 11 | @JsonProperty(X_DISCRIMINATOR_VALUE) 12 | private String discriminatorValue; 13 | 14 | public String getDiscriminatorValue() { 15 | return discriminatorValue; 16 | } 17 | 18 | public void setDiscriminatorValue(String discriminatorValue) { 19 | this.discriminatorValue = discriminatorValue; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/model/Header.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.model; 2 | 3 | public class Header { 4 | 5 | private String name; 6 | private String description; 7 | private boolean required = false; 8 | 9 | public Header(String name) { 10 | this.name = name; 11 | } 12 | 13 | public Header(String name, String description) { 14 | this.name = name; 15 | this.description = description; 16 | } 17 | 18 | public Header(String name, String description, boolean required) { 19 | this.name = name; 20 | this.description = description; 21 | this.required = required; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getDescription() { 33 | return description; 34 | } 35 | 36 | public void setDescription(String description) { 37 | this.description = description; 38 | } 39 | 40 | public boolean isRequired() { 41 | return required; 42 | } 43 | 44 | public void setRequired(boolean required) { 45 | this.required = required; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/model/InheritanceInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.model; 2 | 3 | import com.github.jrcodeza.OpenApiIgnore; 4 | 5 | import java.util.Map; 6 | 7 | @OpenApiIgnore 8 | public class InheritanceInfo { 9 | 10 | private String discriminatorFieldName; 11 | private Map discriminatorClassMap; 12 | 13 | public String getDiscriminatorFieldName() { 14 | return discriminatorFieldName; 15 | } 16 | 17 | public void setDiscriminatorFieldName(String discriminatorName) { 18 | this.discriminatorFieldName = discriminatorName; 19 | } 20 | 21 | public Map getDiscriminatorClassMap() { 22 | return discriminatorClassMap; 23 | } 24 | 25 | public void setDiscriminatorClassMap(Map discriminatorClassMap) { 26 | this.discriminatorClassMap = discriminatorClassMap; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/util/CommonConstants.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.util; 2 | 3 | public final class CommonConstants { 4 | 5 | public static final String COMPONENT_REF_PREFIX = "#/components/schemas/"; 6 | 7 | private CommonConstants() { 8 | throw new AssertionError(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-generator/src/main/java/com/github/jrcodeza/schema/generator/util/GeneratorUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.util; 2 | 3 | import java.lang.reflect.AnnotatedElement; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Modifier; 6 | 7 | import com.github.jrcodeza.OpenApiIgnore; 8 | 9 | public final class GeneratorUtils { 10 | 11 | private GeneratorUtils() { 12 | throw new AssertionError(); 13 | } 14 | 15 | public static boolean shouldBeIgnored(AnnotatedElement annotatedElement) { 16 | return annotatedElement.getAnnotation(OpenApiIgnore.class) != null; 17 | } 18 | 19 | public static boolean shouldBeIgnored(Field field) { 20 | return Modifier.isStatic(field.getModifiers()) || shouldBeIgnored((AnnotatedElement) field); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/OpenAPIGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.charset.StandardCharsets; 6 | 7 | import com.fasterxml.jackson.annotation.JsonInclude; 8 | import com.fasterxml.jackson.core.JsonProcessingException; 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import com.github.jrcodeza.schema.generator.config.builder.OpenApiGeneratorConfigBuilder; 11 | import com.github.jrcodeza.schema.generator.filters.TestOperationFilter; 12 | import com.github.jrcodeza.schema.generator.filters.TestOperationParameterFilter; 13 | import com.github.jrcodeza.schema.generator.filters.TestSchemaFieldFilter; 14 | import com.github.jrcodeza.schema.generator.interceptors.TestOperationInterceptor; 15 | import com.github.jrcodeza.schema.generator.interceptors.TestOperationParameterInterceptor; 16 | import com.github.jrcodeza.schema.generator.interceptors.TestRequestBodyInterceptor; 17 | import com.github.jrcodeza.schema.generator.interceptors.TestSchemaFieldInterceptor; 18 | import com.github.jrcodeza.schema.generator.interceptors.TestSchemaInterceptor; 19 | import com.github.jrcodeza.schema.generator.interceptors.examples.OpenApiExampleResolver; 20 | 21 | import org.apache.commons.io.IOUtils; 22 | import org.json.JSONException; 23 | import org.junit.Test; 24 | import org.skyscreamer.jsonassert.JSONAssert; 25 | 26 | import io.swagger.v3.oas.models.OpenAPI; 27 | import io.swagger.v3.oas.models.info.Info; 28 | 29 | import static java.util.Collections.singletonList; 30 | 31 | public class OpenAPIGeneratorTest { 32 | 33 | private static final TestOperationInterceptor operationInterceptor = new TestOperationInterceptor(); 34 | private static final TestOperationParameterInterceptor operationParameterInterceptor = new TestOperationParameterInterceptor(); 35 | private static final TestRequestBodyInterceptor requestBodyInterceptor = new TestRequestBodyInterceptor(); 36 | private static final TestSchemaFieldInterceptor schemaFieldInterceptor = new TestSchemaFieldInterceptor(); 37 | private static final TestSchemaInterceptor schemaInterceptor = new TestSchemaInterceptor(); 38 | 39 | @Test 40 | public void generateStandardScenario() { 41 | OpenAPI openAPI = createTestGenerator().generate(); 42 | assertOpenApiResult(openAPI, "expected_standard_openapi.json"); 43 | } 44 | 45 | @Test 46 | public void generateExampleScenario() { 47 | OpenAPI openAPI = createTestGenerator().generate( 48 | OpenApiGeneratorConfigBuilder.defaultConfig() 49 | .withGenerateExamples(true) 50 | .withOpenApiExampleResolver(createExampleResolver()) 51 | .build() 52 | ); 53 | assertOpenApiResult(openAPI, "expected_example_openapi.json"); 54 | } 55 | 56 | @Test 57 | public void generateFilteredScenario() { 58 | OpenAPIGenerator openAPIGenerator = createTestGenerator(); 59 | openAPIGenerator.setOperationFilter(new TestOperationFilter()); 60 | openAPIGenerator.setOperationParameterFilter(new TestOperationParameterFilter()); 61 | openAPIGenerator.setSchemaFieldFilter(new TestSchemaFieldFilter()); 62 | 63 | OpenAPI openAPI = openAPIGenerator.generate(); 64 | assertOpenApiResult(openAPI, "expected_filtered_openapi.json"); 65 | } 66 | 67 | private void assertOpenApiResult(OpenAPI openAPI, String pathToExpectedFile) { 68 | ObjectMapper objectMapper = new ObjectMapper(); 69 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 70 | try { 71 | String generated = objectMapper.writeValueAsString(openAPI); 72 | JSONAssert.assertEquals(getResourceFileAsString(pathToExpectedFile), generated, true); 73 | } catch (JsonProcessingException | JSONException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | 78 | private OpenApiExampleResolver createExampleResolver() { 79 | return exampleKey -> "TestExampleResolvedWithKey=" + exampleKey; 80 | } 81 | 82 | private OpenAPIGenerator createTestGenerator() { 83 | OpenAPIGenerator openAPIGenerator = new OpenAPIGenerator( 84 | singletonList("com.github.jrcodeza.schema.generator.domain.*"), 85 | singletonList("com.github.jrcodeza.schema.generator.controller.*"), 86 | createTestInfo() 87 | ); 88 | openAPIGenerator.addOperationInterceptor(operationInterceptor); 89 | openAPIGenerator.addOperationParameterInterceptor(operationParameterInterceptor); 90 | openAPIGenerator.addRequestBodyInterceptor(requestBodyInterceptor); 91 | openAPIGenerator.addSchemaFieldInterceptor(schemaFieldInterceptor); 92 | openAPIGenerator.addSchemaInterceptor(schemaInterceptor); 93 | openAPIGenerator.addGlobalHeader("Test-Global-Header", "Some desc", false); 94 | return openAPIGenerator; 95 | } 96 | 97 | private Info createTestInfo() { 98 | Info info = new Info(); 99 | info.setTitle("Test API"); 100 | info.setDescription("Test description"); 101 | info.setVersion("1.0.0"); 102 | return info; 103 | } 104 | 105 | private String getResourceFileAsString(String pathToExpectedFile) { 106 | ClassLoader classLoader = getClass().getClassLoader(); 107 | try (InputStream inputStream = classLoader.getResourceAsStream(pathToExpectedFile)) { 108 | return IOUtils.toString(inputStream, StandardCharsets.UTF_8); 109 | } catch (IOException e) { 110 | e.printStackTrace(); 111 | } 112 | return null; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/base/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.base; 2 | 3 | public class Entity { 4 | 5 | private String id; 6 | 7 | public String getId() { 8 | return id; 9 | } 10 | 11 | public void setId(String id) { 12 | this.id = id; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/controller/CarController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.github.jrcodeza.schema.generator.domain.Car; 6 | 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestHeader; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.ResponseStatus; 17 | import org.springframework.web.bind.annotation.RestController; 18 | import org.springframework.web.multipart.MultipartFile; 19 | 20 | @RestController 21 | @RequestMapping("/cars") 22 | public class CarController { 23 | 24 | @PostMapping 25 | public Car createCar(@RequestHeader("source") String source, @RequestBody Car car) { 26 | return null; 27 | } 28 | 29 | @GetMapping 30 | public List getCars(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 31 | return null; 32 | } 33 | 34 | @GetMapping("/{carId}") 35 | public Car getCar(@PathVariable("carId") String carId) { 36 | return null; 37 | } 38 | 39 | @PostMapping("/{carId}/photos") 40 | @ResponseStatus(HttpStatus.ACCEPTED) 41 | public void uploadCarPhoto(@PathVariable String carId, @RequestBody MultipartFile multipartFile) { 42 | // do nothing 43 | } 44 | 45 | @RequestMapping(path = "/{carId}/documents", consumes = "multipart/form-data", produces = "application/json", method = RequestMethod.POST) 46 | public Car uploadCarDocuments(@RequestHeader("source") String source, 47 | @PathVariable("carId") String carId, 48 | @RequestParam(name = "documentFile") MultipartFile documentFile, 49 | @RequestParam(name = "type") String type) { 50 | return null; 51 | } 52 | 53 | public void noOperationMethod() { 54 | // do nothing 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/controller/ControllerToBeIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.controller; 2 | 3 | import com.github.jrcodeza.OpenApiIgnore; 4 | import com.github.jrcodeza.schema.generator.domain.dummy.ValidationDummy; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("/toBeIgnored") 12 | @OpenApiIgnore 13 | public class ControllerToBeIgnored { 14 | 15 | @GetMapping 16 | public ValidationDummy getIgnoredController(@RequestParam String someParam) { 17 | return null; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/controller/DummyController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.github.jrcodeza.Header; 6 | import com.github.jrcodeza.OpenApiExample; 7 | import com.github.jrcodeza.OpenApiExamples; 8 | import com.github.jrcodeza.OpenApiIgnore; 9 | import com.github.jrcodeza.Response; 10 | import com.github.jrcodeza.Responses; 11 | import com.github.jrcodeza.schema.generator.domain.CarType; 12 | import com.github.jrcodeza.schema.generator.domain.OptionsClass; 13 | import com.github.jrcodeza.schema.generator.domain.dummy.ValidationDummy; 14 | 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.validation.annotation.Validated; 18 | import org.springframework.web.bind.annotation.DeleteMapping; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | import org.springframework.web.bind.annotation.PatchMapping; 21 | import org.springframework.web.bind.annotation.PathVariable; 22 | import org.springframework.web.bind.annotation.PostMapping; 23 | import org.springframework.web.bind.annotation.PutMapping; 24 | import org.springframework.web.bind.annotation.RequestBody; 25 | import org.springframework.web.bind.annotation.RequestHeader; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestMethod; 28 | import org.springframework.web.bind.annotation.RequestParam; 29 | import org.springframework.web.bind.annotation.ResponseStatus; 30 | import org.springframework.web.bind.annotation.RestController; 31 | import org.springframework.web.multipart.MultipartFile; 32 | 33 | @RestController 34 | @RequestMapping("/dummy") 35 | public class DummyController { 36 | 37 | @RequestMapping(path = "/{id}", method = RequestMethod.HEAD) 38 | public ResponseEntity isPresent(@PathVariable("id") Integer id) { 39 | return null; 40 | } 41 | 42 | @RequestMapping(path = "/{id}", method = RequestMethod.OPTIONS) 43 | public ResponseEntity getOptions(@PathVariable("id") Integer id) { 44 | return null; 45 | } 46 | 47 | @PostMapping 48 | @ResponseStatus(HttpStatus.CREATED) 49 | public ValidationDummy create(@RequestBody @Validated @OpenApiExample("{\"bodyExample\":\"value\"}") ValidationDummy validationDummy) { 50 | return null; 51 | } 52 | 53 | @PutMapping("/{id}") 54 | @Responses({ 55 | @Response(responseCode = 201, description = "Created", responseBody = ValidationDummy.class, 56 | headers = @Header(name = "SomeHeader", description = "TestHeader")), 57 | @Response(responseCode = 200, description = "Replaced", responseBody = ValidationDummy.class) 58 | }) 59 | public ValidationDummy createOrReplace(@PathVariable(required = false) Integer id, @RequestBody @Validated ValidationDummy validationDummy) { 60 | return null; 61 | } 62 | 63 | @PatchMapping(path = "/{id}") 64 | public ValidationDummy patch(@PathVariable Integer id, @RequestBody @Validated ValidationDummy validationDummy) { 65 | return null; 66 | } 67 | 68 | @GetMapping("/{id}/subpath/{anotherId}") 69 | public ValidationDummy getTwoPathVariables(@PathVariable Integer id, @PathVariable("anotherId") Integer another) { 70 | return null; 71 | } 72 | 73 | @GetMapping("/{id}/subpath/") 74 | public ValidationDummy subpath( 75 | @RequestHeader String headerA, 76 | @RequestHeader("headerB") String headerB, 77 | @PathVariable Integer id, 78 | @RequestParam("requestParamA") @OpenApiExample(name = "customValidationDummy", description = "CustomDescription", key = "CUSTOM_EXAMPLE_KEY_1") 79 | String requestParamA, 80 | @RequestParam String requestParamB, 81 | @RequestParam(name = "requestParamC", required = false) String requestParamC 82 | ) { 83 | return null; 84 | } 85 | 86 | @GetMapping("/onlyRequestParams") 87 | public ValidationDummy onlyRequestParams( 88 | @RequestParam("requestParamA") String requestParamA, 89 | @RequestParam @OpenApiExamples(value = { 90 | @OpenApiExample(name = "example_1", description = "example_1_description", value = "moreExamples_1"), 91 | @OpenApiExample(name = "example_2", description = "example_2_description", value = "moreExamples_2") 92 | }) 93 | String requestParamB, 94 | @RequestParam(name = "requestParamC", required = false) String requestParamC 95 | ) { 96 | return null; 97 | } 98 | 99 | @PostMapping("/{id}/subpath/") 100 | public ValidationDummy complexPost( 101 | @RequestHeader String headerA, 102 | @RequestHeader("headerB") String headerB, 103 | @PathVariable Integer id, 104 | @RequestParam("requestParamA") String requestParamA, 105 | @RequestParam String requestParamB, 106 | @RequestParam(name = "requestParamC", required = false) String requestParamC, 107 | @RequestBody ValidationDummy testBody 108 | ) { 109 | return null; 110 | } 111 | 112 | @DeleteMapping(path = "/{id}") 113 | public ValidationDummy delete(@PathVariable Integer id) { 114 | return null; 115 | } 116 | 117 | @GetMapping(path = "/requestParamList") 118 | public ValidationDummy requestParamList(@RequestParam List validationDummies) { 119 | return null; 120 | } 121 | 122 | @PostMapping(path = "/requestBodyList") 123 | public ValidationDummy requestBodyList(@RequestBody List validationDummies, 124 | @RequestParam @OpenApiIgnore String toBeIgnored) { 125 | return null; 126 | } 127 | 128 | @PostMapping(path = "/toBeIgnored") 129 | @OpenApiIgnore 130 | public ValidationDummy methodToBeIgnored(@RequestBody List validationDummies) { 131 | return null; 132 | } 133 | 134 | @PatchMapping(path = "/bodyToBeIgnored/{variable}") 135 | @OpenApiIgnore 136 | public ValidationDummy methodToBeIgnored( 137 | @PathVariable String variable, 138 | @RequestBody @OpenApiIgnore List validationDummies) { 139 | return null; 140 | } 141 | 142 | @GetMapping(path = "/fileWithoutResponseAnnotation") 143 | public MultipartFile getFileWithoutResponseAnnotation() { 144 | return null; 145 | } 146 | 147 | @GetMapping(path = "/fileWithResponseAnnotation", produces = "application/pdf") 148 | @Response(responseCode = 200, description = "desc", responseBody = MultipartFile.class) 149 | public MultipartFile getFileWithResponseAnnotation() { 150 | return null; 151 | } 152 | 153 | @GetMapping(path = "/enumAsParam") 154 | public ValidationDummy enumAsParam(@RequestParam CarType carType) { 155 | return null; 156 | } 157 | 158 | @GetMapping(path = "/xmlAsString", produces = "application/xml") 159 | public String xmlAsString() { 160 | return null; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/controller/ListController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.github.jrcodeza.schema.generator.domain.Car; 6 | 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | @RequestMapping("/lists") 15 | public class ListController { 16 | 17 | @GetMapping("with-response-entity") 18 | public ResponseEntity> getCarsWithResponseEntity(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 19 | return null; 20 | } 21 | 22 | @GetMapping("list-without-generics") 23 | public ResponseEntity getCarsWithListWithoutGenerics(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Car.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import javax.validation.constraints.Max; 4 | import javax.validation.constraints.Min; 5 | import javax.validation.constraints.Size; 6 | 7 | import com.github.jrcodeza.OpenApiExample; 8 | import com.github.jrcodeza.OpenApiExamples; 9 | 10 | @OpenApiExamples(value = { 11 | @OpenApiExample(name = "CarExampleName_1", value = "carExampleValue_1"), 12 | @OpenApiExample(name = "CarExampleName_2", value = "carExampleValue_2") 13 | }) 14 | public class Car extends Product { 15 | 16 | public static final String SHOULD_BE_IGNORED_BECAUSE_STATIC = "TestValue"; 17 | 18 | @Min(0) 19 | @Max(1000) 20 | private Integer torque; 21 | 22 | private Integer maxSpeed; 23 | 24 | @Size(min = 2, max = 30) 25 | @OpenApiExample(value = "field example") 26 | private String model; 27 | 28 | private CarType carType; 29 | 30 | public Integer getTorque() { 31 | return torque; 32 | } 33 | 34 | public void setTorque(Integer torque) { 35 | this.torque = torque; 36 | } 37 | 38 | public Integer getMaxSpeed() { 39 | return maxSpeed; 40 | } 41 | 42 | public void setMaxSpeed(Integer maxSpeed) { 43 | this.maxSpeed = maxSpeed; 44 | } 45 | 46 | public String getModel() { 47 | return model; 48 | } 49 | 50 | public void setModel(String model) { 51 | this.model = model; 52 | } 53 | 54 | public CarType getCarType() { 55 | return carType; 56 | } 57 | 58 | public void setCarType(CarType carType) { 59 | this.carType = carType; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/CarType.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | public enum CarType { 4 | 5 | PERSONAL, 6 | TRUCK, 7 | VAN 8 | 9 | } 10 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import com.github.jrcodeza.OpenApiIgnore; 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | 7 | public class Customer extends Entity { 8 | 9 | private boolean vip; 10 | 11 | @Schema(description = "Testing description") 12 | private Product topCustomerProduct; 13 | 14 | @OpenApiIgnore 15 | private String toBeIgnored; 16 | 17 | public boolean isVip() { 18 | return vip; 19 | } 20 | 21 | public void setVip(boolean vip) { 22 | this.vip = vip; 23 | } 24 | 25 | public Product getTopCustomerProduct() { 26 | return topCustomerProduct; 27 | } 28 | 29 | public void setTopCustomerProduct(Product topCustomerProduct) { 30 | this.topCustomerProduct = topCustomerProduct; 31 | } 32 | 33 | public String getToBeIgnored() { 34 | return toBeIgnored; 35 | } 36 | 37 | public void setToBeIgnored(String toBeIgnored) { 38 | this.toBeIgnored = toBeIgnored; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/CustomerInventory.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | public class CustomerInventory { 4 | 5 | private Customer[] customers; 6 | 7 | private Object object; 8 | 9 | public Customer[] getCustomers() { 10 | return customers; 11 | } 12 | 13 | public void setCustomers(Customer[] customers) { 14 | this.customers = customers; 15 | } 16 | 17 | public Object getObject() { 18 | return object; 19 | } 20 | 21 | public void setObject(Object object) { 22 | this.object = object; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class Entity { 6 | 7 | @NotNull 8 | private String id; 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Laptop.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class Laptop extends Product { 6 | 7 | @NotNull 8 | private String model; 9 | 10 | @NotNull 11 | private Boolean hasWifi; 12 | 13 | public String getModel() { 14 | return model; 15 | } 16 | 17 | public void setModel(String model) { 18 | this.model = model; 19 | } 20 | 21 | public Boolean getHasWifi() { 22 | return hasWifi; 23 | } 24 | 25 | public void setHasWifi(Boolean hasWifi) { 26 | this.hasWifi = hasWifi; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/OptionsClass.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | public class OptionsClass { 4 | 5 | private String options; 6 | 7 | public String getOptions() { 8 | return options; 9 | } 10 | 11 | public void setOptions(String options) { 12 | this.options = options; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | 6 | public class Order extends Entity { 7 | 8 | private LocalDateTime dateTime; 9 | private Customer customer; 10 | private List products; 11 | 12 | public Customer getCustomer() { 13 | return customer; 14 | } 15 | 16 | public void setCustomer(Customer customer) { 17 | this.customer = customer; 18 | } 19 | 20 | public List getProducts() { 21 | return products; 22 | } 23 | 24 | public void setProducts(List products) { 25 | this.products = products; 26 | } 27 | 28 | public LocalDateTime getDateTime() { 29 | return dateTime; 30 | } 31 | 32 | public void setDateTime(LocalDateTime dateTime) { 33 | this.dateTime = dateTime; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | 6 | import javax.validation.constraints.NotNull; 7 | import java.math.BigDecimal; 8 | 9 | @JsonTypeInfo( 10 | use = JsonTypeInfo.Id.NAME, 11 | property = "type") 12 | @JsonSubTypes({ 13 | @JsonSubTypes.Type(value = Car.class, name = "car"), 14 | @JsonSubTypes.Type(value = Van.class, name = "van"), 15 | @JsonSubTypes.Type(value = Laptop.class, name = "laptop") 16 | }) 17 | public class Product extends Entity { 18 | 19 | @NotNull 20 | private BigDecimal price; 21 | private int amount; 22 | 23 | public BigDecimal getPrice() { 24 | return price; 25 | } 26 | 27 | public void setPrice(BigDecimal price) { 28 | this.price = price; 29 | } 30 | 31 | public int getAmount() { 32 | return amount; 33 | } 34 | 35 | public void setAmount(int amount) { 36 | this.amount = amount; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/Van.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain; 2 | 3 | public class Van extends Car { 4 | 5 | private Integer maxLoad; 6 | 7 | public Integer getMaxLoad() { 8 | return maxLoad; 9 | } 10 | 11 | public void setMaxLoad(Integer maxLoad) { 12 | this.maxLoad = maxLoad; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/dummy/ArrayDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain.dummy; 2 | 3 | import com.github.jrcodeza.schema.generator.domain.Car; 4 | import com.github.jrcodeza.schema.generator.domain.Product; 5 | 6 | public class ArrayDummy { 7 | 8 | private Integer[] integers; 9 | private Product[] products; 10 | private Car[] cars; 11 | private int[] primitiveIntegers; 12 | 13 | public Integer[] getIntegers() { 14 | return integers; 15 | } 16 | 17 | public void setIntegers(Integer[] integers) { 18 | this.integers = integers; 19 | } 20 | 21 | public Product[] getProducts() { 22 | return products; 23 | } 24 | 25 | public void setProducts(Product[] products) { 26 | this.products = products; 27 | } 28 | 29 | public Car[] getCars() { 30 | return cars; 31 | } 32 | 33 | public void setCars(Car[] cars) { 34 | this.cars = cars; 35 | } 36 | 37 | public int[] getPrimitiveIntegers() { 38 | return primitiveIntegers; 39 | } 40 | 41 | public void setPrimitiveIntegers(int[] primitiveIntegers) { 42 | this.primitiveIntegers = primitiveIntegers; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/dummy/ListDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain.dummy; 2 | 3 | import com.github.jrcodeza.schema.generator.domain.Car; 4 | import com.github.jrcodeza.schema.generator.domain.CarType; 5 | import com.github.jrcodeza.schema.generator.domain.Product; 6 | 7 | import javax.validation.constraints.Size; 8 | import java.util.List; 9 | 10 | public class ListDummy { 11 | 12 | @Size(min = 2, max = 6) 13 | private List products; 14 | private List integers; 15 | private List cars; 16 | private List enums; 17 | 18 | public List getProducts() { 19 | return products; 20 | } 21 | 22 | public void setProducts(List products) { 23 | this.products = products; 24 | } 25 | 26 | public List getIntegers() { 27 | return integers; 28 | } 29 | 30 | public void setIntegers(List integers) { 31 | this.integers = integers; 32 | } 33 | 34 | public List getCars() { 35 | return cars; 36 | } 37 | 38 | public void setCars(List cars) { 39 | this.cars = cars; 40 | } 41 | 42 | public List getEnums() { 43 | return enums; 44 | } 45 | 46 | public void setEnums(List enums) { 47 | this.enums = enums; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/domain/dummy/ValidationDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.domain.dummy; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import javax.validation.constraints.DecimalMax; 6 | import javax.validation.constraints.DecimalMin; 7 | import javax.validation.constraints.Max; 8 | import javax.validation.constraints.Min; 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Pattern; 11 | import javax.validation.constraints.Size; 12 | 13 | import io.swagger.v3.oas.annotations.media.Schema; 14 | 15 | public class ValidationDummy { 16 | 17 | @DecimalMin("1.05") 18 | @DecimalMax("2.5") 19 | private BigDecimal decimalRange; 20 | 21 | @Min(3) 22 | @Max(7) 23 | private Integer minMax; 24 | 25 | @NotNull 26 | private String notNull; 27 | 28 | @Pattern(regexp = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b") 29 | private String regex; 30 | 31 | @Size(min = 2, max = 10) 32 | private String stringSize; 33 | 34 | @Size(max = 10) 35 | private String stringSizeOnlyMax; 36 | 37 | @Schema(deprecated = true) 38 | private String deprecated; 39 | 40 | @Deprecated 41 | private String javaDeprecated; 42 | 43 | @Schema(accessMode = Schema.AccessMode.WRITE_ONLY) 44 | private String writeOnly; 45 | 46 | @Schema(accessMode = Schema.AccessMode.READ_ONLY) 47 | private String readOnly; 48 | 49 | public BigDecimal getDecimalRange() { 50 | return decimalRange; 51 | } 52 | 53 | public void setDecimalRange(BigDecimal decimalRange) { 54 | this.decimalRange = decimalRange; 55 | } 56 | 57 | public Integer getMinMax() { 58 | return minMax; 59 | } 60 | 61 | public void setMinMax(Integer minMax) { 62 | this.minMax = minMax; 63 | } 64 | 65 | public String getNotNull() { 66 | return notNull; 67 | } 68 | 69 | public void setNotNull(String notNull) { 70 | this.notNull = notNull; 71 | } 72 | 73 | public String getRegex() { 74 | return regex; 75 | } 76 | 77 | public void setRegex(String regex) { 78 | this.regex = regex; 79 | } 80 | 81 | public String getStringSize() { 82 | return stringSize; 83 | } 84 | 85 | public void setStringSize(String stringSize) { 86 | this.stringSize = stringSize; 87 | } 88 | 89 | public String getStringSizeOnlyMax() { 90 | return stringSizeOnlyMax; 91 | } 92 | 93 | public void setStringSizeOnlyMax(String stringSizeOnlyMax) { 94 | this.stringSizeOnlyMax = stringSizeOnlyMax; 95 | } 96 | 97 | public String getDeprecated() { 98 | return deprecated; 99 | } 100 | 101 | public void setDeprecated(String deprecated) { 102 | this.deprecated = deprecated; 103 | } 104 | 105 | public String getJavaDeprecated() { 106 | return javaDeprecated; 107 | } 108 | 109 | public void setJavaDeprecated(String javaDeprecated) { 110 | this.javaDeprecated = javaDeprecated; 111 | } 112 | 113 | public String getWriteOnly() { 114 | return writeOnly; 115 | } 116 | 117 | public void setWriteOnly(String writeOnly) { 118 | this.writeOnly = writeOnly; 119 | } 120 | 121 | public String getReadOnly() { 122 | return readOnly; 123 | } 124 | 125 | public void setReadOnly(String readOnly) { 126 | this.readOnly = readOnly; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/filters/TestOperationFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public class TestOperationFilter implements OperationFilter { 6 | @Override 7 | public boolean shouldIgnore(Method method) { 8 | return method.getName().equals("uploadCarDocuments"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/filters/TestOperationParameterFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | public class TestOperationParameterFilter implements OperationParameterFilter { 7 | @Override 8 | public boolean shouldIgnore(Method method, Parameter parameter, String parameterName) { 9 | return parameterName.equals("requestParamC"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/filters/TestSchemaFieldFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.filters; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class TestSchemaFieldFilter implements SchemaFieldFilter { 6 | @Override 7 | public boolean shouldIgnore(Class clazz, Field field) { 8 | return field.getName().equals("maxSpeed"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/interceptors/TestOperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import com.github.jrcodeza.schema.generator.interceptors.OperationInterceptor; 6 | 7 | import io.swagger.v3.oas.models.Operation; 8 | 9 | public class TestOperationInterceptor implements OperationInterceptor { 10 | 11 | @Override 12 | public void intercept(Method method, Operation transformedOperation) { 13 | transformedOperation.setSummary("Interceptor summary"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/interceptors/TestOperationParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | import com.github.jrcodeza.schema.generator.interceptors.OperationParameterInterceptor; 7 | 8 | import static com.github.jrcodeza.schema.generator.util.TestUtils.emptyIfNull; 9 | 10 | 11 | public class TestOperationParameterInterceptor implements OperationParameterInterceptor { 12 | 13 | @Override 14 | public void intercept(Method method, Parameter parameter, String parameterName, 15 | io.swagger.v3.oas.models.parameters.Parameter transformedParameter) { 16 | transformedParameter.setDescription(emptyIfNull(transformedParameter.getDescription()) + ". Interceptor OperationParameter test"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/interceptors/TestRequestBodyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | import com.github.jrcodeza.schema.generator.interceptors.RequestBodyInterceptor; 7 | 8 | import io.swagger.v3.oas.models.parameters.RequestBody; 9 | 10 | import static com.github.jrcodeza.schema.generator.util.TestUtils.emptyIfNull; 11 | 12 | public class TestRequestBodyInterceptor implements RequestBodyInterceptor { 13 | 14 | @Override 15 | public void intercept(Method method, Parameter parameter, String parameterName, RequestBody transformedRequestBody) { 16 | transformedRequestBody.setDescription(emptyIfNull(transformedRequestBody.getDescription()) + ". Test requestBody interceptor"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/interceptors/TestSchemaFieldInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import com.github.jrcodeza.schema.generator.interceptors.SchemaFieldInterceptor; 6 | 7 | import io.swagger.v3.oas.models.media.Schema; 8 | 9 | import static com.github.jrcodeza.schema.generator.util.TestUtils.emptyIfNull; 10 | 11 | public class TestSchemaFieldInterceptor implements SchemaFieldInterceptor { 12 | 13 | @Override 14 | public void intercept(Class clazz, Field field, Schema transformedFieldSchema) { 15 | transformedFieldSchema.setDescription(emptyIfNull(transformedFieldSchema.getDescription()) + ". Test schemaField interceptor"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/interceptors/TestSchemaInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.interceptors; 2 | 3 | import com.github.jrcodeza.schema.generator.interceptors.SchemaInterceptor; 4 | 5 | import io.swagger.v3.oas.models.media.Schema; 6 | 7 | import static com.github.jrcodeza.schema.generator.util.TestUtils.emptyIfNull; 8 | 9 | public class TestSchemaInterceptor implements SchemaInterceptor { 10 | 11 | @Override 12 | public void intercept(Class clazz, Schema transformedSchema) { 13 | transformedSchema.setDescription(emptyIfNull(transformedSchema.getDescription()) + ". Test schema interceptors"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-generator/src/test/java/com/github/jrcodeza/schema/generator/util/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.generator.util; 2 | 3 | public final class TestUtils { 4 | 5 | private TestUtils() { 6 | throw new AssertionError(); 7 | } 8 | 9 | public static String emptyIfNull(String string) { 10 | return string == null ? "" : string; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /schema-v2-generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.jrcodeza 6 | spring-openapi 7 | 1.4.11-SNAPSHOT 8 | 9 | 10 | spring-openapi-schema-v2-generator 11 | 12 | 1.4.11-SNAPSHOT 13 | jar 14 | 15 | Spring Open API - Schema v2 generator 16 | 17 | 18 | 19 | com.github.jrcodeza 20 | spring-openapi-annotations 21 | ${project.version} 22 | 23 | 24 | 25 | org.springframework 26 | spring-core 27 | 28 | 29 | org.springframework 30 | spring-context 31 | 32 | 33 | org.springframework 34 | spring-web 35 | 36 | 37 | 38 | io.swagger 39 | swagger-annotations 40 | 41 | 42 | io.swagger 43 | swagger-models 44 | 45 | 46 | 47 | com.fasterxml.jackson.core 48 | jackson-databind 49 | 50 | 51 | com.fasterxml.jackson.core 52 | jackson-annotations 53 | 54 | 55 | 56 | javax.validation 57 | validation-api 58 | 59 | 60 | 61 | org.apache.logging.log4j 62 | log4j-api 63 | 64 | 65 | org.apache.logging.log4j 66 | log4j-core 67 | 68 | 69 | org.apache.logging.log4j 70 | log4j-slf4j-impl 71 | 72 | 73 | 74 | org.apache.commons 75 | commons-lang3 76 | 77 | 78 | 79 | com.jayway.jsonpath 80 | json-path 81 | 82 | 83 | 84 | org.skyscreamer 85 | jsonassert 86 | 87 | 88 | commons-io 89 | commons-io 90 | 91 | 92 | junit 93 | junit 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-compiler-plugin 102 | 103 | 8 104 | 8 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/ComponentSchemaTransformer.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator; 2 | 3 | import io.swagger.models.ComposedModel; 4 | import io.swagger.models.Model; 5 | import io.swagger.models.ModelImpl; 6 | import io.swagger.models.RefModel; 7 | import io.swagger.models.properties.ObjectProperty; 8 | import io.swagger.models.properties.Property; 9 | import io.swagger.models.properties.RefProperty; 10 | import io.swagger.models.properties.UntypedProperty; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.util.ReflectionUtils; 13 | 14 | import javax.validation.constraints.NotNull; 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.ParameterizedType; 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Optional; 23 | import java.util.stream.Stream; 24 | 25 | import com.github.jrcodeza.schema.v2.generator.interceptors.SchemaFieldInterceptor; 26 | import com.github.jrcodeza.schema.v2.generator.model.GenerationContext; 27 | import com.github.jrcodeza.schema.v2.generator.model.InheritanceInfo; 28 | import com.github.jrcodeza.schema.v2.generator.util.CommonConstants; 29 | import com.github.jrcodeza.schema.v2.generator.util.GeneratorUtils; 30 | 31 | public class ComponentSchemaTransformer extends OpenApiTransformer { 32 | 33 | private final List schemaFieldInterceptors; 34 | 35 | public ComponentSchemaTransformer(List schemaFieldInterceptors) { 36 | this.schemaFieldInterceptors = schemaFieldInterceptors; 37 | } 38 | 39 | public Model transformSimpleSchema(Class clazz, GenerationContext generationContext) { 40 | if (clazz.isEnum()) { 41 | return createEnumModel(clazz.getEnumConstants()); 42 | } 43 | 44 | ModelImpl schema = new ModelImpl(); 45 | schema.setType("object"); 46 | getClassProperties(clazz, generationContext).forEach(schema::addProperty); 47 | 48 | if (generationContext.getInheritanceMap().containsKey(clazz.getName())) { 49 | InheritanceInfo inheritanceInfo = generationContext.getInheritanceMap().get(clazz.getName()); 50 | schema.setDiscriminator(inheritanceInfo.getDiscriminatorFieldName()); 51 | } 52 | if (clazz.getSuperclass() != null) { 53 | return traverseAndAddProperties(schema, generationContext, clazz.getSuperclass(), clazz); 54 | } 55 | return schema; 56 | } 57 | 58 | private Model traverseAndAddProperties(ModelImpl schema, GenerationContext generationContext, Class superclass, Class clazz) { 59 | if (!isInPackagesToBeScanned(superclass, generationContext)) { 60 | // adding properties from parent classes is present due to swagger ui bug, after using different ui 61 | // this becomes relevant only for third party packages 62 | getClassProperties(superclass, generationContext).forEach(schema::addProperty); 63 | if (superclass.getSuperclass() != null && !"java.lang".equals(superclass.getSuperclass().getPackage().getName())) { 64 | return traverseAndAddProperties(schema, generationContext, superclass.getSuperclass(), superclass); 65 | } 66 | return schema; 67 | } else { 68 | RefModel parentClassSchema = new RefModel(); 69 | parentClassSchema.set$ref(CommonConstants.COMPONENT_REF_PREFIX + superclass.getSimpleName()); 70 | 71 | ComposedModel composedSchema = new ComposedModel(); 72 | composedSchema.setAllOf(Arrays.asList(parentClassSchema, schema)); 73 | InheritanceInfo inheritanceInfo = generationContext.getInheritanceMap().get(superclass.getName()); 74 | if (inheritanceInfo != null) { 75 | String discriminatorName = inheritanceInfo.getDiscriminatorClassMap().get(clazz.getName()); 76 | composedSchema.setVendorExtension("x-discriminator-value", discriminatorName); 77 | composedSchema.setVendorExtension("x-ms-discriminator-value", discriminatorName); 78 | } 79 | return composedSchema; 80 | } 81 | } 82 | 83 | private Map getClassProperties(Class clazz, GenerationContext generationContext) { 84 | Map classPropertyMap = new HashMap<>(); 85 | ReflectionUtils.doWithLocalFields(clazz, 86 | field -> getFieldSchema(field, generationContext).ifPresent(schema -> { 87 | schemaFieldInterceptors 88 | .forEach(modelClassFieldInterceptor -> modelClassFieldInterceptor.intercept(clazz, field, schema)); 89 | classPropertyMap.put(field.getName(), schema); 90 | }) 91 | ); 92 | return classPropertyMap; 93 | } 94 | 95 | private Optional getFieldSchema(Field field, GenerationContext generationContext) { 96 | if (GeneratorUtils.shouldBeIgnored(field)) { 97 | return Optional.empty(); 98 | } 99 | 100 | Class typeSignature = field.getType(); 101 | Annotation[] annotations = field.getAnnotations(); 102 | 103 | Optional optionalProperty; 104 | if (typeSignature.isPrimitive()) { 105 | optionalProperty = createBaseTypeProperty(field, annotations); 106 | } else if (typeSignature.isArray()) { 107 | optionalProperty = createArrayTypeProperty(generationContext, typeSignature, annotations); 108 | } else if (StringUtils.equalsIgnoreCase(typeSignature.getName(), "java.lang.Object")) { 109 | ObjectProperty objectSchema = new ObjectProperty(); 110 | objectSchema.setName(field.getName()); 111 | return Optional.of(objectSchema); 112 | } else if (typeSignature.isAssignableFrom(List.class)) { 113 | if (field.getGenericType() instanceof ParameterizedType) { 114 | Class listGenericParameter = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; 115 | optionalProperty = Optional.of(parseArraySignature(listGenericParameter, generationContext, annotations)); 116 | } else { 117 | optionalProperty = Optional.empty(); 118 | } 119 | } else { 120 | optionalProperty = createClassRefProperty(generationContext, typeSignature, annotations); 121 | } 122 | optionalProperty.ifPresent(property -> property.setRequired(isRequired(annotations))); 123 | return optionalProperty; 124 | } 125 | 126 | private Optional createClassRefProperty(GenerationContext generationContext, Class typeClass, Annotation[] annotations) { 127 | Property schema = createRefTypeProperty(typeClass, annotations, generationContext); 128 | return Optional.ofNullable(schema); 129 | } 130 | 131 | private Optional createArrayTypeProperty(GenerationContext generationContext, Class typeSignature, Annotation[] annotations) { 132 | Class arrayComponentType = typeSignature.getComponentType(); 133 | Property schema = parseArraySignature(arrayComponentType, generationContext, annotations); 134 | return Optional.ofNullable(schema); 135 | } 136 | 137 | private Optional createBaseTypeProperty(Field field, Annotation[] annotations) { 138 | Property schema = createBaseTypeProperty(field.getType(), annotations); 139 | schema.setRequired(true); 140 | return Optional.ofNullable(schema); 141 | } 142 | 143 | private boolean isRequired(Annotation[] annotations) { 144 | return Stream.of(annotations).anyMatch(annotation -> annotation instanceof NotNull); 145 | } 146 | 147 | @Override 148 | protected Property createArrayProperty(Class typeSignature, GenerationContext generationContext, Annotation[] annotations) { 149 | return parseArraySignature(typeSignature, generationContext, annotations); 150 | } 151 | 152 | @Override 153 | protected Property createRefProperty(Class typeSignature, GenerationContext generationContext) { 154 | RefProperty schema = new RefProperty(); 155 | if (isInPackagesToBeScanned(typeSignature, generationContext)) { 156 | schema.set$ref(CommonConstants.COMPONENT_REF_PREFIX + typeSignature.getSimpleName()); 157 | return schema; 158 | } 159 | UntypedProperty fallBack = new UntypedProperty(); 160 | fallBack.setType("object"); 161 | return fallBack; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/OpenAPIV2Generator.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Collectors; 12 | 13 | import com.fasterxml.jackson.annotation.JsonInclude; 14 | import com.fasterxml.jackson.annotation.JsonSubTypes; 15 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.fasterxml.jackson.databind.ObjectMapper; 18 | import com.github.jrcodeza.OpenApiIgnore; 19 | import com.github.jrcodeza.schema.v2.generator.config.OpenApiV2GeneratorConfig; 20 | import com.github.jrcodeza.schema.v2.generator.config.builder.OpenApiV2GeneratorConfigBuilder; 21 | import com.github.jrcodeza.schema.v2.generator.interceptors.OperationInterceptor; 22 | import com.github.jrcodeza.schema.v2.generator.interceptors.OperationParameterInterceptor; 23 | import com.github.jrcodeza.schema.v2.generator.interceptors.RequestBodyInterceptor; 24 | import com.github.jrcodeza.schema.v2.generator.interceptors.SchemaFieldInterceptor; 25 | import com.github.jrcodeza.schema.v2.generator.interceptors.SchemaInterceptor; 26 | import com.github.jrcodeza.schema.v2.generator.model.GenerationContext; 27 | import com.github.jrcodeza.schema.v2.generator.model.Header; 28 | import com.github.jrcodeza.schema.v2.generator.model.InheritanceInfo; 29 | import com.jayway.jsonpath.DocumentContext; 30 | import com.jayway.jsonpath.JsonPath; 31 | 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import org.springframework.beans.factory.config.BeanDefinition; 35 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 36 | import org.springframework.core.env.Environment; 37 | import org.springframework.core.type.filter.AnnotationTypeFilter; 38 | import org.springframework.core.type.filter.RegexPatternTypeFilter; 39 | import org.springframework.web.bind.annotation.RestController; 40 | 41 | import io.swagger.models.Info; 42 | import io.swagger.models.Model; 43 | import io.swagger.models.Path; 44 | import io.swagger.models.Swagger; 45 | 46 | import static java.util.Arrays.asList; 47 | import static java.util.Collections.unmodifiableList; 48 | 49 | public class OpenAPIV2Generator { 50 | 51 | private static final String DEFAULT_DISCRIMINATOR_NAME = "type"; 52 | private static Logger logger = LoggerFactory.getLogger(OpenAPIV2Generator.class); 53 | private final ComponentSchemaTransformer componentSchemaTransformer; 54 | private final OperationsTransformer operationsTransformer; 55 | private final Info info; 56 | private final List schemaInterceptors; 57 | private final List schemaFieldInterceptors; 58 | private final List operationParameterInterceptors; 59 | private final List operationInterceptors; 60 | private final List requestBodyInterceptors; 61 | private final List
globalHeaders; 62 | private List modelPackages; 63 | private List controllerBasePackages; 64 | private Environment environment; 65 | 66 | public OpenAPIV2Generator(List modelPackages, List controllerBasePackages, Info info) { 67 | this(modelPackages, controllerBasePackages, info, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); 68 | } 69 | 70 | public OpenAPIV2Generator(List modelPackages, List controllerBasePackages, Info info, 71 | List schemaInterceptors, 72 | List schemaFieldInterceptors, 73 | List operationParameterInterceptors, 74 | List operationInterceptors, 75 | List requestBodyInterceptors) { 76 | this.modelPackages = modelPackages; 77 | this.controllerBasePackages = controllerBasePackages; 78 | componentSchemaTransformer = new ComponentSchemaTransformer(schemaFieldInterceptors); 79 | globalHeaders = new ArrayList<>(); 80 | 81 | GenerationContext operationsGenerationContext = new GenerationContext(null, removeRegexFormatFromPackages(modelPackages)); 82 | operationsTransformer = new OperationsTransformer( 83 | operationsGenerationContext, operationParameterInterceptors, operationInterceptors, requestBodyInterceptors, globalHeaders 84 | ); 85 | 86 | this.info = info; 87 | this.schemaInterceptors = schemaInterceptors; 88 | this.schemaFieldInterceptors = schemaFieldInterceptors; 89 | this.operationParameterInterceptors = operationParameterInterceptors; 90 | this.operationInterceptors = operationInterceptors; 91 | this.requestBodyInterceptors = requestBodyInterceptors; 92 | } 93 | 94 | public String generateJson() throws JsonProcessingException { 95 | return generateJson(OpenApiV2GeneratorConfigBuilder.empty().build()); 96 | } 97 | 98 | public String generateJson(OpenApiV2GeneratorConfig config) throws JsonProcessingException { 99 | Swagger openAPI = generate(config); 100 | ObjectMapper objectMapper = new ObjectMapper(); 101 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 102 | DocumentContext doc = JsonPath.parse(objectMapper.writeValueAsString(openAPI)); 103 | doc.delete("$..responseSchema"); 104 | doc.delete("$..originalRef"); 105 | return doc.jsonString(); 106 | } 107 | 108 | public Swagger generate(OpenApiV2GeneratorConfig config) { 109 | logger.info("Starting OpenAPI v2 generation"); 110 | environment = config.getEnvironment(); 111 | Swagger openAPI = new Swagger(); 112 | openAPI.setDefinitions(createDefinitions()); 113 | openAPI.setPaths(createPaths(config)); 114 | openAPI.setInfo(info); 115 | openAPI.setBasePath(config.getBasePath()); 116 | openAPI.setHost(config.getHost()); 117 | logger.info("OpenAPI v2 generation done!"); 118 | return openAPI; 119 | } 120 | 121 | public Swagger generate() { 122 | return generate(OpenApiV2GeneratorConfigBuilder.empty().build()); 123 | } 124 | 125 | public void addSchemaInterceptor(SchemaInterceptor schemaInterceptor) { 126 | schemaInterceptors.add(schemaInterceptor); 127 | } 128 | 129 | public void addSchemaFieldInterceptor(SchemaFieldInterceptor schemaFieldInterceptor) { 130 | schemaFieldInterceptors.add(schemaFieldInterceptor); 131 | } 132 | 133 | public void addOperationParameterInterceptor(OperationParameterInterceptor operationParameterInterceptor) { 134 | operationParameterInterceptors.add(operationParameterInterceptor); 135 | } 136 | 137 | public void addOperationInterceptor(OperationInterceptor operationInterceptor) { 138 | operationInterceptors.add(operationInterceptor); 139 | } 140 | 141 | public void addRequestBodyInterceptor(RequestBodyInterceptor requestBodyInterceptor) { 142 | requestBodyInterceptors.add(requestBodyInterceptor); 143 | } 144 | 145 | public void addGlobalHeader(String name, String description, boolean required) { 146 | globalHeaders.add(new Header(name, description, required)); 147 | } 148 | 149 | private Map createPaths(OpenApiV2GeneratorConfig config) { 150 | ClassPathScanningCandidateComponentProvider scanner = createClassPathScanningCandidateComponentProvider(); 151 | scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); 152 | 153 | List> controllerClasses = new ArrayList<>(); 154 | List packagesWithoutRegex = removeRegexFormatFromPackages(controllerBasePackages); 155 | for (String controllerPackage : packagesWithoutRegex) { 156 | logger.debug("Scanning controller package=[{}]", controllerPackage); 157 | for (BeanDefinition beanDefinition : scanner.findCandidateComponents(controllerPackage)) { 158 | logger.debug("Scanning controller class=[{}]", beanDefinition.getBeanClassName()); 159 | controllerClasses.add(getClass(beanDefinition)); 160 | } 161 | } 162 | return operationsTransformer.transformOperations(controllerClasses, config); 163 | } 164 | 165 | private ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider() { 166 | if (environment == null) { 167 | return new ClassPathScanningCandidateComponentProvider(false); 168 | } 169 | return new ClassPathScanningCandidateComponentProvider(false, environment); 170 | } 171 | 172 | private Map createDefinitions() { 173 | Map schemaMap = new HashMap<>(); 174 | ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); 175 | modelPackages.forEach(modelPackage -> scanner.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(modelPackage)))); 176 | 177 | List packagesWithoutRegex = removeRegexFormatFromPackages(modelPackages); 178 | Map inheritanceMap = new HashMap<>(); 179 | for (String modelPackage : packagesWithoutRegex) { 180 | logger.debug("Scanning model package=[{}]", modelPackage); 181 | for (BeanDefinition beanDefinition : scanner.findCandidateComponents(modelPackage)) { 182 | logger.debug("Scanning model class=[{}]", beanDefinition.getBeanClassName()); 183 | // populating inheritance info 184 | Class clazz = getClass(beanDefinition); 185 | if (inheritanceMap.containsKey(clazz.getName()) || clazz.getAnnotation(OpenApiIgnore.class) != null) { 186 | continue; 187 | } 188 | getInheritanceInfo(clazz).ifPresent(inheritanceInfo -> { 189 | logger.debug("Adding entry [{}] to inheritance map", clazz.getName()); 190 | inheritanceMap.put(clazz.getName(), inheritanceInfo); 191 | }); 192 | } 193 | for (BeanDefinition beanDefinition : scanner.findCandidateComponents(modelPackage)) { 194 | Class clazz = getClass(beanDefinition); 195 | if (schemaMap.containsKey(clazz.getSimpleName()) || clazz.getAnnotation(OpenApiIgnore.class) != null) { 196 | continue; 197 | } 198 | GenerationContext generationContext = new GenerationContext(inheritanceMap, packagesWithoutRegex); 199 | Model transformedComponentSchema = componentSchemaTransformer.transformSimpleSchema(clazz, generationContext); 200 | schemaInterceptors.forEach(schemaInterceptor -> schemaInterceptor.intercept(clazz, transformedComponentSchema)); 201 | schemaMap.put(clazz.getSimpleName(), transformedComponentSchema); 202 | } 203 | } 204 | return schemaMap; 205 | } 206 | 207 | private Class getClass(BeanDefinition beanDefinition) { 208 | try { 209 | return Class.forName(beanDefinition.getBeanClassName()); 210 | } catch (ClassNotFoundException e) { 211 | throw new IllegalArgumentException(e); 212 | } 213 | } 214 | 215 | private Optional getInheritanceInfo(Class clazz) { 216 | if (clazz.getAnnotation(JsonSubTypes.class) != null) { 217 | List annotations = unmodifiableList(asList(clazz.getAnnotations())); 218 | JsonTypeInfo jsonTypeInfo = annotations.stream() 219 | .filter(annotation -> annotation instanceof JsonTypeInfo) 220 | .map(annotation -> (JsonTypeInfo) annotation) 221 | .findFirst() 222 | .orElse(null); 223 | 224 | InheritanceInfo inheritanceInfo = new InheritanceInfo(); 225 | inheritanceInfo.setDiscriminatorFieldName(getDiscriminatorName(jsonTypeInfo)); 226 | inheritanceInfo.setDiscriminatorClassMap(scanJacksonInheritance(annotations)); 227 | return Optional.of(inheritanceInfo); 228 | } 229 | return Optional.empty(); 230 | } 231 | 232 | private String getDiscriminatorName(JsonTypeInfo jsonTypeInfo) { 233 | if (jsonTypeInfo == null) { 234 | return DEFAULT_DISCRIMINATOR_NAME; 235 | } 236 | return jsonTypeInfo.property(); 237 | } 238 | 239 | private List removeRegexFormatFromPackages(List modelPackages) { 240 | return modelPackages.stream() 241 | .map(modelPackage -> modelPackage.replace(".*", "")) 242 | .collect(Collectors.toList()); 243 | } 244 | 245 | private Map scanJacksonInheritance(List annotations) { 246 | return annotations.stream() 247 | .filter(annotation -> annotation instanceof JsonSubTypes) 248 | .map(annotation -> (JsonSubTypes) annotation) 249 | .flatMap(jsonSubTypesMapped -> Arrays.stream(jsonSubTypesMapped.value())) 250 | .collect(Collectors.toMap(o -> o.value().getCanonicalName(), JsonSubTypes.Type::name)); 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/config/CompatibilityMode.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.config; 2 | 3 | public enum CompatibilityMode { 4 | 5 | NONE, 6 | NSWAG 7 | 8 | } 9 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/config/OpenApiV2GeneratorConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.config; 2 | 3 | import org.springframework.core.env.Environment; 4 | 5 | public class OpenApiV2GeneratorConfig { 6 | 7 | private CompatibilityMode compatibilityMode; 8 | private String basePath; 9 | private String host; 10 | private Environment environment; 11 | 12 | public CompatibilityMode getCompatibilityMode() { 13 | return compatibilityMode; 14 | } 15 | 16 | public void setCompatibilityMode(CompatibilityMode compatibilityMode) { 17 | this.compatibilityMode = compatibilityMode; 18 | } 19 | 20 | public String getBasePath() { 21 | return basePath; 22 | } 23 | 24 | public void setBasePath(String basePath) { 25 | this.basePath = basePath; 26 | } 27 | 28 | public String getHost() { 29 | return host; 30 | } 31 | 32 | public void setHost(String host) { 33 | this.host = host; 34 | } 35 | 36 | public Environment getEnvironment() { 37 | return environment; 38 | } 39 | 40 | public void setEnvironment(Environment environment) { 41 | this.environment = environment; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/config/builder/OpenApiV2GeneratorConfigBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.config.builder; 2 | 3 | import org.springframework.core.env.Environment; 4 | 5 | import com.github.jrcodeza.schema.v2.generator.config.CompatibilityMode; 6 | import com.github.jrcodeza.schema.v2.generator.config.OpenApiV2GeneratorConfig; 7 | 8 | public final class OpenApiV2GeneratorConfigBuilder { 9 | 10 | private OpenApiV2GeneratorConfig openApiV2GeneratorConfig; 11 | 12 | private OpenApiV2GeneratorConfigBuilder() { 13 | openApiV2GeneratorConfig = new OpenApiV2GeneratorConfig(); 14 | } 15 | 16 | public static OpenApiV2GeneratorConfigBuilder empty() { 17 | return new OpenApiV2GeneratorConfigBuilder(); 18 | } 19 | 20 | public OpenApiV2GeneratorConfigBuilder withCompatibilityMode(CompatibilityMode compatibilityMode) { 21 | openApiV2GeneratorConfig.setCompatibilityMode(compatibilityMode); 22 | return this; 23 | } 24 | 25 | public OpenApiV2GeneratorConfigBuilder withBasePath(String basePath) { 26 | openApiV2GeneratorConfig.setBasePath(basePath); 27 | return this; 28 | } 29 | 30 | public OpenApiV2GeneratorConfigBuilder withHost(String host) { 31 | openApiV2GeneratorConfig.setHost(host); 32 | return this; 33 | } 34 | 35 | public OpenApiV2GeneratorConfigBuilder withEnvironment(Environment environment) { 36 | openApiV2GeneratorConfig.setEnvironment(environment); 37 | return this; 38 | } 39 | 40 | public OpenApiV2GeneratorConfig build() { 41 | return openApiV2GeneratorConfig; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/interceptors/OperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.Operation; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | public interface OperationInterceptor { 8 | 9 | void intercept(Method method, Operation transformedOperation); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/interceptors/OperationParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | public interface OperationParameterInterceptor { 7 | 8 | void intercept(Method method, Parameter parameter, String parameterName, 9 | io.swagger.models.parameters.Parameter transformedParameter); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/interceptors/RequestBodyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.parameters.BodyParameter; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Parameter; 7 | 8 | public interface RequestBodyInterceptor { 9 | 10 | void intercept(Method method, Parameter parameter, String parameterName, BodyParameter transformedRequestBody); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/interceptors/SchemaFieldInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.properties.Property; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | public interface SchemaFieldInterceptor { 8 | 9 | void intercept(Class clazz, Field field, Property transformedFieldSchema); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/interceptors/SchemaInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.Model; 4 | 5 | public interface SchemaInterceptor { 6 | 7 | void intercept(Class clazz, Model transformedSchema); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/CustomBodyParameter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | import io.swagger.models.parameters.AbstractParameter; 9 | import io.swagger.models.parameters.Parameter; 10 | import io.swagger.models.properties.Property; 11 | 12 | public class CustomBodyParameter extends AbstractParameter implements Parameter { 13 | 14 | private CustomSchema schema; 15 | private Map examples; 16 | private String type; 17 | private Property items; 18 | 19 | public CustomBodyParameter() { 20 | super.setIn("body"); 21 | } 22 | 23 | public CustomBodyParameter schema(CustomSchema schema) { 24 | this.setSchema(schema); 25 | return this; 26 | } 27 | 28 | public CustomBodyParameter example(String mediaType, String value) { 29 | this.addExample(mediaType, value); 30 | return this; 31 | } 32 | 33 | public CustomBodyParameter description(String description) { 34 | this.setDescription(description); 35 | return this; 36 | } 37 | 38 | public CustomBodyParameter name(String name) { 39 | this.setName(name); 40 | return this; 41 | } 42 | 43 | public CustomSchema getSchema() { 44 | return schema; 45 | } 46 | 47 | public void setSchema(CustomSchema schema) { 48 | this.schema = schema; 49 | } 50 | 51 | @JsonProperty("x-examples") 52 | public Map getExamples() { 53 | return this.examples; 54 | } 55 | 56 | public void setExamples(Map examples) { 57 | this.examples = examples; 58 | } 59 | 60 | private void addExample(String mediaType, String value) { 61 | if (this.examples == null) { 62 | this.examples = new LinkedHashMap<>(); 63 | } 64 | 65 | this.examples.put(mediaType, value); 66 | } 67 | 68 | public String getType() { 69 | return type; 70 | } 71 | 72 | public void setType(String type) { 73 | this.type = type; 74 | } 75 | 76 | public Property getItems() { 77 | return items; 78 | } 79 | 80 | public void setItems(Property items) { 81 | this.items = items; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/CustomQueryParameter.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import io.swagger.models.parameters.QueryParameter; 6 | 7 | public class CustomQueryParameter extends QueryParameter { 8 | 9 | @JsonProperty("$ref") 10 | private String ref; 11 | 12 | public String getRef() { 13 | return ref; 14 | } 15 | 16 | public void setRef(String ref) { 17 | this.ref = ref; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/CustomSchema.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class CustomSchema { 6 | 7 | @JsonProperty("$ref") 8 | private String ref; 9 | 10 | public String getRef() { 11 | return ref; 12 | } 13 | 14 | public void setRef(String ref) { 15 | this.ref = ref; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/GenerationContext.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import com.github.jrcodeza.OpenApiIgnore; 7 | 8 | @OpenApiIgnore 9 | public class GenerationContext { 10 | 11 | private final List modelPackages; 12 | private final Map inheritanceMap; 13 | 14 | public GenerationContext(Map inheritanceMap, List modelPackages) { 15 | this.modelPackages = modelPackages; 16 | this.inheritanceMap = inheritanceMap; 17 | } 18 | 19 | public List getModelPackages() { 20 | return modelPackages; 21 | } 22 | 23 | public Map getInheritanceMap() { 24 | return inheritanceMap; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/Header.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import com.github.jrcodeza.OpenApiIgnore; 4 | 5 | @OpenApiIgnore 6 | public class Header { 7 | 8 | private String name; 9 | private String description; 10 | private boolean required = false; 11 | 12 | public Header(String name) { 13 | this.name = name; 14 | } 15 | 16 | public Header(String name, String description) { 17 | this.name = name; 18 | this.description = description; 19 | } 20 | 21 | public Header(String name, String description, boolean required) { 22 | this.name = name; 23 | this.description = description; 24 | this.required = required; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public String getDescription() { 36 | return description; 37 | } 38 | 39 | public void setDescription(String description) { 40 | this.description = description; 41 | } 42 | 43 | public boolean isRequired() { 44 | return required; 45 | } 46 | 47 | public void setRequired(boolean required) { 48 | this.required = required; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/model/InheritanceInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.model; 2 | 3 | import java.util.Map; 4 | 5 | import com.github.jrcodeza.OpenApiIgnore; 6 | 7 | @OpenApiIgnore 8 | public class InheritanceInfo { 9 | 10 | private String discriminatorFieldName; 11 | private Map discriminatorClassMap; 12 | 13 | public String getDiscriminatorFieldName() { 14 | return discriminatorFieldName; 15 | } 16 | 17 | public void setDiscriminatorFieldName(String discriminatorName) { 18 | this.discriminatorFieldName = discriminatorName; 19 | } 20 | 21 | public Map getDiscriminatorClassMap() { 22 | return discriminatorClassMap; 23 | } 24 | 25 | public void setDiscriminatorClassMap(Map discriminatorClassMap) { 26 | this.discriminatorClassMap = discriminatorClassMap; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/util/CommonConstants.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.util; 2 | 3 | public final class CommonConstants { 4 | 5 | public static final String COMPONENT_REF_PREFIX = "#/definitions/"; 6 | public static final String FILE_COMPONENT_NAME = "ApiFile"; 7 | 8 | private CommonConstants() { 9 | throw new AssertionError(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /schema-v2-generator/src/main/java/com/github/jrcodeza/schema/v2/generator/util/GeneratorUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.util; 2 | 3 | import java.lang.reflect.AnnotatedElement; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Modifier; 6 | 7 | import com.github.jrcodeza.OpenApiIgnore; 8 | 9 | public final class GeneratorUtils { 10 | 11 | private GeneratorUtils() { 12 | throw new AssertionError(); 13 | } 14 | 15 | public static boolean shouldBeIgnored(AnnotatedElement annotatedElement) { 16 | return annotatedElement.getAnnotation(OpenApiIgnore.class) != null; 17 | } 18 | 19 | public static boolean shouldBeIgnored(Field field) { 20 | return Modifier.isStatic(field.getModifiers()) || shouldBeIgnored((AnnotatedElement) field); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/OpenAPIV2GeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator; 2 | 3 | import io.swagger.models.Info; 4 | import org.apache.commons.io.IOUtils; 5 | import org.json.JSONException; 6 | import org.junit.Test; 7 | import org.skyscreamer.jsonassert.JSONAssert; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.nio.charset.StandardCharsets; 12 | 13 | import com.fasterxml.jackson.core.JsonProcessingException; 14 | import com.github.jrcodeza.schema.v2.generator.config.CompatibilityMode; 15 | import com.github.jrcodeza.schema.v2.generator.config.builder.OpenApiV2GeneratorConfigBuilder; 16 | import com.github.jrcodeza.schema.v2.generator.interceptors.TestOperationInterceptor; 17 | import com.github.jrcodeza.schema.v2.generator.interceptors.TestOperationParameterInterceptor; 18 | import com.github.jrcodeza.schema.v2.generator.interceptors.TestRequestBodyInterceptor; 19 | import com.github.jrcodeza.schema.v2.generator.interceptors.TestSchemaFieldInterceptor; 20 | import com.github.jrcodeza.schema.v2.generator.interceptors.TestSchemaInterceptor; 21 | 22 | import static java.util.Collections.singletonList; 23 | 24 | public class OpenAPIV2GeneratorTest { 25 | 26 | private static final TestOperationInterceptor operationInterceptor = new TestOperationInterceptor(); 27 | private static final TestOperationParameterInterceptor operationParameterInterceptor = new TestOperationParameterInterceptor(); 28 | private static final TestRequestBodyInterceptor requestBodyInterceptor = new TestRequestBodyInterceptor(); 29 | private static final TestSchemaFieldInterceptor schemaFieldInterceptor = new TestSchemaFieldInterceptor(); 30 | private static final TestSchemaInterceptor schemaInterceptor = new TestSchemaInterceptor(); 31 | 32 | @Test 33 | public void generateStandardScenario() throws JsonProcessingException { 34 | String openAPIJson = createTestGenerator().generateJson(); 35 | assertOpenApiResult(openAPIJson, "expected_v2_openapi.json"); 36 | } 37 | 38 | @Test 39 | public void generateStandardScenarioWithCustomConfig() throws JsonProcessingException { 40 | String openAPIJson = createTestGenerator().generateJson(OpenApiV2GeneratorConfigBuilder.empty() 41 | .withCompatibilityMode(CompatibilityMode.NSWAG) 42 | .withBasePath("/test") 43 | .withHost("test.com") 44 | .build() 45 | ); 46 | assertOpenApiResult(openAPIJson, "expected_v2_openapi_nswag.json"); 47 | } 48 | 49 | private void assertOpenApiResult(String openAPI, String pathToExpectedFile) { 50 | try { 51 | JSONAssert.assertEquals(getResourceFileAsString(pathToExpectedFile), openAPI, true); 52 | } catch (JSONException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | private OpenAPIV2Generator createTestGenerator() { 58 | OpenAPIV2Generator openAPIGenerator = new OpenAPIV2Generator( 59 | singletonList("com.github.jrcodeza.schema.v2.generator.domain.*"), 60 | singletonList("com.github.jrcodeza.schema.v2.generator.controller.*"), 61 | createTestInfo() 62 | ); 63 | openAPIGenerator.addOperationInterceptor(operationInterceptor); 64 | openAPIGenerator.addOperationParameterInterceptor(operationParameterInterceptor); 65 | openAPIGenerator.addRequestBodyInterceptor(requestBodyInterceptor); 66 | openAPIGenerator.addSchemaFieldInterceptor(schemaFieldInterceptor); 67 | openAPIGenerator.addSchemaInterceptor(schemaInterceptor); 68 | openAPIGenerator.addGlobalHeader("Test-Global-Header", "Some desc", false); 69 | return openAPIGenerator; 70 | } 71 | 72 | private Info createTestInfo() { 73 | Info info = new Info(); 74 | info.setTitle("Test API"); 75 | info.setDescription("Test description"); 76 | info.setVersion("1.0.0"); 77 | return info; 78 | } 79 | 80 | private String getResourceFileAsString(String pathToExpectedFile) { 81 | ClassLoader classLoader = getClass().getClassLoader(); 82 | try (InputStream inputStream = classLoader.getResourceAsStream(pathToExpectedFile)) { 83 | return IOUtils.toString(inputStream, StandardCharsets.UTF_8); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } 87 | return null; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/base/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.base; 2 | 3 | public class Entity { 4 | 5 | private String id; 6 | 7 | public String getId() { 8 | return id; 9 | } 10 | 11 | public void setId(String id) { 12 | this.id = id; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/controller/CarController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.controller; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestHeader; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.ResponseStatus; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import org.springframework.web.multipart.MultipartFile; 15 | 16 | import java.util.List; 17 | 18 | import com.github.jrcodeza.schema.v2.generator.domain.Car; 19 | 20 | @RestController 21 | @RequestMapping("/cars") 22 | public class CarController { 23 | 24 | @PostMapping 25 | public Car createCar(@RequestHeader("source") String source, @RequestBody Car car) { 26 | return null; 27 | } 28 | 29 | @GetMapping 30 | public List getCars(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 31 | return null; 32 | } 33 | 34 | @GetMapping("/{carId}") 35 | public Car getCar(@PathVariable("carId") String carId) { 36 | return null; 37 | } 38 | 39 | @PostMapping("/{carId}/photos") 40 | @ResponseStatus(HttpStatus.ACCEPTED) 41 | public void uploadCarPhoto(@PathVariable String carId, @RequestBody MultipartFile multipartFile) { 42 | // do nothing 43 | } 44 | 45 | @RequestMapping(path = "/{carId}/documents", consumes = "multipart/form-data", produces = "application/json", method = RequestMethod.POST) 46 | public Car uploadCarDocuments(@RequestHeader("source") String source, 47 | @PathVariable("carId") String carId, 48 | @RequestParam(name = "documentFile") MultipartFile documentFile, 49 | @RequestParam(name = "type") String type) { 50 | return null; 51 | } 52 | 53 | public void noOperationMethod() { 54 | // do nothing 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/controller/ControllerToBeIgnored.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import com.github.jrcodeza.OpenApiIgnore; 9 | import com.github.jrcodeza.schema.v2.generator.domain.dummy.ValidationDummy; 10 | 11 | @RestController 12 | @RequestMapping("/toBeIgnored") 13 | @OpenApiIgnore 14 | public class ControllerToBeIgnored { 15 | 16 | @GetMapping 17 | public ValidationDummy getIgnoredController(@RequestParam String someParam) { 18 | return null; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/controller/DummyController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.github.jrcodeza.Header; 6 | import com.github.jrcodeza.OpenApiExample; 7 | import com.github.jrcodeza.OpenApiExamples; 8 | import com.github.jrcodeza.OpenApiIgnore; 9 | import com.github.jrcodeza.Response; 10 | import com.github.jrcodeza.Responses; 11 | import com.github.jrcodeza.schema.v2.generator.domain.CarType; 12 | import com.github.jrcodeza.schema.v2.generator.domain.OptionsClass; 13 | import com.github.jrcodeza.schema.v2.generator.domain.dummy.ValidationDummy; 14 | 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.validation.annotation.Validated; 18 | import org.springframework.web.bind.annotation.DeleteMapping; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | import org.springframework.web.bind.annotation.PatchMapping; 21 | import org.springframework.web.bind.annotation.PathVariable; 22 | import org.springframework.web.bind.annotation.PostMapping; 23 | import org.springframework.web.bind.annotation.PutMapping; 24 | import org.springframework.web.bind.annotation.RequestBody; 25 | import org.springframework.web.bind.annotation.RequestHeader; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestMethod; 28 | import org.springframework.web.bind.annotation.RequestParam; 29 | import org.springframework.web.bind.annotation.ResponseStatus; 30 | import org.springframework.web.bind.annotation.RestController; 31 | import org.springframework.web.multipart.MultipartFile; 32 | 33 | @RestController 34 | @RequestMapping("/dummy") 35 | public class DummyController { 36 | 37 | @RequestMapping(path = "/{id}", method = RequestMethod.HEAD) 38 | public ResponseEntity isPresent(@PathVariable("id") Integer id) { 39 | return null; 40 | } 41 | 42 | @RequestMapping(path = "/{id}", method = RequestMethod.OPTIONS) 43 | public ResponseEntity getOptions(@PathVariable("id") Integer id) { 44 | return null; 45 | } 46 | 47 | @PostMapping 48 | @ResponseStatus(HttpStatus.CREATED) 49 | public ValidationDummy create(@RequestBody @Validated @OpenApiExample("{\"bodyExample\":\"value\"}") ValidationDummy validationDummy) { 50 | return null; 51 | } 52 | 53 | @PutMapping("/{id}") 54 | @Responses({ 55 | @Response(responseCode = 201, description = "Created", responseBody = ValidationDummy.class, 56 | headers = @Header(name = "SomeHeader", description = "TestHeader")), 57 | @Response(responseCode = 200, description = "Replaced", responseBody = ValidationDummy.class) 58 | }) 59 | public ValidationDummy createOrReplace(@PathVariable(required = false) Integer id, @RequestBody @Validated ValidationDummy validationDummy) { 60 | return null; 61 | } 62 | 63 | @PatchMapping(path = "/{id}") 64 | public ValidationDummy patch(@PathVariable Integer id, @RequestBody @Validated ValidationDummy validationDummy) { 65 | return null; 66 | } 67 | 68 | @GetMapping("/{id}/subpath/{anotherId}") 69 | public ValidationDummy getTwoPathVariables(@PathVariable Integer id, @PathVariable("anotherId") Integer another) { 70 | return null; 71 | } 72 | 73 | @GetMapping("/{id}/subpath/") 74 | public ValidationDummy subpath( 75 | @RequestHeader String headerA, 76 | @RequestHeader("headerB") String headerB, 77 | @PathVariable Integer id, 78 | @RequestParam("requestParamA") @OpenApiExample(name = "customValidationDummy", description = "CustomDescription", key = "CUSTOM_EXAMPLE_KEY_1") 79 | String requestParamA, 80 | @RequestParam String requestParamB, 81 | @RequestParam(name = "requestParamC", required = false) String requestParamC 82 | ) { 83 | return null; 84 | } 85 | 86 | @GetMapping("/onlyRequestParams") 87 | public ValidationDummy onlyRequestParams( 88 | @RequestParam("requestParamA") String requestParamA, 89 | @RequestParam @OpenApiExamples(value = { 90 | @OpenApiExample(name = "example_1", description = "example_1_description", value = "moreExamples_1"), 91 | @OpenApiExample(name = "example_2", description = "example_2_description", value = "moreExamples_2") 92 | }) 93 | String requestParamB, 94 | @RequestParam(name = "requestParamC", required = false) String requestParamC 95 | ) { 96 | return null; 97 | } 98 | 99 | @PostMapping("/{id}/subpath/") 100 | public ValidationDummy complexPost( 101 | @RequestHeader String headerA, 102 | @RequestHeader("headerB") String headerB, 103 | @PathVariable Integer id, 104 | @RequestParam("requestParamA") String requestParamA, 105 | @RequestParam String requestParamB, 106 | @RequestParam(name = "requestParamC", required = false) String requestParamC, 107 | @RequestBody ValidationDummy testBody 108 | ) { 109 | return null; 110 | } 111 | 112 | @DeleteMapping(path = "/{id}") 113 | public ValidationDummy delete(@PathVariable Integer id) { 114 | return null; 115 | } 116 | 117 | @PostMapping(path = "/requestBodyList") 118 | public ValidationDummy requestBodyList(@RequestBody List validationDummies, 119 | @RequestParam @OpenApiIgnore String toBeIgnored) { 120 | return null; 121 | } 122 | 123 | @PostMapping(path = "/toBeIgnored") 124 | @OpenApiIgnore 125 | public ValidationDummy methodToBeIgnored(@RequestBody List validationDummies) { 126 | return null; 127 | } 128 | 129 | @PatchMapping(path = "/bodyToBeIgnored/{variable}") 130 | @OpenApiIgnore 131 | public ValidationDummy methodToBeIgnored( 132 | @PathVariable String variable, 133 | @RequestBody @OpenApiIgnore List validationDummies) { 134 | return null; 135 | } 136 | 137 | @GetMapping(path = "/fileWithoutResponseAnnotation") 138 | public MultipartFile getFileWithoutResponseAnnotation() { 139 | return null; 140 | } 141 | 142 | @GetMapping(path = "/fileWithResponseAnnotation", produces = "application/pdf") 143 | @Response(responseCode = 200, description = "desc", responseBody = MultipartFile.class) 144 | public MultipartFile getFileWithResponseAnnotation() { 145 | return null; 146 | } 147 | 148 | @GetMapping(path = "/enumAsParam") 149 | public ValidationDummy enumAsParam(@RequestParam CarType carType) { 150 | return null; 151 | } 152 | 153 | @GetMapping(path = "/xmlAsString", produces = "application/xml") 154 | public String xmlAsString() { 155 | return null; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/controller/ListController.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.controller; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.List; 10 | 11 | import com.github.jrcodeza.schema.v2.generator.domain.Car; 12 | 13 | @RestController 14 | @RequestMapping("/lists") 15 | public class ListController { 16 | 17 | @GetMapping("with-response-entity") 18 | public ResponseEntity> getCarsWithResponseEntity(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 19 | return null; 20 | } 21 | 22 | @GetMapping("list-without-generics") 23 | public ResponseEntity getCarsWithListWithoutGenerics(@RequestParam(required = false) String model, @RequestParam(required = false) Integer torque) { 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Car.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import javax.validation.constraints.Max; 4 | import javax.validation.constraints.Min; 5 | import javax.validation.constraints.Size; 6 | 7 | import com.github.jrcodeza.OpenApiExample; 8 | import com.github.jrcodeza.OpenApiExamples; 9 | 10 | @OpenApiExamples(value = { 11 | @OpenApiExample(name = "CarExampleName_1", value = "carExampleValue_1"), 12 | @OpenApiExample(name = "CarExampleName_2", value = "carExampleValue_2") 13 | }) 14 | public class Car extends Product { 15 | 16 | public static final String SHOULD_BE_IGNORED_BECAUSE_STATIC = "TestValue"; 17 | 18 | @Min(0) 19 | @Max(1000) 20 | private Integer torque; 21 | 22 | private Integer maxSpeed; 23 | 24 | @Size(min = 2, max = 30) 25 | @OpenApiExample(value = "field example") 26 | private String model; 27 | 28 | private CarType carType; 29 | 30 | public Integer getTorque() { 31 | return torque; 32 | } 33 | 34 | public void setTorque(Integer torque) { 35 | this.torque = torque; 36 | } 37 | 38 | public Integer getMaxSpeed() { 39 | return maxSpeed; 40 | } 41 | 42 | public void setMaxSpeed(Integer maxSpeed) { 43 | this.maxSpeed = maxSpeed; 44 | } 45 | 46 | public String getModel() { 47 | return model; 48 | } 49 | 50 | public void setModel(String model) { 51 | this.model = model; 52 | } 53 | 54 | public CarType getCarType() { 55 | return carType; 56 | } 57 | 58 | public void setCarType(CarType carType) { 59 | this.carType = carType; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/CarType.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | public enum CarType { 4 | 5 | PERSONAL, 6 | TRUCK, 7 | VAN 8 | 9 | } 10 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import com.github.jrcodeza.OpenApiIgnore; 4 | 5 | public class Customer extends Entity { 6 | 7 | private boolean vip; 8 | 9 | private Product topCustomerProduct; 10 | 11 | @OpenApiIgnore 12 | private String toBeIgnored; 13 | 14 | public boolean isVip() { 15 | return vip; 16 | } 17 | 18 | public void setVip(boolean vip) { 19 | this.vip = vip; 20 | } 21 | 22 | public Product getTopCustomerProduct() { 23 | return topCustomerProduct; 24 | } 25 | 26 | public void setTopCustomerProduct(Product topCustomerProduct) { 27 | this.topCustomerProduct = topCustomerProduct; 28 | } 29 | 30 | public String getToBeIgnored() { 31 | return toBeIgnored; 32 | } 33 | 34 | public void setToBeIgnored(String toBeIgnored) { 35 | this.toBeIgnored = toBeIgnored; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/CustomerInventory.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | public class CustomerInventory { 4 | 5 | private Customer[] customers; 6 | 7 | private Object object; 8 | 9 | public Customer[] getCustomers() { 10 | return customers; 11 | } 12 | 13 | public void setCustomers(Customer[] customers) { 14 | this.customers = customers; 15 | } 16 | 17 | public Object getObject() { 18 | return object; 19 | } 20 | 21 | public void setObject(Object object) { 22 | this.object = object; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Entity.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class Entity { 6 | 7 | @NotNull 8 | private String id; 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Laptop.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class Laptop extends Product { 6 | 7 | @NotNull 8 | private String model; 9 | 10 | @NotNull 11 | private Boolean hasWifi; 12 | 13 | public String getModel() { 14 | return model; 15 | } 16 | 17 | public void setModel(String model) { 18 | this.model = model; 19 | } 20 | 21 | public Boolean getHasWifi() { 22 | return hasWifi; 23 | } 24 | 25 | public void setHasWifi(Boolean hasWifi) { 26 | this.hasWifi = hasWifi; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/OptionsClass.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | public class OptionsClass { 4 | 5 | private String options; 6 | 7 | public String getOptions() { 8 | return options; 9 | } 10 | 11 | public void setOptions(String options) { 12 | this.options = options; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | 6 | public class Order extends Entity { 7 | 8 | private LocalDateTime dateTime; 9 | private Customer customer; 10 | private List products; 11 | 12 | public Customer getCustomer() { 13 | return customer; 14 | } 15 | 16 | public void setCustomer(Customer customer) { 17 | this.customer = customer; 18 | } 19 | 20 | public List getProducts() { 21 | return products; 22 | } 23 | 24 | public void setProducts(List products) { 25 | this.products = products; 26 | } 27 | 28 | public LocalDateTime getDateTime() { 29 | return dateTime; 30 | } 31 | 32 | public void setDateTime(LocalDateTime dateTime) { 33 | this.dateTime = dateTime; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import java.math.BigDecimal; 5 | 6 | import com.fasterxml.jackson.annotation.JsonSubTypes; 7 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 8 | 9 | @JsonTypeInfo( 10 | use = JsonTypeInfo.Id.NAME, 11 | property = "type") 12 | @JsonSubTypes({ 13 | @JsonSubTypes.Type(value = Car.class, name = "car"), 14 | @JsonSubTypes.Type(value = Van.class, name = "van"), 15 | @JsonSubTypes.Type(value = Laptop.class, name = "laptop") 16 | }) 17 | public class Product extends Entity { 18 | 19 | @NotNull 20 | private BigDecimal price; 21 | private int amount; 22 | 23 | public BigDecimal getPrice() { 24 | return price; 25 | } 26 | 27 | public void setPrice(BigDecimal price) { 28 | this.price = price; 29 | } 30 | 31 | public int getAmount() { 32 | return amount; 33 | } 34 | 35 | public void setAmount(int amount) { 36 | this.amount = amount; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/Van.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain; 2 | 3 | public class Van extends Car { 4 | 5 | private Integer maxLoad; 6 | 7 | public Integer getMaxLoad() { 8 | return maxLoad; 9 | } 10 | 11 | public void setMaxLoad(Integer maxLoad) { 12 | this.maxLoad = maxLoad; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/dummy/ArrayDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain.dummy; 2 | 3 | import com.github.jrcodeza.schema.v2.generator.domain.Car; 4 | import com.github.jrcodeza.schema.v2.generator.domain.Product; 5 | 6 | public class ArrayDummy { 7 | 8 | private Integer[] integers; 9 | private Product[] products; 10 | private Car[] cars; 11 | private int[] primitiveIntegers; 12 | 13 | public Integer[] getIntegers() { 14 | return integers; 15 | } 16 | 17 | public void setIntegers(Integer[] integers) { 18 | this.integers = integers; 19 | } 20 | 21 | public Product[] getProducts() { 22 | return products; 23 | } 24 | 25 | public void setProducts(Product[] products) { 26 | this.products = products; 27 | } 28 | 29 | public Car[] getCars() { 30 | return cars; 31 | } 32 | 33 | public void setCars(Car[] cars) { 34 | this.cars = cars; 35 | } 36 | 37 | public int[] getPrimitiveIntegers() { 38 | return primitiveIntegers; 39 | } 40 | 41 | public void setPrimitiveIntegers(int[] primitiveIntegers) { 42 | this.primitiveIntegers = primitiveIntegers; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/dummy/ListDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain.dummy; 2 | 3 | import javax.validation.constraints.Size; 4 | import java.util.List; 5 | 6 | import com.github.jrcodeza.schema.v2.generator.domain.Car; 7 | import com.github.jrcodeza.schema.v2.generator.domain.CarType; 8 | import com.github.jrcodeza.schema.v2.generator.domain.Product; 9 | 10 | public class ListDummy { 11 | 12 | @Size(min = 2, max = 6) 13 | private List products; 14 | private List integers; 15 | private List cars; 16 | private List enums; 17 | 18 | public List getProducts() { 19 | return products; 20 | } 21 | 22 | public void setProducts(List products) { 23 | this.products = products; 24 | } 25 | 26 | public List getIntegers() { 27 | return integers; 28 | } 29 | 30 | public void setIntegers(List integers) { 31 | this.integers = integers; 32 | } 33 | 34 | public List getCars() { 35 | return cars; 36 | } 37 | 38 | public void setCars(List cars) { 39 | this.cars = cars; 40 | } 41 | 42 | public List getEnums() { 43 | return enums; 44 | } 45 | 46 | public void setEnums(List enums) { 47 | this.enums = enums; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/domain/dummy/ValidationDummy.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.domain.dummy; 2 | 3 | import javax.validation.constraints.DecimalMax; 4 | import javax.validation.constraints.DecimalMin; 5 | import javax.validation.constraints.Max; 6 | import javax.validation.constraints.Min; 7 | import javax.validation.constraints.NotNull; 8 | import javax.validation.constraints.Pattern; 9 | import javax.validation.constraints.Size; 10 | import java.math.BigDecimal; 11 | 12 | public class ValidationDummy { 13 | 14 | @DecimalMin("1.05") 15 | @DecimalMax("2.5") 16 | private BigDecimal decimalRange; 17 | 18 | @Min(3) 19 | @Max(7) 20 | private Integer minMax; 21 | 22 | @NotNull 23 | private String notNull; 24 | 25 | @Pattern(regexp = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b") 26 | private String regex; 27 | 28 | @Size(min = 2, max = 10) 29 | private String stringSize; 30 | 31 | @Size(max = 10) 32 | private String stringSizeOnlyMax; 33 | 34 | private String deprecated; 35 | 36 | @Deprecated 37 | private String javaDeprecated; 38 | 39 | private String writeOnly; 40 | 41 | private String readOnly; 42 | 43 | public BigDecimal getDecimalRange() { 44 | return decimalRange; 45 | } 46 | 47 | public void setDecimalRange(BigDecimal decimalRange) { 48 | this.decimalRange = decimalRange; 49 | } 50 | 51 | public Integer getMinMax() { 52 | return minMax; 53 | } 54 | 55 | public void setMinMax(Integer minMax) { 56 | this.minMax = minMax; 57 | } 58 | 59 | public String getNotNull() { 60 | return notNull; 61 | } 62 | 63 | public void setNotNull(String notNull) { 64 | this.notNull = notNull; 65 | } 66 | 67 | public String getRegex() { 68 | return regex; 69 | } 70 | 71 | public void setRegex(String regex) { 72 | this.regex = regex; 73 | } 74 | 75 | public String getStringSize() { 76 | return stringSize; 77 | } 78 | 79 | public void setStringSize(String stringSize) { 80 | this.stringSize = stringSize; 81 | } 82 | 83 | public String getStringSizeOnlyMax() { 84 | return stringSizeOnlyMax; 85 | } 86 | 87 | public void setStringSizeOnlyMax(String stringSizeOnlyMax) { 88 | this.stringSizeOnlyMax = stringSizeOnlyMax; 89 | } 90 | 91 | public String getDeprecated() { 92 | return deprecated; 93 | } 94 | 95 | public void setDeprecated(String deprecated) { 96 | this.deprecated = deprecated; 97 | } 98 | 99 | public String getJavaDeprecated() { 100 | return javaDeprecated; 101 | } 102 | 103 | public void setJavaDeprecated(String javaDeprecated) { 104 | this.javaDeprecated = javaDeprecated; 105 | } 106 | 107 | public String getWriteOnly() { 108 | return writeOnly; 109 | } 110 | 111 | public void setWriteOnly(String writeOnly) { 112 | this.writeOnly = writeOnly; 113 | } 114 | 115 | public String getReadOnly() { 116 | return readOnly; 117 | } 118 | 119 | public void setReadOnly(String readOnly) { 120 | this.readOnly = readOnly; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/interceptors/TestOperationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.Operation; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | public class TestOperationInterceptor implements OperationInterceptor { 8 | 9 | @Override 10 | public void intercept(Method method, Operation transformedOperation) { 11 | transformedOperation.setSummary("Interceptor summary"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/interceptors/TestOperationParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Parameter; 5 | 6 | import com.github.jrcodeza.schema.v2.generator.util.TestUtils; 7 | 8 | public class TestOperationParameterInterceptor implements OperationParameterInterceptor { 9 | 10 | @Override 11 | public void intercept(Method method, Parameter parameter, String parameterName, 12 | io.swagger.models.parameters.Parameter transformedParameter) { 13 | transformedParameter.setDescription(TestUtils.emptyIfNull(transformedParameter.getDescription()) + ". Interceptor OperationParameter test"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/interceptors/TestRequestBodyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.parameters.BodyParameter; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Parameter; 7 | 8 | import com.github.jrcodeza.schema.v2.generator.util.TestUtils; 9 | 10 | public class TestRequestBodyInterceptor implements RequestBodyInterceptor { 11 | 12 | @Override 13 | public void intercept(Method method, Parameter parameter, String parameterName, BodyParameter transformedRequestBody) { 14 | transformedRequestBody.setDescription(TestUtils.emptyIfNull(transformedRequestBody.getDescription()) + ". Test requestBody interceptor"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/interceptors/TestSchemaFieldInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.properties.Property; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | import com.github.jrcodeza.schema.v2.generator.util.TestUtils; 8 | 9 | public class TestSchemaFieldInterceptor implements SchemaFieldInterceptor { 10 | 11 | @Override 12 | public void intercept(Class clazz, Field field, Property transformedFieldSchema) { 13 | transformedFieldSchema.setDescription(TestUtils.emptyIfNull(transformedFieldSchema.getDescription()) + ". Test schemaField interceptor"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/interceptors/TestSchemaInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.interceptors; 2 | 3 | import io.swagger.models.Model; 4 | 5 | import com.github.jrcodeza.schema.v2.generator.util.TestUtils; 6 | 7 | public class TestSchemaInterceptor implements SchemaInterceptor { 8 | 9 | @Override 10 | public void intercept(Class clazz, Model transformedSchema) { 11 | transformedSchema.setDescription(TestUtils.emptyIfNull(transformedSchema.getDescription()) + ". Test schema interceptors"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /schema-v2-generator/src/test/java/com/github/jrcodeza/schema/v2/generator/util/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.jrcodeza.schema.v2.generator.util; 2 | 3 | public final class TestUtils { 4 | 5 | private TestUtils() { 6 | throw new AssertionError(); 7 | } 8 | 9 | public static String emptyIfNull(String string) { 10 | return string == null ? "" : string; 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------