3

I'm trying to figure out how manage two (or more) version of my API endpoints using Spring Fox.

To version my APIs, I'm using the Versioning through content negotiation, also know as Versioning using Accept header. The versions of each endpoint are controlled individually using the header information. Per example, for the version one I use the attribute produces:

@Override
@PostMapping(
        produces = "application/vnd.company.v1+json")
public ResponseEntity<User> createUser(

For version two, I use:

@Override
@PostMapping(
        produces = "application/vnd.company.v2+json",
        consumes = "application/vnd.company.v2+json")
public ResponseEntity<User> createUserVersion2(

I not use consumes for the first (v1) version, so if the client use only application/json on the call the first version will be called by default.

I would like to show the two version on the Swagger UI. How to do that?

Dherik
  • 17,757
  • 11
  • 115
  • 164

2 Answers2

8

It's very simple. Just create one Docket for each version.

Example, the first version:

@Bean
public Docket customImplementation(
        @Value("${springfox.documentation.info.title}") String title,
        @Value("${springfox.documentation.info.description}") String description) {

    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo(title, description, "1.0"))
            .groupName("v1")
            .useDefaultResponseMessages(false)
            .securitySchemes(newArrayList(apiKey()))
            .pathMapping("/api")
            .securityContexts(newArrayList(securityContext())).select()
            .apis(e -> Objects.requireNonNull(e).produces().parallelStream()
                    .anyMatch(p -> "application/vnd.company.v1+json".equals(p.toString())))
            .paths(PathSelectors.any())
            .build();
}

And for version two:

@Bean
public Docket customImplementationV2(
        @Value("${springfox.documentation.info.title}") String title,
        @Value("${springfox.documentation.info.description}") String description) {

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(title, description, "2.0"))
                .groupName("v2")
                .select()
                .apis(e -> Objects.requireNonNull(e).produces()
                        .parallelStream()
                        .anyMatch(p -> "application/vnd.company.v2+json".equals(p.toString())))
                .build();
}

The secret here is filter the available endpoints by the produces attribute.

The Swagger-UI will show the two versions on the combo:

enter image description here

This code needs to be on a class annotated with @Configuration. You also need to enable the Swagger with @EnableSwagger2.

Dherik
  • 17,757
  • 11
  • 115
  • 164
  • Can't find selector from last picture in swagger-ui. @Dherik, can you say the URL to such form and swagger-ui version? – WeGa Jul 14 '21 at 06:20
  • @WeGa, I can't really remember. But probably was the latest version of `springfox-swagger` lib at 04/2018 and the URL probably was: http://{{your-api-address}}/swagger.json – Dherik Jul 14 '21 at 11:47
  • I made exactly identical API definitions by declaring Dockets like your example, but I still get the V1 endpoints in both V1 and V2 documentations. Did you also have this issue at some point? If so, how did you solve it? Thanks. – Cécile Fecherolle Feb 16 '22 at 11:23
1

As mentioned by Dherik you can create Docket for each version. But to filter here I have tried using Predicate and custom controller annotations.

  1. Configuration class annotated with @Configuration and @EnableSwagger2

     import com.google.common.base.Predicate;
    
     @Bean
     public Docket apiV30() {
         return new Docket(DocumentationType.SWAGGER_2)
             .groupName("v30")
             .select()
             .apis(selectorV30())
             .paths(PathSelectors.any()).build().apiInfo(apiEndPointsInfo());
     }
    
     private Predicate<RequestHandler> selectorV30(){
         return new Predicate<RequestHandler>() {
             @Override
             public boolean apply(RequestHandler input) {
                 return input.findControllerAnnotation(SwaggerDocV30.class).isPresent();
             }
         };
     }
    
     @Bean
     public Docket apiV31() {
         return new Docket(DocumentationType.SWAGGER_2)
             .groupName("v31")
             .select()
             .apis(selectorV31())
             .paths(PathSelectors.any()).build().apiInfo(apiEndPointsInfo());
     }
    
     private Predicate<RequestHandler> selectorV31(){
         return new Predicate<RequestHandler>() {
             @Override
             public boolean apply(RequestHandler input) {
                 return input.findControllerAnnotation(SwaggerDocV31.class).isPresent();
             }
         };
     }
    
  2. Custom Annotation class : SwaggerDocV30

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public  @interface SwaggerDocV30 {
    }
    
  3. Custom Annotation class : SwaggerDocV31

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public  @interface SwaggerDocV31 {
    }
    
  4. Finally annotate your controllers with @SwaggerDocV30 or @SwaggerDocV31

     @SwaggerDocV30
     @Controller
     public class MyController extends AbstractController {}
    

    Or

     @SwaggerDocV31
     @Controller
     public class MyController extends AbstractController {}]
    

    image

Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
Manish
  • 11
  • 1