In this article we’ll look at a Kotlin springboot project using a generated API from a “swagger file”. As usual, find the code in GitHub at sylhare/Petshop 🐶🛒.
Code generation
Using the petstore.yml example. We are going to use openapi-generator which is a fork from the swagger-codegen to generate the code for our Kotlin application.
Specification
Swagger is based on the OpenAPI Specification,
that’s why you can see the openapi: <version>
at the top of the yaml file. When talking about swagger yaml file, it’s
basically this type of OpenAPI specific yaml file.
Create the Rest API with only the openapi file
Generate the files
You need to install openapi-generator:
brew install openapi-generator
Then run it using your openapi yaml file and if you have your config file. You can also specify some global properties as key=value pairs. (Find the configuration for java and kotlin)
openapi-generator generate -i src/main/resources/petstore.yml -g kotlin-spring --config src/main/resources/api-config.json
# openapi-generator generate -i ../src/main/resources/petstore.yml -g kotlin-spring --config ../src/main/resources/api-config.json --global-property apiTests=true,modelTests=true,apiDocs=true,modelDocs=true
Note: The modelTests, apiTests, modelDocs, apiDocs needs to be defined in the yaml to be generated
This will create the project with a build.gradle.kts (and also a pom.xml for maven).
Make it compile properly
The api-config.json
has "serviceInterface": true
to create the service in the api.
You will still have to add to the corresponding api:
val service: PetApiService
For gradle, the wrapper won’t be created automatically, you can do so by running:
gradle wrapper --gradle-version 4.8 --distribution-type all
./gradlew assemble
The syntax used in the generated build.gradle.kts is rather old and mostly compatible with 4.8. You will have some work to do if you intend to put your project up to date.
Implement the logic
Then you need to create the PetApiServiceImpl
that implements PetApiService
and add it to the PetApiController
implementing PetApi
:
@RestController
class PetApiController : PetApi {
@Autowired
override lateinit var service: PetApiServiceImpl
}
This service is where you can start adding your own implementation of the Petshop. Because the openapi-generator only creates the apis/endpoints and models from the yaml, it’s not that magical! 🧙
To build the project using gradle, run:
./gradlew build && java -jar build/libs/openapi-spring-1.0.0.jar
If all builds successfully, the server should run on http://localhost:8080/
Customize an API respecting an openapi contract
Add the gradle task
If you want to customize or use the generated classes for something else. You can use the org.openapi.generator plugin in your code like:
plugins {
id("org.openapi.generator") version "5.1.1"
}
Then you will be able to use the openApiGenerate
task to generate the code from the yaml files.
openApiGenerate {
generatorName.set("spring")
inputSpec.set("src/main/resources/petstore.yml")
outputDir.set("$buildDir/generated")
configFile.set("src/main/resources/api-config.json")
}
If we analyze the task, we find:
- The generator is
spring
to be compatible with our spring-boot application - The
inputSpec
is the openapi file that the code will be generated from. - The
outputDir
is where the generated code will be placed. (it’s better to leave it in the build directory) - The
api-config.json
store all the configuration for the code generation.
In some case where there are multiple yaml files depending on each other for definitions, you can
use templateDir.set()
to set the directory where those dependant yaml files will be.
We could use other generator like the
jaxrs-spec
depending on the framework used. (e.g. the JAX-RS provides annotations and interfaces that can be implemented to create a Restful API)
Then you need to add the generated code to your source set:
// Add the generated sources to your project
java.sourceSets["main"].java.srcDir("$buildDir/generated/src/main/java")
This way you can call it from your src/main/kotlin code and start implementing it.
OpenAPI yaml file
Create a basic yaml file following the swagger documentation, or just modify an existing one to get this “petshop” api:
openapi: 3.0.0
info:
description: Petshop to test yml file generation
version: 1.0.0
title: Petshop example
paths:
/petshop: # That will be the name of the api -> PetshopApi
post: # Type of request: POST
tags:
- shop
summary: Do nothing really
description: ''
operationId: petshopMethod # That will be the method to add the logic
responses: # ApiResponse documentation
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
'405':
description: Invalid input
requestBody: # RequestBody for the Post request
$ref: '#/components/requestBodies/Pet'
The '#/components/requestBodies/Pet'
are defined in
the petstore.yml
, you can either copy that yaml file inside the petstore.yaml or use file referencing
like './petstore.yml#/components/requestBodies/Pet'
which will refer to the Pet component from the pestore.yml.
(This is a case where you would use the template setting in the gradle task).
Implementation
Then we implement a basic logic that basically return what has been sent:
@RestController
class ShopImpl : ShopApi {
override fun petshopMethod(pet: Pet?): ResponseEntity<Pet> {
return ResponseEntity(pet!!, HttpStatus.OK)
}
}
You can see that the endpoint is inheriting ShopApi
which is defined in the yaml file above.
If not implemented the petshopMethod
would return a 415 “NOT_IMPLEMENTED”.
All the endpoint logic is saved in the PetShopApi interface, we only need to do the implementation.
Unit test
We can finally test that everything works:
@Test
fun postExamplePetMvc() {
mockMvc.perform(post("/shop").content(petPayload).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string(petPayload))
}
Using @AutoConfigureMockMvc
with our @SpringBootTest
we can autowire the MockMvc
to make the call to our spring
api. Then check that the status, contentType and response is what is expected.
The petPayload
is a string of the json representation of the Pet
object.