fbpx

How To Write Swagger API Annotations With Less Lines Of Code…

Recently, at Stepwise, I worked on an interesting, extremely business-oriented project. One of the business requirements for the application was to not only deliver the code according to the documentation but also supply it with full Swagger annotations. Because of how complex the logic was, it involved a lot of Swagger API annotations and it quickly became really bothersome. We came up with a solution to declare the annotations with fewer lines of code and I think it may come in handy for some people, so here, I’m going to share our solution.

A Quick Note On Swagger

Swagger Core is a Java implementation of the OpenAPI Specification. Current version supports JAX-RS2. If you do not have previous experience with Swagger, I encourage you to check out a great article by Ramesh Fadatare on DZone, where he goes through the basics of Swagger 2, which can shed some light on how it works.

The Problem We Encountered

At first, working with Swagger and using the standard way to declare annotations was not a problem for us, but due to the business requirements and app’s complexity, it soon became quite bothersome when we noticed the amount of code we needed to declare even the simplest methods, as shown by an example below:

@GET
@Operation(
        summary = "Get Bank Accounts for user",
        description = "REST Endpoint that returns bank accounts of a certain user",
        responses = [
            ApiResponse(
                    responseCode = "200",
                    description = "Returns Bank Accounts"
                            headers = [
                            Header(name = "Privacy-Declaration"),
                            Header(name = "X-Request-ID"),
                            Header(name = "X-Correlation-ID"),
                            Header(name = "API-Version"),
                            Header(name = "Backend-Version")
                    ]
            )
        ],
        tags = ["system/v0"]
)
@Path("/bank-accounts")
fun getBankAccounts

Moreover, the lines of code needed for Swagger significantly increased total lines of code and affected readability of our endpoint classes. This, besides making our work harder and less efficient, might’ve caused a potential problem in the future, so we needed a way to fix this.

What We’ve Come Up With

When searching for a solution, we noticed that the only unique properties of each annotation were the summary, the description and the ApiResponse.description. And in many similar endpoints, it was declared in the same way, with a slight change in those three parameters. Due to the annotations’ construction constraints,you can’t just move the declaration to a method or another place. Fortunately, Swagger 2 Core supplies us with a great set of tools, one of them being OpenApiExtension class, which enables us to extend and register as well as “complete” our operation (I’m going to talk about this more later on). The solution for our case was to implement a custom annotation:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Operation
annotation class BankAccountFetchOperation(
        val summary: String,
        val description: String,
        val responseDescription: String
)

This way, the annotation only takes parameters that are needed for the full operation description and becomes significantly shorter. But, we’re still left to create the full operation description. How to do that? Keeping it short and sweet — the answer (at least in our case) is creating an OpenApiExtension class which completes every method annotated with an operation with a full description and API responses:

@Component
class BankAccountOperationAPIExtension : OpenAPIExtension {
    @PostConstruct
    fun registerSelfToSwaggerModelConverters() {
        OpenAPIExtensions.getExtensions()
                .add(DefaultEndpointServiceAPIExtension())
    }
    override fun extractOperationMethod
            (method: Method?, chain: MutableIterator?): String? =
            chain?.next()?.extractOperationMethod(method, chain)
    override fun decorateOperation(operation: Operation, method: Method, chain: MutableIterator?) {
        val annotationList = method.annotations as Array
        annotationList.forEach { annotation ->
            when (annotation) {
                is BankAccountOperation -> fillBankAccountOperationInformation(operation, annotation)
            }
        }
    }
    private fun fillBankAccountOperationInformation(operation: Operation, annotation: BankAccountOperation) {
        operation.summary = annotation.summary
        operation.description = annotation.description
        operation.responses?.addApiResponse(
                HTTP_OK_STATUS_CODE,
                ApiResponse().apply {
                    description = annotation.responseDescription
                    headers = headerMap
                }
        )
    }
}

I’ve simplified this example to make it easier to go through — extending OpenApiExtensions requires overriding function extractParameters — but the full code can be found on my GitHub.

The Bottom Line: How It Works?

Now, to the most important thing, here’s how the solution works: the extension class gets invoked on every method, once after application startup — on first Swagger documentation creation, usually when invoking a Swagger endpoint. The extension class searches for @BankAccountOperation annotation, and if one is found, fills the @Operation annotation with information from @BankAccountOperation. And then, you get a nice and short declaration like this:

@GET
@BankAccountOperations(
        summary = "Get Bank Accounts for user",
        description = "REST-service that returns user's Bank Accounts",
        responseDescription = "Returns bank accounts"
)
@Path("/bank-accounts")
fun getBankAccounts

Of course this example barely scratches the surface of how many personalization possibilities Swagger offers, but I’m hoping that it at least gives you an insight on how to simplify swagger annotation declaration by moving the most out of the boilerplate to an OpenApiExtension class.

If you liked this article and want to dive deeper into this topic, you can visit Swagger Core Documentation about extensions (especially get to know Model Converters, they are very useful tools)