Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pageable/start from 1 #1566

Open
wants to merge 8 commits into
base: 4.7.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ public class DataConfiguration implements DataSettings {
public static class PageableConfiguration {
public static final int DEFAULT_MAX_PAGE_SIZE = 100;
public static final boolean DEFAULT_SORT_IGNORE_CASE = false;
public static final boolean DEFAULT_START_FROM_PAGE_ONE = false;
public static final String DEFAULT_SORT_PARAMETER = "sort";
public static final String DEFAULT_SIZE_PARAMETER = "size";
public static final String DEFAULT_PAGE_PARAMETER = "page";
public static final String PREFIX = "pageable";
private int maxPageSize = DEFAULT_MAX_PAGE_SIZE;
private Integer defaultPageSize = null; // When is not specified the maxPageSize should be used
private boolean sortIgnoreCase = DEFAULT_SORT_IGNORE_CASE;
private boolean startFromPageOne = DEFAULT_START_FROM_PAGE_ONE;
private String sortParameterName = DEFAULT_SORT_PARAMETER;
private String sizeParameterName = DEFAULT_SIZE_PARAMETER;
private String pageParameterName = DEFAULT_PAGE_PARAMETER;
Expand Down Expand Up @@ -78,6 +80,23 @@ public void setSortDelimiter(String sortDelimiter) {
}
}

/**
* @return Whether the first page parameter starts at one
*/
public boolean isStartFromPageOne() {
return startFromPageOne;
}

/**
* This parameter is used to shift the provided page number back one when this is true.
* So when the page parameter is entered as one, the first page (0) is provided.
*
* @param startFromPageOne Whether the first page is 1
*/
public void setStartFromPageOne(boolean startFromPageOne) {
this.startFromPageOne = startFromPageOne;
}

/**
* @return The maximum page size when binding {@link io.micronaut.data.model.Pageable} objects.
*/
Expand All @@ -95,7 +114,7 @@ public void setMaxPageSize(int maxPageSize) {

/**
* @return the page size to use when binding {@link io.micronaut.data.model.Pageable}
* objects and no size parameter is used. By default is set to the same vale as {@link #maxPageSize}
* objects and no size parameter is used. By default is set to the same value as {@link #maxPageSize}
*/
public int getDefaultPageSize() {
return defaultPageSize == null ? maxPageSize : defaultPageSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.micronaut.http.bind.binders.RequestArgumentBinder;
import io.micronaut.http.bind.binders.TypedRequestArgumentBinder;

import io.micronaut.http.exceptions.HttpStatusException;
import jakarta.inject.Singleton;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -96,6 +97,13 @@ public BindingResult<Pageable> bind(ArgumentConversionContext<Pageable> context,
sort = Sort.of(orders);
}

if (configuration.isStartFromPageOne()) {
if (page < 1) {
throw new IllegalArgumentException(String.format("%s parameter starts at 1", configuration.getPageParameterName()));
}
page--;
}

if (size < 1) {
if (page == 0 && configuredMaxSize < 1 && sort == null) {
pageable = Pageable.UNPAGED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class PageableConfigSpec extends Specification {
'micronaut.data.pageable.default-page-size': 10,
'micronaut.data.pageable.sort-parameter-name': 's',
'micronaut.data.pageable.page-parameter-name': 'index',
'micronaut.data.pageable.size-parameter-name': 'max'
'micronaut.data.pageable.size-parameter-name': 'max',
'micronaut.data.pageable.start-from-page-one': true
)
DataConfiguration.PageableConfiguration configuration = context.getBean(DataConfiguration.PageableConfiguration)

Expand All @@ -38,6 +39,7 @@ class PageableConfigSpec extends Specification {
configuration.sortParameterName == 's'
configuration.pageParameterName == 'index'
configuration.sizeParameterName == 'max'
configuration.startFromPageOne

cleanup:
context.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ class PageableRequestArgumentBinderSpec extends Specification {
void 'test bind size #size and page #page with custom configuration'() {
given:
def configuration = new DataConfiguration.PageableConfiguration()
configuration.defaultPageSize=40
configuration.maxPageSize=200
configuration.sizeParameterName="perPage"
configuration.pageParameterName="myPage"
configuration.defaultPageSize = 40
configuration.maxPageSize = 200
configuration.sizeParameterName = "perPage"
configuration.pageParameterName = "myPage"
PageableRequestArgumentBinder binder = new PageableRequestArgumentBinder(configuration)
def get = HttpRequest.GET('/')
get.parameters.add("perPage", size)
Expand All @@ -94,4 +94,38 @@ class PageableRequestArgumentBinderSpec extends Specification {
"-1" | "0" | 40 | 0 // negative => uses default != max
"junk" | "0" | 40 | 0 // can't be parsed
}

@Unroll
void 'test page #page is or is not shifted with custom configuration for startFromPageOne #startFromPageOne'() {
given:
def configuration = new DataConfiguration.PageableConfiguration()
configuration.startFromPageOne = startFromPageOne
PageableRequestArgumentBinder binder = new PageableRequestArgumentBinder(configuration)
def get = HttpRequest.GET('/')
get.parameters.add("page", page)
Pageable p = binder.bind(ConversionContext.of(Pageable), get).get()

expect:
p.number == pageNumber

where:
page | pageNumber | startFromPageOne
"1" | 0 | true // first page is shifted to 0
"5" | 4 | true // fifth page is shifted to 4
}

void 'test IllegalArgumentException is thrown when startFromPageOne is true and page provided is 0'() {
given:
def configuration = new DataConfiguration.PageableConfiguration()
configuration.startFromPageOne = true
PageableRequestArgumentBinder binder = new PageableRequestArgumentBinder(configuration)
def get = HttpRequest.GET('/')
get.parameters.add("page", "0")

when:
binder.bind(ConversionContext.of(Pageable), get).get()

then:
thrown(IllegalArgumentException)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ public BindingResult<Pageable> bind(ArgumentConversionContext<Pageable> context,
sort = Sort.unsorted();
}

if (configuration.isStartFromPageOne()) {
if (page < 1) {
throw new IllegalArgumentException(String.format("%s parameter starts at 1", configuration.getPageParameterName()));
}
page--;
}

if (size < 1) {
if (page == 0 && configuredMaxSize < 1 && sort.isUnsorted()) {
pageable = Pageable.unpaged();
Expand Down