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

标签 java rest enums jax-rs swagger

我想从具有外部枚举定义的 JAX-RS 端点生成 swagger,但是生成的 swagger 直接将枚举包含到模型的定义中。这意味着没有生成枚举文档,而且在客户端复制了相同的枚举。

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

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
}

生成的 swagger 的摘录

{
  (...)
  },
  "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"
        }
      }
    }
  }
}

我做错了什么还是 swagger-jaxrs 库的“功能”?

谢谢你的帮助

最佳答案

Am I doing something wrong or is it a 'feature' of the swagger-jaxrs library ?

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

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

但在我看来,在 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();
    

希望这有帮助。

关于java - 使用外部枚举定义从 JAX-RS 端点生成 Swagger,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42157941/

相关文章:

c# - 如何将具有自定义值的枚举用于 Sharepoint Web 部件?

swift - 通过枚举显示多个 UICollectionViewCells?

java - 在 java 中读取和写入 xml

java - 按位置或索引从 Android 中的 HashMap 获取键

rest - http post - 如何发送授权 header ?

仅使用 WEB API key 的 Firebase Cloud Firestore REST api 身份验证?

java - BufferedReader 值到 char 数组

java - 构造函数中的缝焊注入(inject) : Unable to resolve any beans for Types

javascript - 在一个函数中运行 2 个查询的正确 RESTful API 实践?

java - 如何为 ButtonGroup setSelected(其中所有 JRadioButton 都是在循环中创建的)?