Создание Swagger из конечной точки JAX-RS с внешним определением перечисления

Я хочу создать swagger из конечной точки JAX-RS с внешним определением перечисления, однако сгенерированный swagger напрямую включает перечисление в определение модели. Это означает, что документация перечисления не создается, но и что то же самое перечисление дублируется на стороне клиента.

Я использую swagger-jaxrs зависимость для сканирования моей конечной точки и создания файла JSON swagger. Это GitHub хранилище смогите быть использовано к воспроизвести проблему. Я также создал GitHub вопрос в репозитории 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
}

экстракт чванства генерируется

{
  (...)
  },
  "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 библиотеки ?

Спасибо за помощь

3 ответов


Я делаю что-то неправильно или это "особенность" swagger-jaxrs библиотека ?

значение перечисления рассматривается как тип примитивного значения по swagger и swagger из коробки не генерирует определение модели для типа перечисления (код строка 209 в). Таким образом, это функция и не связана с swagger-jaxrs.

тем не менее, вы можете создать определение swagger в соответствии с вашими ожиданиями, предоставив пользовательскую модель конвертер(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();

хмель это помогает.


вы можете попробовать reference атрибут @ApiModelProperty аннотация:

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

основываясь на этом сообщении С прошлого года я считаю, что это не тривиально, и, возможно, придется расширить соответствующие ресурсы Swagger. Единственным другим вариантом было бы вручную ссылаться на модель согласно ответ Cássio Mazzochi Molin (просто будьте осторожны, чтобы переименование SynchronizationStatus не нарушало документы API из-за принудительного использования не сгенерированной строки)