question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Generate Swagger from JAX-RS endpoint with external enum definition

See original GitHub issue

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.

The JAX-RS endpoint

@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()));
    }

}

The model with the enumeration

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
}

An extract of the swagger generated

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

Expected result

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

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:4
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
InterNetX-DLcommented, Jul 18, 2018

Is this still a thing ?

I found this in the source code (ModelResolver.java) of 1.5.21-SNAPSHOT. It seems that it is not intendet to create models for enums.

if(type.isEnumType() || PrimitiveType.fromType(type) != null) {
    // We don't build models for primitive types
    return null;
}
0reactions
nhenneauxcommented, May 9, 2019

A solution was proposed on Stack Overflow https://stackoverflow.com/a/42311837/1630604. I use the following class based on this solution. It also includes a fix for minPropertiesand maxProperties inside maps. Description using the value when empty and ignore of Jersey classes inside the model.

public class EnumAsModelAwareResolver extends ModelResolver {

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

    @Override
    public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) {
        if (this.shouldIgnoreClass(type)) {
            return null;
        }
        final JavaType javaType = _mapper.constructType(type);
        if (javaType.isEnumType()) {
            String reference = findReference(javaType);
            // ask context to resolver enum type (for adding model definition for enum under definitions section)
            context.resolve(type);
            return new RefProperty(reference);
        }
        if (javaType.isMapLikeType() && !Map.class.equals(javaType.getRawClass())) {
            String reference = findReference(javaType);
            // ask context to resolver map type (for adding model definition for map under definitions section)
            context.resolve(type);
            return new RefProperty(reference);
        }
        final Property property = chain.next().resolveProperty(type, context, annotations, chain);

        if (property instanceof MapProperty) {
            final MapProperty mapProperty = (MapProperty) property;
            Optional.ofNullable(annotations)
                    .map(Arrays::stream)
                    .orElseGet(Stream::empty)
                    .filter(annotation -> Size.class.equals(annotation.annotationType()))
                    .map(annotation -> (Size) annotation)
                    .forEach(size -> {
                        if (size.min() > 0) {
                            mapProperty.setMinProperties(size.min());
                        }
                        mapProperty.setMaxProperties(size.max());
                    });
        }

        return property;
    }

    private String findReference(JavaType javaType) {
        Class<?> rawClass = javaType.getRawClass();

        if (!rawClass.isAnnotationPresent(ApiModel.class)) {
            return rawClass.getSimpleName();
        }
        ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
        final String reference = annotation.reference();
        if (reference.trim().length() == 0) {
            return rawClass.getSimpleName();
        }
        return reference;
    }

    @Override
    protected boolean shouldIgnoreClass(Type type) {
        if (type instanceof SimpleType) {
            final SimpleType simpleType = (SimpleType) type;
            if (simpleType.getRawClass().getName().startsWith("org.glassfish.jersey"))
                return true;
        }
        return super.shouldIgnoreClass(type);
    }

    @Override
    public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        if (this.shouldIgnoreClass(type)) {
            return null;
        }
        JavaType javaType = Json.mapper().constructType(type);
        if (javaType.isEnumType()) {
            Class<?> rawClass = javaType.getRawClass();

            return new ModelImpl()
                    .type(StringProperty.TYPE)
                    ._enum(findEnumConstants(rawClass))
                    .name(findReference(javaType))
                    .description(getDescription(rawClass));
        }
        if (javaType.isMapLikeType() && javaType.getRawClass() != null && javaType.getRawClass().getGenericSuperclass() != null) {
            Class<?> rawClass = javaType.getRawClass();
            final Type mapType = ((ParameterizedType) rawClass.getGenericSuperclass()).getActualTypeArguments()[1];
            Property primitiveProperty = PrimitiveType.createProperty(mapType);
            final ModelImpl model = new ModelImpl()
                    .type("object")
                    .name(findReference(javaType))
                    .description(getDescription(rawClass));
            if (primitiveProperty != null) {
                return model
                        .additionalProperties(primitiveProperty)
                        ;
            }
            return model
                    .additionalProperties(context.resolveProperty(this.getInnerType(mapType.getTypeName()), new Annotation[0]))
                    ;
        }
        return chain.next().resolve(type, context, chain);
    }

    private String getDescription(Class<?> rawClass) {
        ApiModel annotation = rawClass.getAnnotation(ApiModel.class);
        if (annotation == null) {
            return null;
        }
        if (annotation.description().trim().length() == 0) {
            return annotation.value();
        }
        return annotation.description();
    }

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

}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Generate Swagger from JAX-RS endpoint with external enum ...
Show activity on this post. I want to generate a swagger from a JAX-RS endpoint with an external enumeration definition however the generated...
Read more >
Document Enum in Swagger - Baeldung
Learn how to document enum in Swagger using the Maven plugin and verify the generated JSON document in the Swagger editor.
Read more >
Enforce REST API Standard with Swagger — Engineering Blog
Swagger Codegen parses an OpenAPI/Swagger Definition defined in JSON/YAML and generates code using Mustache templates.
Read more >
Apache CXF -- JAXRS Services Description
External WADL documents and JAXRS endpoints. WADL Auto Generation at Runtime. Documenting resource classes and methods in generated WADL. Support for Javadoc.
Read more >
38.4. Customizing Enumeration Mapping Red Hat JBoss Fuse ...
name, Specifies the name of the generated Java enum type. This value must be a valid Java identifier. ; map, Specifies if the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found