Generate a REST client from a swagger file
As primarily a Java developer, I had the fortunate experience of getting to write Kotlin for roughly seven months recently. Here I share my experience of creating a REST client using an existing swagger file.
However, I am also going to include generating a swagger file from an existing API, in case someone may require that step also.
Again, the initial assumption here is that you are familiar with Kotlin, Gradle and Spring Boot. If not, you can convert Kotlin to Java easily enough online or via your IDE.
Swagger Generation
Some approaches to building APIs are either ‘top-down’ or ‘bottom-up’.
In this example, I have an existing API (bottom-up) and want to generate the swagger.yaml file from my existing controller — in order to generate the REST client, amongst other things.
First things first is to create a config file that utilises the @EnableSwagger2
annotation; something like this:
@Configuration
@EnableSwagger2
class SwaggerConfig {@Bean
open fun api(): Docket = Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(“com.company.package”))
.paths(PathSelectors.any())
.build()
}
Then you can write a test in your ./test
package to execute the json
that Swagger
automatically will generate for you under localhost:8080/v2/api-docs
@RunWith(SpringRunner::class)
@SpringBootTest
class GenerateSwagger(@Autowired val context: WebApplicationContext) {@Test
fun generateSwagger() {
val mockMvc: MockMvc = MockMvcBuilders.webAppContextSetup(context).build()mockMvc.perform(MockMvcRequestBuilders.get("/v2/api-docs").accept(MediaType.APPLICATION_JSON))
.andDo { result: MvcResult ->
FileUtils.writeStringToFile(File("./src/main/resources/swagger.yaml"),
jsonToYaml(result.response.contentAsString))
}
}private fun jsonToYaml(json: String): String {
val jsonNodeTree: JsonNode = ObjectMapper().readTree(json)
return YAMLMapper().writeValueAsString(jsonNodeTree)
}
}
The jsonToYaml
function takes in the json
String
generated in the /v2/api-docs
as returns a String
in Yaml
format.
And simple as that, you will get a swagger.yaml
file in the location you provide (I chose ./src/main/resources
here)
You will need several dependencies so I’ve included the relevant ones I used here (using Gradle
):
io.springfox:springfox-swagger2:2.7.0 com.fasterxml.jackson.core:jackson-core:2.9.4 com.fasterxml.jackson.core:jackson-databind:2.9.4 com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.4 org.apache.commons:commons-io:1.3.2 io.springfox:springfox-swagger-ui:2.7.0
And the usual spring-boot-starter
ones of course.
REST Client
There are five steps listed here, centred around the Gradle file, for creating the client.
Open API Generate
1. Add the Open API Generator plugin to the build.gradle.kts
file –
id("org.openapi.generator") version "4.2.2"
2. Implement the Open API Generate task
a. additionalProperties.put("sourceFolder", "")
– this sets the default generated folder from /src/main/java
to the top level as the build wasn’t picking up the files I wanted without this
b. The swagger file needs to exist in the location set in inputSpec.set("...")
3. The output is a folder in the project’s directory with the name specified in outputDir.set("...")
openApiGenerate {
generatorName.set("java")
library.set("rest-assured")
inputSpec.set("src/main/resources/swagger.yaml")
outputDir.set("$buildDir/...")
additionalProperties.put("sourceFolder", "")
}
Compile
1. Add a task to compile the source files from the generated output above
a. Specify the folder where the source files live- source = fileTree(file("..."))
b. The task must be called compile
, otherwise it doesn’t work
c. The classpath must be set — here I use the main source set’s runtime classpath — see https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_source_sets for more information
d. destinationDir
needs to be set so the output can go somewhere
e. You can exclude subfolder(s) here (if required) — exclude("*/test/*")
2. The output of this task is a folder, again in the build directory, where all the source files have been compiled into .class
files
3. The compilation may fail if the build.gradle.kts
file doesn’t have all the required dependencies, so you might need to add some new ones in the DependencyHandlerScope
– compile("...")
tasks.register<JavaCompile>("compile") {
source = fileTree(file("$buildDir/..."))
classpath = sourceSets["main"].runtimeClasspath
destinationDir = File("$buildDir/...")
exclude("*/test/*")
}
Build
1. Add a task to build a jar from the compiled class files
a. You can set the name of the generated file with archiveBaseName.set("...")
b. You need to specify the source path — from("...")
c. buildSource
is a chosen name and can be anything other than pre-existing task names
2. The resulting jar is defaulted to the /build/libs
folder
tasks.register<Jar>("buildSource") {
archiveBaseName.set("...")
from("$buildDir/...")
}
Task Ordering
1. In order to get the tasks to run in order, one way to do this is to add a finalizedBy
clause to the build task itself
a. This can be achieved by adding to the TaskContainerScope
in the Gradle file. They can be separated individually, or with comma separated values
b. The below snippet will run openApiGenerate
, then compile
, then buildSource
tasks {
"build" {finalizedBy("openApiGenerate")
finalizedBy("compile", "buildSource")
}
}
Action
- Because of using the
finalizedBy("...")
options in thebuild
task, the only command needed is to run the normal./gradlew clean build
.
Conclusion
And that’s it, simple as you like. These steps will create the swagger file from an existing API, and build a REST client for whatever usage is necessary.