I want to generate a swagger from a JAX-RS endpoint with an external enumeration definition however the generated swagger directly includes the enumeration into the definition of the model. It implies that the enumeration documentation is not generated but also that the same enumeration is duplicated on the client side.
I use the swagger-jaxrs
dependency to scan my endpoint and generate the swagger json file. This GitHub repository can be used to reproduce the problem. I also have created a GitHub issue on the swagger-core repository.
@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"
}
}
}
}
}
Am I doing something wrong or is it a 'feature' of the swagger-jaxrs
library ?
Thanks for your help
Am I doing something wrong or is it a 'feature' of the swagger-jaxrs library ?
Enum value are treat as primitive value type by swagger and swagger out-of-the-box does not generate model definition for enum type (see code line 209 under). So the is a feature and not related with swagger-jaxrs.
However, you can generate the swagger definition, as per your expectation, by providing the custom model converter(io.swagger.converter.ModelConverter
).
But it seems to me a nice feature to be available in swagger out-of-the-box.
Following is a ruff implementation which can help you to generate the expected swagger definition.
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);
}
}
In your test use:
final BeanConfig beanConfig = new nhenneaux.test.endpoint.model.EnumModelAwareBeanConfig();
Hops this helps.
You could try the reference
attribute of the @ApiModelProperty
annotation:
@ApiModelProperty(reference = "#/definitions/SynchronizationStatus")
public SynchronizationStatus getLdap1() {
return ldap1;
}
Based on this mailing list post from last year I believe it is not trivial and one may have to extend the appropriate Swagger resources. The only other option would be to manually reference the model as per Cássio Mazzochi Molin's answer (just be careful that renaming SynchronizationStatus doesn't break the API docs due to the forced use of a non-generated string)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With