kongchen / swagger-maven-plugin

JAX-RS & SpringMVC supported maven build plugin, helps you generate Swagger JSON and API document in build phase.
http://kongchen.github.io/swagger-maven-plugin/
Apache License 2.0
762 stars 451 forks source link

Plugin doesn't work with Spring Webflux #579

Open nefsir opened 6 years ago

nefsir commented 6 years ago

I've faced some problems with swagger.yaml generation while migrating to Spring Webflux. When response type is wrapped in Mono than swagger.yaml doesn't contain wrapped type information.

breun commented 5 years ago

This issue is not exclusive to WebFlux/Reactor types. You will also have this issue with other wrapper types, like Spring web's DeferredResult andResponseEntity. Or Callable, Future or CompletableFuture. Or RxJava's Single, Observable and Flowable.

You can fix this issue in your project using a ModelConverter like this (you can take out the entries for the classes you don't have in your project):

package your.nice.package;

import io.reactivex.Flowable;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.jackson.AbstractModelConverter;
import io.swagger.models.Model;
import io.swagger.models.properties.Property;
import io.swagger.util.Json;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
import rx.Single;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

public class UnwrappingModelConverter extends AbstractModelConverter {
    private static final Set<Type> classesToUnwrap;

    static {
        final Set<Type> set = new HashSet<>();

        // JDK
        set.add(Callable.class);
        set.add(CompletableFuture.class);
        set.add(Future.class);

        // Project Reactor
        set.add(Mono.class);
        set.add(Flux.class);

        // Spring web
        set.add(ResponseEntity.class);
        set.add(DeferredResult.class);

        // RxJava 1.x
        set.add(Single.class);
        set.add(Observable.class);

        // RxJava 2.x
        set.add(io.reactivex.Single.class);
        set.add(io.reactivex.Observable.class);
        set.add(Flowable.class);

        classesToUnwrap = set;
    }

    public UnwrappingModelConverter() {
        super(Json.mapper());
    }

    @Override
    public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) {
        return super.resolveProperty(unwrap(type), context, annotations, chain);
    }

    @Override
    public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        return super.resolve(unwrap(type), context, chain);
    }

    private Type unwrap(Type type) {
        if (type instanceof ParameterizedType) {
            final ParameterizedType parameterizedType = (ParameterizedType) type;

            // Recursively unwrap
            if (classesToUnwrap.contains(parameterizedType.getRawType())) {
                return unwrap(parameterizedType.getActualTypeArguments()[0]);
            }
        }

        return type;
    }
}

Enable your model converter by creating src/main/resources/META-INF/services/io.swagger.converter.ModelConverter and putting the fully qualified class name of your model converter in it.

wllianwd commented 2 years ago

I was testing this converter above and don't work. Basically, even if the response is Mono or Flux, the Type is never a ParameterizedType, all of the, are SimpleType. If I print the Type it looks like the correct one.

For instance for a return type Mono<Something> I have the bellow:

  "definitions" : {
    "Mono" : {
      "type" : "object"
    },
    "MonoSomething" : {
      "type" : "object"
    }
  }

Any idea?