I am trying to use a Micronaut controller with pagination. Micronaut-Data has this Spring inspired way to access the repositories using the Pageable class and returning a Page
The problem comes when you want to show this paginated data. I have not been able create a call the controller with pagination. Here I have a simple controller:
@Controller
public class PageableController {
private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);
@Get(produces = APPLICATION_JSON, value = "/test{?pageable}")
public Page<String> getNames(@Nullable Pageable pageable) {
LOGGER.info("pageable {}", pageable);
if( pageable == null){
return Page.of(Arrays.asList("foo", "bar"), Pageable.UNPAGED, 2);
}else{
return Page.of(Arrays.asList("foo", "bar"), pageable, 2);
}
}
}
I would expect to be able to call it with something like this. But currently the logger shows that pageable is always null:
@MicronautTest
class PageableControllerTest {
@Inject
@Client("/")
private RxHttpClient client;
@Test
void callsWithPageable() {
String uri = "/test?size=20&number=2";
String orders = client.toBlocking().retrieve(HttpRequest.GET(uri));
//TODO, assert orders and pagination
}
It would be even better if we could test it with something like:
@Test
void callsWithPageableParsingJson() {
String uri = "/test?size=20&number=2";
//This fails to parse as it can't build pages.
Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));
assertThat(pages.getSize(), is(2));
assertThat(pages.getContent(), contains("foo", "bar"));
}
// Inspired by Argument.listOf
private static <T> Argument<Page<T>> pageOf(Class<T> type) {
return Argument.of((Class<Page<T>>) ((Class) Page.class), type);
}
And this Micronaut bug shows that the right way to paginate is with Micronaut Data
The problem was solved by adding the following dependecy:
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-runtime</artifactId>
<version>1.0.0.M1</version>
</dependency>
My controller layer had access to the micronaut-data-model but this jar contains the important class PageableRequestArgumentBinder. Just by being i the classpath it will automatically be injected as a binder with no need for extra configuration.
And yes, Free See was right and now I can remove the pageable argument from the path and the argument from the method does not need to be @Nullable :
@Controller
public class PageableController {
private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);
@Get(produces = APPLICATION_JSON, value = "/test")
public Page<String> getNames(Pageable pageable) {
LOGGER.info("pageable {}", pageable);
return Page.of(Arrays.asList("foo", "bar", "baz"), pageable, 3);
}
To call it we new to use the standard parameters names defined in DataConfiguration.PageableConfiguration.
If you want to use different parameters you can change it with properties:
micronaut:
data:
pageable:
max-page-size: 500
And you can test it with
@Test
void callsWithPageable() {
String uri = "/test?page=1&size=2";
Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));
assertThat(pages.getPageNumber(), is(1));
assertThat(pages.getTotalPages(), is(2));
assertThat(pages.getSize(), is(2));
assertThat(pages.getContent(), contains("foo", "bar", "baz"));
}
And, to make things even better, the client can convert the result to a page using the pageOf method that returns Argument>