Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure springfox to unwrap reactive types such as Mono and Flux without having to explicitly specify response type in @ApiResponse

I'm using springfox 3.0.0 for reactive support, and am using @EnableSwagger2WebFlux on my swagger config.

My swagger config is as follows:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage(basePackage))
            .paths(PathSelectors.any())
            .build()
            .securityContexts(Lists.newArrayList(securityContext()))
            .securitySchemes(Lists.newArrayList(apiKey()))
            .globalOperationParameters(operationParameters());
}

I have a simple controller, as shown below:

  @CrossOrigin(origins = "*", allowedHeaders = "*", maxAge = 3600)
  @RestController
  @RequestMapping("/")
  public class ApiController {

    @ApiOperation(value = "get all partners", authorizations = {@Authorization(value = "Bearer")})
    @RequestMapping(value = "/partner",
            method = RequestMethod.GET,
            produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
    )
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Request succeeded")
    })
    public Mono<ResponseEntity<Flux<PartnerDTO>>> getAllPartners(
            @ApiIgnore ServerHttpRequest httpRequest
    ) {
        return ...
    }

When springfox generates the documentation, it has the following type: enter image description here

And this type is useless in my API operation: enter image description here

I know I can fix this by specifying the response type in @ApiOperation, but I'm trying to avoid that, e.g.

@ApiOperation(value = "get all partners", authorizations = {@Authorization(value = "Bearer")})
@RequestMapping(value = "/partner",
        method = RequestMethod.GET,
        produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
)
@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Request succeeded", response = PartnerDTO.class)
})
public Mono<ResponseEntity<Flux<PartnerDTO>>> getAllPartners(
        @ApiIgnore ServerHttpRequest httpRequest
) {

I don't like this approach as it's a manual process and and thus prone to errors. I'd like some automatic way to do the following conversion:

Flux<T> -> T[] (since flux emits 0...N elements)
Mono<T> -> T
ResponseEntity<T> -> T

And of course it would have to be recursive (e.g. Mono<ResponseEntity<Flux<T>>> -> T).

like image 980
adnan_e Avatar asked Aug 31 '19 09:08

adnan_e


People also ask

Is Springdoc better than Springfox?

springdoc is a much more recent library that does not have so much legacy code as Springfox. As a consequence of the first one, springdoc is actually updated regularly and the amount of open issues is relatively small (only 11 vs 270 on Springfox).

What is Springfox?

Springfox is a framework that acts as the “glue” between Swagger and Spring. It generates the specification (contract) based on your code and also deploys the Swagger UI client with your application, allowing you to immediately test your REST API.

How do I enable swagger in spring boot?

To enable the Swagger2 in Spring Boot application, you need to add the following dependencies in our build configurations file. For Gradle users, add the following dependencies in your build. gradle file. Now, add the @EnableSwagger2 annotation in your main Spring Boot application.


3 Answers

I went through the code of springfox trying to find some entry point for custom type resolving, and luckily it has a HandlerMethodResolver which can be injected externally.

I added a custom implementation of this resolver in my swagger config class:

@Bean
@Primary
public HandlerMethodResolver fluxMethodResolver(TypeResolver resolver) {
    return new HandlerMethodResolver(resolver) {
        @Override
        public ResolvedType methodReturnType(HandlerMethod handlerMethod) {
            var retType = super.methodReturnType(handlerMethod);

            // we unwrap Mono, Flux, and as a bonus - ResponseEntity
            while (
                    retType.getErasedType() == Mono.class
                    || retType.getErasedType() == Flux.class
                    || retType.getErasedType() == ResponseEntity.class
            ) {
                if ( retType.getErasedType() == Flux.class ) {
                    // treat it as an array
                    var type = retType.getTypeBindings().getBoundType(0);
                    retType = new ResolvedArrayType(type.getErasedType(), type.getTypeBindings(), type);
                } else {
                    retType = retType.getTypeBindings().getBoundType(0);
                }
            }

            return retType;
        }
    };
}

Which does exactly what I need.

enter image description here

It automatically converts Mono<ResponseEntity<Flux<PartnerDTO>>> to PartnerDTO[], and Mono<ResponseEntity<Mono<PartnerDTO>>> to PartnerDTO.

EDIT:: I changed this implementation to convert Flux to T[], as it should have been from the start.

like image 124
adnan_e Avatar answered Oct 18 '22 21:10

adnan_e


You can also follow Sprinfox example:

  • declare a RecursiveAlternateTypeRule.java
  • add it as alternateTypeRules in your Swagger Configuration

    @Configuration
    @EnableSwagger2WebFlux
    public abstract class AbstractSwaggerConfiguration {
    
    @Autowired
    private TypeResolver resolver;
    
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2) //
            .select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()) //
            .build()
            .alternateTypeRules(new RecursiveAlternateTypeRule(resolver,
                Arrays.asList(AlternateTypeRules.newRule(resolver.resolve(Mono.class, WildcardType.class), resolver.resolve(WildcardType.class)),
                    AlternateTypeRules.newRule(resolver.resolve(ResponseEntity.class, WildcardType.class), resolver.resolve(WildcardType.class)))))
            .alternateTypeRules(
                new RecursiveAlternateTypeRule(resolver, Arrays.asList(AlternateTypeRules.newRule(resolver.resolve(Flux.class, WildcardType.class), resolver.resolve(List.class, WildcardType.class)),
                    AlternateTypeRules.newRule(resolver.resolve(ResponseEntity.class, WildcardType.class), resolver.resolve(WildcardType.class)))));
        }
    }
    
like image 38
Mica Avatar answered Oct 18 '22 21:10

Mica


Springfox, as it seems, missed the boat. Use springdoc-openapi instead. We have had also other issues with springfox, not only the missing webflux support, and we happily switched to springdoc-openapi

For webflux applications, all you need is to add dependency springdoc-openapi-webflux-ui:

<dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-webflux-ui</artifactId>
      <version>1.6.9</version>
</dependency>

No additional configuration is needed, and the Mono and Flux will be unwrapped out-of-the-box.

There is also a manual how to migrate from springfox to springdoc-openapi.

If you need to programmatically customize the configuration, you define an OpenAPI bean like this:

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;

@Configuration
public class OpenApi30Config {
    @Bean
    public OpenAPI openApi() {
        return new OpenAPI()
           .info(new Info().title("My Application")
                .description("This is my application")
                .version("1.0")
            )
            // and lot of other configuration possible here
            ;
    }
}

like image 1
Honza Zidek Avatar answered Oct 18 '22 20:10

Honza Zidek