使用外部枚举定义从JAX-RS端点生成Swagger

时间:2017-02-10 11:10:15

标签: java rest enums jax-rs swagger

我想使用外部枚举定义从JAX-RS端点生成一个招摇,但生成的swagger直接包含枚举到模型的定义中。这意味着不会生成枚举文档,而是在客户端复制相同的枚举。

我使用swagger-jaxrs依赖项来扫描我的端点并生成swagger json文件。这个GitHub repository可用于重现问题。我还在swagger-core存储库上创建了一个GitHub issue

JAX-RS端点

@Api("hello")
@Path("/helloSwagger")
public class HelloSwagger {

    @ApiOperation(value = "Get all unique customers", notes = "Get all customers matching the given search string.", responseContainer = "Set", response = User.class)
    @GET
    @Path("/getUniqueUsers")
    @Produces(MediaType.APPLICATION_JSON)
    public Set<User> getUniqueUsers(
            @ApiParam(value = "The search string is used to find customer by their name. Not case sensitive.") @QueryParam("search") String searchString,
            @ApiParam(value = "Limits the size of the result set", defaultValue = "50") @QueryParam("limit") int limit
    ) {
        return new HashSet<>(Arrays.asList(new User(), new User()));
    }

}

具有枚举的模型

public class User {

    private String name = "unknown";
    private SynchronizationStatus ldap1 = SynchronizationStatus.UNKNOWN;
    private SynchronizationStatus ldap2 = SynchronizationStatus.OFFLINE;

    @ApiModelProperty(value = "The user name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ApiModelProperty(value = "The synchronization status with the LDAP1")
    public SynchronizationStatus getLdap1() {
        return ldap1;
    }

    public void setLdap1(SynchronizationStatus ldap1) {
        this.ldap1 = ldap1;
    }

    public SynchronizationStatus getLdap2() {
        return ldap2;
    }

    public void setLdap2(SynchronizationStatus ldap2) {
        this.ldap2 = ldap2;
    }
}

@ApiModel("The synchronization status with LDAP instance.")
public enum SynchronizationStatus {

    UNKNOWN,
    SYNC,
    OFFLINE,
    CONFLICT
}

生成的招摇的摘录

{
  (...)
  },
  "definitions" : {
    "User" : {
      "type" : "object",
      "properties" : {
        "name" : {
          "type" : "string",
          "description" : "The user name"
        },
        "ldap1" : {
          "type" : "string",
          "description" : "The synchronization status with the LDAP1",
          "enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ]
        },
        "ldap2" : {
          "type" : "string",
          "enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ]
        }
      }
    }
  }
}

预期结果

{
  (...)
  "definitions" : {
    "SynchronizationStatus" : {
      "description" : "The synchronization status with LDAP instance.",
      "enum" : [ "UNKNOWN", "SYNC", "OFFLINE", "CONFLICT" ],
      "type" : "string"
    },
    "User" : {
      "type" : "object",
      "properties" : {
        "name" : {
          "type" : "string",
          "description" : "The user name"
        },
        "ldap1" : {
          "$ref" : "#/definitions/SynchronizationStatus"
        },
        "ldap2" : {
          "$ref" : "#/definitions/SynchronizationStatus"
        }
      }
    }
  }
}

我做错了什么或者它是一个&#39;功能&#39; swagger-jaxrs库?

感谢您的帮助

4 个答案:

答案 0 :(得分:3)

您可以尝试reference注释的@ApiModelProperty属性:

@ApiModelProperty(reference = "#/definitions/SynchronizationStatus")
public SynchronizationStatus getLdap1() {
    return ldap1;
}

答案 1 :(得分:3)

  

我做错了什么,或者它是摇摇欲坠的jaxrs的'特征'   图书馆?

Enum值被swagger视为原始值类型,而swagger开箱即用不会为枚举类型生成模型定义(see code第209行)。所以这是一个功能,与swagger-jaxrs无关。

但是,您可以通过提供自定义模型转换器(io.swagger.converter.ModelConverter)按照您的预期生成swagger定义。

但在我看来,这是一个很好的功能,可以开箱即用。

以下是一个ruff实现,它可以帮助您生成预期的swagger定义。

package nhenneaux.test.swagger.ext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;

import com.fasterxml.jackson.databind.JavaType;

import io.swagger.annotations.ApiModel;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.jackson.ModelResolver;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import io.swagger.util.Json;

public class EnumAsModelAwareResolver extends ModelResolver {
    static final EnumAsModelAwareResolver INSTANCE = new EnumAsModelAwareResolver();

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

    @Override
    public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations,
            Iterator<ModelConverter> chain) {
        if (isEnumAnApiModel(type)) {
            String name = findName(type);
            // ask context to resolver enum type (for adding model definition
            // for enum under definitions section
            context.resolve(type);

            return new RefProperty(name);
        }
        return chain.next().resolveProperty(type, context, annotations, chain);
    }

    private String findName(Type type) {
        JavaType javaType = _mapper.constructType(type);
        Class<?> rawClass = javaType.getRawClass();
        ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
        String name = annotation.value();
        if (name == null || name.length() == 0) {
            name = rawClass.getSimpleName();
        }
        return name;
    }

    private boolean isEnumAnApiModel(Type type) {
        JavaType javaType = _mapper.constructType(type);
        return javaType.isEnumType()
                && javaType.getRawClass().isAnnotationPresent(ApiModel.class);
    }

    @Override
    public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        JavaType javaType = Json.mapper().constructType(type);
        if (javaType.isEnumType()) {
            ModelImpl model = new ModelImpl();
            Class<?> rawClass = javaType.getRawClass();
            ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
            String name = annotation.value();
            if (name == null || name.length() == 0) {
                name = rawClass.getSimpleName();
            }
            model.setName(name);
            model.setDescription(annotation.description());
            model.setType(StringProperty.TYPE);

            List<String> constants = findEnumConstants(rawClass);
            model.setEnum(constants);
            return model;
        }
        return chain.next().resolve(type, context, chain);
    }

    private List<String> findEnumConstants(Class<?> rawClass) {
        StringProperty p = new StringProperty();
        _addEnumProps(rawClass, p);
        return p.getEnum();
    }

}

package nhenneaux.test.swagger.ext;

import io.swagger.converter.ModelConverters;
import io.swagger.jaxrs.config.BeanConfig;
import nhenneaux.test.swagger.ext.EnumAsModelAwareResolver;

public class EnumModelAwareBeanConfig extends BeanConfig {
    public EnumModelAwareBeanConfig() {
        registerResolver();
    }

    private void registerResolver() {
        ModelConverters modelConverters = ModelConverters.getInstance();
        // remove and add; in case it is called multiple times.
        // should find a better way to register this.
        modelConverters.removeConverter(EnumAsModelAwareResolver.INSTANCE);
        modelConverters.addConverter(EnumAsModelAwareResolver.INSTANCE);
    }

}

在您的测试中使用:

final BeanConfig beanConfig = new nhenneaux.test.endpoint.model.EnumModelAwareBeanConfig();

跳这有帮助。

答案 2 :(得分:1)

基于去年的fiddle,我认为这不是微不足道的,可能需要扩展适当的Swagger资源。唯一的另一种选择是根据mailing list post手动引用模型(请注意,由于强制使用非生成的字符串,重命名SynchronizationStatus不会破坏API文档)

答案 3 :(得分:0)

我能够通过(使用 swagger-jaxrs-2.1.3)实现这一点

System.setProperty(ModelResolver.SET_PROPERTY_OF_ENUMS_AS_REF, "true");
Reader reader = new Reader();
OpenAPI api = reader.read(...);