Search code examples
spring-bootswaggerspring-webfluxswagger-uispringfox

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).


Solution

  • 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.