44

I have developed a microservice using Spring Boot. The documentation for the REST API is made with Swagger. Some REST resources make use of Spring concepts to provide pagination for free. Below is an example:

@RequestMapping(value = "/buckets", method = GET)
public PagedResources list(Pageable pageable, PagedResourcesAssembler assembler) {
    return bucketService.listBuckets(pageable, assembler);
}

If I open the Swagger page, the following form is available for the resource:

enter image description here

The issue I have is that the pageable parameter is detected with content-type application/json and I don't know how to pass a value to change the page size for example. All values seem to be ignored.

Is it possible to pass the query parameters as JSON object? or is it possible to configure Swagger to generate independent query parameter fields for getters contained by the Pageable interface?

Please note that I am using Springfox with Gradle:

compile 'io.springfox:springfox-spring-web:2.3.1'
compile 'io.springfox:springfox-swagger2:2.3.1'
compile 'io.springfox:springfox-swagger-ui:2.3.1'
Laurent
  • 14,122
  • 13
  • 57
  • 89

13 Answers13

42

This is a known issue with Spring-Fox. See Issue #755. Based on zdila's comment 2 at this time alternative is to add @ApiImplicitParams which is not ideal but it does work.

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "integer", paramType = "query",
            value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "integer", paramType = "query",
            value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query",
            value = "Sorting criteria in the format: property(,asc|desc). " +
                    "Default sort order is ascending. " +
                    "Multiple sort criteria are supported.")
})

[Swagger UI showing @ApiImplicitParams for Pageable]

1 https://github.com/springfox/springfox/issues/755

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

Laurent
  • 14,122
  • 13
  • 57
  • 89
Vineet Bhatia
  • 2,469
  • 4
  • 30
  • 28
23

Building on Vineet Bhatia's answer, you can wrap the solution up in a custom annotation for reusability:

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })
@interface ApiPageable {
}

Which can then be used like so:

@ApiPageable
public Page<Data> getData(Pageable pageRequest) {
Sean Connolly
  • 5,692
  • 7
  • 37
  • 74
11

Vineet Bhatia's answer with @ApiImplicitParams looks fine. But I faced with situation, when @ApiIgnor and @ApiParam(hidden = true) doesn't work and you can still observe the asembler and pageable params. I fixed this problem by adding next line

docket.ignoredParameterTypes(Pageable.class, PagedResourcesAssembler.class);

to the Docket bean in my SwaggerConfig.

General Failure
  • 2,421
  • 4
  • 23
  • 49
Mykola L
  • 121
  • 1
  • 4
  • Looks like this doesn't works with the SpringDocs-openapi-ui. Any guidance how to make it work ? – PAA Jan 21 '20 at 09:26
10

Java example:

Bean:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .paths(PathSelectors.any())
        .build()
        .directModelSubstitute(Pageable.class, SwaggerPageable.class);
}

SwaggerPageable:

@Getter
private static class SwaggerPageable {

    @ApiParam(value = "Number of records per page", example = "0")
    @Nullable
    private Integer size;

    @ApiParam(value = "Results page you want to retrieve (0..N)", example = "0")
    @Nullable
    private Integer page;

    @ApiParam(value = "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
    @Nullable
    private String sort;

}

Swagger:

enter image description here

Marcus Voltolim
  • 413
  • 4
  • 12
9

Open API 3.0 has seamless integration.

For example,

@GetMapping("/filter")
public Page<Employee> filterEmployees(Pageable pageable) {
     return repository.getEmployees(pageable);
}

Add springdoc-openapi-data-rest dependency

implementation 'org.springdoc:springdoc-openapi-data-rest:1.5.2'

Note: You may add '@ParameterObject' if you have multiple parameters

public Page<Employee> filterEmployees(@ParameterObject Pageable pageable)
binary
  • 1,364
  • 1
  • 15
  • 20
7

Here is the version of the annotation that was integrated into springdoc-openapi-data-rest for OpenAPI v3:

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Parameter(in = ParameterIn.QUERY
        , description = "Zero-based page index (0..N)"
        , name = "page"
        , content = @Content(schema = @Schema(type = "integer", defaultValue = "0")))
@Parameter(in = ParameterIn.QUERY
        , description = "The size of the page to be returned"
        , name = "size"
        , content = @Content(schema = @Schema(type = "integer", defaultValue = "20")))
@Parameter(in = ParameterIn.QUERY
        , description = "Sorting criteria in the format: property(,asc|desc). "
        + "Default sort order is ascending. " + "Multiple sort criteria are supported."
        , name = "sort"
        , content = @Content(array = @ArraySchema(schema = @Schema(type = "string"))))
public @interface PageableAsQueryParam {

}

See https://springdoc.github.io/springdoc-openapi-demos/faq.html#how-can-i-map-pageable-spring-date-commons-object-to-correct-url-parameter-in-swagger-ui

nimai
  • 2,083
  • 1
  • 18
  • 16
  • Could you please guide me here : https://stackoverflow.com/questions/60834165/spring-open-api-specification-3-array-property-names-has-to-be-pluralized-co – PAA Mar 24 '20 at 16:32
  • Is there any way to send query parameters in single Parameter ? – PAA Apr 03 '20 at 07:48
5

This solution works without the need to annotate every single API method in every single controller. First we create a replacement for Pageable class with the correct property names and descriptions (Kotlin code, you can use a Interface for Java):

data class SwaggerPageable(
        @ApiModelProperty("Number of records per page", example = "20")
        val size: Int?,

        @ApiModelProperty("Results page you want to retrieve (0..N)", example = "0")
        val page: Int?,

        @ApiModelProperty("Sorting criteria in the format: property(,asc|desc)." +
                "Default sort order is ascending. Multiple sort criteria are supported.")
        var sort: String?
)

Then in the Swagger config, just add a direct substitute from Pageable to this class (again Kotlin code, but Java should be pretty similar):

@Bean
fun api(): Docket {
    return Docket(DocumentationType.SWAGGER_2)
            .select()
            .paths(PathSelectors.any())
            .build()
            .directModelSubstitute(Pageable::class.java, SwaggerPageable::class.java)
}

The result looks like this:

enter image description here

The downside is not being able to define the default value in the ApiModelProperty, but this is more than good enough for my project.

redent84
  • 18,901
  • 4
  • 62
  • 85
4

Answer of Vineet Bhatia will have validation problem when you are not running on localhost. It will argue for integer parameters that they are not corresponding to json schema.

So I changed integer to string:

    @ApiImplicitParams({
        @ApiImplicitParam(name = "page", dataType = "string", paramType = "query",
                value = "Results page you want to retrieve (0..N)"),
        @ApiImplicitParam(name = "size", dataType = "string", paramType = "query",
                value = "Number of records per page."),
        @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query",
                value = "Sorting criteria in the format: property(,asc|desc). " +
                        "Default sort order is ascending. " +
                        "Multiple sort criteria are supported.")
})
3

For people who wants to solve this problem in 2019. This configuration via springfox documentation works fine except you can't set description for parameters.

Code is here.

https://github.com/springfox/springfox/blob/ef1721afc4c910675d9032bee59aea8e75e06d27/springfox-data-rest/src/main/java/springfox/documentation/spring/data/rest/configuration/SpringDataRestConfiguration.java

helsonxiao
  • 91
  • 3
  • with this class I get the following error: Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.rest.core.config.RepositoryRestConfiguration' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} – mor222 Apr 25 '19 at 17:01
  • @mor222 - you can remove RepositoryRestConfiguration altogether and replace `restConfiguration.getPageParamName()` with "page", `restConfiguration.getLimitParamName()` with "size" and `restConfiguration.getSortParamName()` with "sort" respectively – Lewis Munene Feb 22 '22 at 20:48
1

here is a solution for springdoc-openapi-ui

SpringDocUtils.getConfig()
.replaceWithClass(org.springframework.data.domain.Pageable.class, SwaggerPageable.class);
    
@Getter
private static class SwaggerPageable {
    
        @ApiParam(value = "Number of records per page", example = "0")
        @Nullable
        private Integer size;
    
        @ApiParam(value = "Results page you want to retrieve (0..N)", example = "0")
        @Nullable
        private Integer page;
    
        @ApiParam(value = "Sorting criteria in the format: property(,asc|desc). Default sort order is ascending. Multiple sort criteria are supported.")
        @Nullable
        private String sort;
    
}
agdula
  • 26
  • 3
  • Hey! The question already has answers which cover both spring fox and spring doc openapi, so your answer doesn’t add more information. Please have a look at https://stackoverflow.com/help/how-to-answer – Moritz Apr 16 '22 at 08:09
  • @Moritz Hey. Would you be so kind and point out that answer. I was looking for it and had to dig out the SpringDocUtils.getConfig() .replaceWithClass(org.springframework.data.domain.Pageable.class, SwaggerPageable.class); port from a github discussin (@ApiImplicitParams is not the only solution).. – agdula Apr 17 '22 at 17:30
  • oh... and... I don't have enough stars to add comments to other posts so this was the only way I could bing that line up. Feel free to use my finding add a comment to some other post. I'll be happy to remove my answer – agdula Apr 17 '22 at 17:37
0

Answer to validation problem indicated by Evgeny.

Using

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page."),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })

throws an exception:

Illegal DefaultValue  for parameter type integer
java.lang.NumberFormatException: For input string: ""
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
    at java.base/java.lang.Long.parseLong(Long.java:709)
    at java.base/java.lang.Long.valueOf(Long.java:1151)
    at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412)
    at jdk.internal.reflect.GeneratedMethodAccessor366.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)

(at least, it does with springfox-swagger2 and springfox-swagger2-ui version 2.9.2)

You can avoid the exception by following Evgeny's answer or by adding default values and example values for the integer parameters:

@ApiImplicitParams({
    @ApiImplicitParam(name = "page", dataType = "int", paramType = "query", value = "Results page you want to retrieve (0..N)", defaultValue = "0", example = "2"),
    @ApiImplicitParam(name = "size", dataType = "int", paramType = "query", value = "Number of records per page.", defaultValue = "20", example = "10"),
    @ApiImplicitParam(name = "sort", allowMultiple = true, dataType = "string", paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
            + "Default sort order is ascending. " + "Multiple sort criteria are supported.") })
0

An update:

Use org.springdoc.core.converters.models.DefaultPageable instead of Pageable. It already has the @ParameterObject which makes it breakdown in to 3 parameters when the Swagger JSON is generated.

selalerer
  • 3,766
  • 2
  • 23
  • 33
-1

Although the solution with the implicit parameters works, it introduces a lot of extra, brittle code. In the end we went with the following solution:

@GetMapping(value = "/")
public HttpEntity<PagedResources<Item>> getItems(
    @RequestParam(value = "page", required = false) Integer page,
    @RequestParam(value = "size", required = false) Integer size,
    PagedResourcesAssembler assembler) {
    Page<Item> itemPage = itemService.listItems(PageRequest.of(page, size, Sort.unsorted()));
    return new ResponseEntity<>(assembler.toResource(itemPage), HttpStatus.OK);
}

We pass a PageRequest (which implements Pageable) to our service, which returns a Page. (all from org.springframework.data.domain).

The org.springframework.data.web.PagedResourcesAssembler gets injected automagically via the controller method and allows mapping Items to org.springframework.hateoas.PagedResources

We didn't require dynamic sorting so we omitted that; it poses some challenges to add sorting since springfox does not play nice with org.springframework.data.domain.Sort.

Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60