Externalize Springdoc OpenAPI schema (write documentation outside of target classes)

Sometimes you might not be able to add @Schema annotations to a class you are using in your public API (e.g. when the class is coming from an external dependency). Other times you might not want to (e.g. when the class is a value object from your domain). In this article you will learn how to define OpenAPI 3.0 class schema separately from your model, without having to touch the class you are documenting.

Option 1

One possible solution is to use springdoc-openapi-externalized-documentation, a library I wrote for externalizing @Schema annotations. To use it, declare a dependency in your POM:

<!-- https://mvnrepository.com/artifact/com.danielfrak.code/springdoc-openapi-externalized-documentation -->
<dependency>
    <groupId>com.danielfrak.code</groupId>
    <artifactId>springdoc-openapi-externalized-documentation</artifactId>
    <version>1.0.0</version>
</dependency>

Now you can add an @ExernalizedSchema annotation on any class. I recommend you create a new empty class for every class whose schema you want to externally define:

import com.danielfrak.code.springdoc.openapi.externalizeddocs.ExternalizedSchema;
import io.swagger.v3.oas.annotations.media.Schema;
import pl.nc.datapresenter.data.articles.domain.ArticleType;

@ExternalizedSchema(source = MyValueObject.class, schema = @Schema(
    implementation = String.class,
    example = "Example value",
    description = "Some description"
))
public class MyValueObjectDocs {
}

In the example above, I have defined an external schema for MyValueObject, which should be treated as a String in the OpenAPI spec. Additionally, the defined example and description for it will be included in that spec. The result will be the same as if I added the @Schema annotation on MyValueObject itself.

Option 2

If you prefer not to use annotations, another option is to use my springdoc-openapi-programmatic-documentation, which I wrote to allow for programmatic declaration of the schema:

<!-- https://mvnrepository.com/artifact/com.danielfrak.code/springdoc-openapi-programmatic-documentation -->
<dependency>
  <groupId>com.danielfrak.code</groupId>
  <artifactId>springdoc-openapi-programmatic-documentation</artifactId>
  <version>1.0.1</version>
</dependency>

The schema building here is done strictly with code:

import io.swagger.v3.oas.models.media.Schema;
import org.springframework.context.annotation.Configuration;
import com.danielfrak.code.config.openapi.ModelDocumentation;
import com.danielfrak.code.config.openapi.ModelDocumentation.Model;
import com.danielfrak.code.model.MyValueObject;

@Configuration
public class OpenApiConfig {

    public OpenApiConfig(ModelDocumentation modelDocumentation) {
        modelDocumentation
                .add(new DocumentedModel()
                        .source(MyValueObject.class)
                        .implementation(String.class)
                        .schema(new Schema<>()
                                .example("Example value")
                                .description("Base description")));
    }
}

The above will produce the same result as its “Option 1” counterpart. Furthermore, the add(…) methods can be chained to easily document more than one class at once.

Conclusion and further development

Both of these libraries should work great for basic use cases (such as adding documentation to value objects and enums), even though each one works a little bit differently under the hood.

If you find a bug or a missing feature that you need, feel free to file an issue on their respective Github pages. Better yet, I encourage you to file a Pull Request of your own! The libraries were born out of personal necessity and will hopefully evolve to be something useful to many others as well.

Daniel Frąk Written by:

3 Comments

  1. Jonathan Locke
    September 14, 2021
    Reply

    This looks useful. It’s a couple years later. Is there an official answer to this problem from swagger.io yet?

    Thanks,

    Jon

    • Daniel Frąk
      September 18, 2021
      Reply

      Thank you! As far as I’m aware, there’s still no way to do this via Swagger annotations or methods.

Leave a Reply

Your email address will not be published. Required fields are marked *