Here's my expansion on the previous two examples. It also includes ApiParam annotation values and is configurable through SpringDataWebProperties settings.
Before Field Expansion:
After Field Expansion:
There are several ways to use Swagger AlternateTypeRules. They can be added directly to the Swagger Docket, added as a bean in several ways. Inspired by the above examples, here's how I did it, using an Annotation Proxy (show below) to include the additional ApiParam data:
@EnableSwagger2
@Configuration
public class SwaggerConfiguration {
@Bean
public AlternateTypeRuleConvention springDataWebPropertiesConvention(final SpringDataWebProperties webProperties) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; }
@Override
public List<AlternateTypeRule> rules() {
return singletonList(
newRule(Pageable.class, pageableDocumentedType(webProperties.getPageable(), webProperties.getSort()))
);
}
};
}
private Type pageableDocumentedType(SpringDataWebProperties.Pageable pageable, SpringDataWebProperties.Sort sort) {
final String firstPage = pageable.isOneIndexedParameters() ? "1" : "0";
return new AlternateTypeBuilder()
.fullyQualifiedClassName(fullyQualifiedName(Pageable.class))
.property(property(pageable.getPageParameter(), Integer.class, ImmutableMap.of(
"value", "Page " + (pageable.isOneIndexedParameters() ? "Number" : "Index"),
"defaultValue", firstPage,
"allowableValues", String.format("range[%s, %s]", firstPage, Integer.MAX_VALUE),
"example", firstPage
)))
.property(property(pageable.getSizeParameter(), Integer.class, ImmutableMap.of(
"value", "Page Size",
"defaultValue", String.valueOf(pageable.getDefaultPageSize()),
"allowableValues", String.format("range[1, %s]", pageable.getMaxPageSize()),
"example", "5"
)))
.property(property(sort.getSortParameter(), String[].class, ImmutableMap.of(
"value", "Page Multi-Sort: fieldName,(asc|desc)"
)))
.build();
}
private String fullyQualifiedName(Class<?> convertedClass) {
return String.format("%s.generated.%s", convertedClass.getPackage().getName(), convertedClass.getSimpleName());
}
private AlternateTypePropertyBuilder property(String name, Class<?> type, Map<String, Object> parameters) {
return new AlternateTypePropertyBuilder()
.withName(name)
.withType(type)
.withCanRead(true)
.withCanWrite(true)
.withAnnotations(Collections.singletonList(AnnotationProxy.of(ApiParam.class, parameters)));
}
}
In order to add the additional data like defaultValue and allowableValues, you have to use the .withAnnotations()
method, which requires an Annotation Proxy. There are several available, here's mine (using lombok):
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Accessors(fluent = true)
public class AnnotationProxy implements Annotation, InvocationHandler {
@Getter
private final Class<? extends Annotation> annotationType;
private final Map<String, Object> values;
public static <A extends Annotation> A of(Class<A> annotation, Map<String, Object> values) {
return (A) Proxy.newProxyInstance(annotation.getClassLoader(),
new Class[]{annotation},
new AnnotationProxy(annotation, new HashMap<String, Object>(values) {{
put("annotationType", annotation); // Required because getDefaultValue() returns null for this call
}}));
}
public Object invoke(Object proxy, Method method, Object[] args) {
return values.getOrDefault(method.getName(), method.getDefaultValue());
}
}