6

When I update the swagger to swagger2 to with my spring boot it stopped showing correct parameters for pageable type when it should show page and size instead it started showing pageSize and pageNumber which is not correct in the rest side.

enter image description here

I did not change anything manually but for some reason, it is showing the wrong parameter name.

 return new Docket(DocumentationType.SWAGGER_2)
            .groupName("Rest API")
            .securitySchemes(Collections.singletonList(new BasicAuth(BASIC_AUTH)))
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .paths(s -> oneOf(
                "/some/**",
                "/search-controller/**").test(s))
            .build();

And the pom is

<dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-data-rest</artifactId>
      <version>2.9.0</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.0</version>
    </dependency>

And the controller is something like below

@RequestMapping(method = RequestMethod.GET)
    public HttpEntity<?> findAll(@RequestParam(value = "countryIsoAlpha2", required = false) final String countryKey,  final Pageable pageable){

}
Abdullah Al Noman
  • 2,817
  • 4
  • 20
  • 35

4 Answers4

10

https://github.com/springfox/springfox/issues/755#issuecomment-393378205

Below is an example for creating a rule that automatically provides a convention for configuring Pageable type.

@Configuration
public class SwaggerConfig {

    @Bean
    public AlternateTypeRuleConvention pageableConvention(
            final TypeResolver resolver) {
        return new AlternateTypeRuleConvention() {

            @Override
            public int getOrder() {
                return Ordered.HIGHEST_PRECEDENCE;
            }

            @Override
            public List<AlternateTypeRule> rules() {
                return Arrays.asList(
                        newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin()))
                );
            }
        };
    }

    private Type pageableMixin() {
        return new AlternateTypeBuilder()
                .fullyQualifiedClassName(
                        String.format("%s.generated.%s",
                                Pageable.class.getPackage().getName(),
                                Pageable.class.getSimpleName()))
                .withProperties(Arrays.asList(
                        property(Integer.class, "page"),
                        property(Integer.class, "size"),
                        property(String.class, "sort")
                ))
                .build();
    }

    private AlternateTypePropertyBuilder property(Class<?> type, String name) {
        return new AlternateTypePropertyBuilder()
                .withName(name)
                .withType(type)
                .withCanRead(true)
                .withCanWrite(true);
    }
}
meng lingjie
  • 101
  • 1
  • 4
  • It worked, thank you! I just need to change "newRule" to "new AlternateTypeRule" (is a constructor) in rules() method. – Crozeta May 07 '19 at 15:15
  • I saw a lot of huggly implementations.. for me this is a nice solution, thanks! – Jose Pose S Jun 29 '19 at 13:54
  • This worked for me, but in order for it to support multiple criteria sort, I had to replace `String.class, "sort"` with `String[].class, "sort"`. – Hassan Jun 19 '20 at 14:42
3

Here's my expansion on the previous two examples. It also includes ApiParam annotation values and is configurable through SpringDataWebProperties settings.

Before Field Expansion: Before Field Expansion

After 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());
    }
}
rvertigo
  • 275
  • 2
  • 6
1

I am seeing same behavior after upgrading to springfox 2.9.0 without changing any code. It appears that springfox is adding pageSize, pageNumber and offset to the Swagger docs when it encounters the Pageable interface as a request parameter in the Spring controller method.

0

Below is the my Swagger config class and it is working fine.

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.archisoft.mtx.controller"))
                .paths(regex("/api.*"))
                .build()
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        ApiInfo apiInfo = new ApiInfo(
                "Backend API Services",
                "Backend APIs for Business to Business",
                "V1.0",
                "Terms of service",
                "Sadun | Tharanga email",
                "Archisoft Global",
                "Archisoft URL");
        return apiInfo;
    }

    @Bean
    public AlternateTypeRuleConvention pageableConvention(
            final TypeResolver resolver,
            final RepositoryRestConfiguration restConfiguration) {
        return new AlternateTypeRuleConvention() {

            @Override
            public int getOrder() {
                return Ordered.HIGHEST_PRECEDENCE;
            }

            @Override
            public List<AlternateTypeRule> rules() {
                return singletonList(
                        newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin()))
                );
            }
        };
    }

    private Type pageableMixin() {
        return new AlternateTypeBuilder()
                .fullyQualifiedClassName(
                        String.format("%s.generated.%s",
                                Pageable.class.getPackage().getName(),
                                Pageable.class.getSimpleName()))
                .withProperties(Stream.of(
                        property(Integer.class, "page"),
                        property(Integer.class, "size"),
                        property(String.class, "sort")
                ).collect(toList()))
                .build();
    }

    private AlternateTypePropertyBuilder property(Class<?> type, String name) {
        return new AlternateTypePropertyBuilder()
                .withName(name)
                .withType(type)
                .withCanRead(true)
                .withCanWrite(true);
    }
}
WGSSAMINTHA
  • 180
  • 1
  • 11