Generate a REST client from a swagger file
By: Conor Prunty
Tags: Rest Client, Gradle, Spring Boot, Kotlin

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 { class SwaggerConfig { @Bean

open fun api(): Docket = Docket(DocumentationType.SWAGGER_2)

.select()

.apis(RequestHandlerSelectors.basePackage(“com.company.package”))

.paths(PathSelectors.any())

.build()

} 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



@SpringBootTest

class GenerateSwagger( @RunWith (SpringRunner::class)class GenerateSwagger( @Autowired val context: WebApplicationContext) { @Test

fun generateSwagger() {

val mockMvc: MockMvc = MockMvcBuilders.webAppContextSetup(context).build() 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 the build 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.