diff --git a/server/opensharing-server-core/src/main/java/io/opensharing/http/Pagination.java b/server/opensharing-server-core/src/main/java/io/opensharing/http/Pagination.java index f625c97e..dc6796bb 100644 --- a/server/opensharing-server-core/src/main/java/io/opensharing/http/Pagination.java +++ b/server/opensharing-server-core/src/main/java/io/opensharing/http/Pagination.java @@ -35,6 +35,18 @@ public static ListResponse page( return new ListResponse<>(items, rows.size() > size ? encode(offset + size) : null); } + /** Reads a {@code maxResults} query parameter, which is null when absent. */ + public static Integer maxResults(String value) { + if (value == null || value.isEmpty()) { + return null; + } + try { + return Integer.valueOf(value); + } catch (NumberFormatException notAnInteger) { + throw ApiException.invalidParameter("maxResults must be an integer"); + } + } + private static int pageSize(Integer maxResults) { if (maxResults == null || maxResults == 0) { return MAX_RESULTS; diff --git a/server/opensharing-server/src/main/java/io/opensharing/recipient/RecipientAdminService.java b/server/opensharing-server/src/main/java/io/opensharing/recipient/RecipientAdminService.java index 629d16a2..b0aab8e6 100644 --- a/server/opensharing-server/src/main/java/io/opensharing/recipient/RecipientAdminService.java +++ b/server/opensharing-server/src/main/java/io/opensharing/recipient/RecipientAdminService.java @@ -14,6 +14,7 @@ import com.linecorp.armeria.server.annotation.StatusCode; import io.opensharing.auth.ProviderTokenDecorator; import io.opensharing.http.ListResponse; +import io.opensharing.http.Pagination; /** * Provider HTTP API for recipient CRUD, behind {@link ProviderTokenDecorator}. Only the owner may @@ -53,10 +54,13 @@ public RecipientResponse create(ServiceRequestContext ctx, CreateRecipientReques @Get("/recipients") public ListResponse list( ServiceRequestContext ctx, - @Param @Nullable Integer maxResults, + @Param @Nullable String maxResults, @Param @Nullable String pageToken) { return recipients.list( - ProviderTokenDecorator.user(ctx), maxResults, pageToken, activationBaseUrl); + ProviderTokenDecorator.user(ctx), + Pagination.maxResults(maxResults), + pageToken, + activationBaseUrl); } /** diff --git a/server/opensharing-server/src/main/java/io/opensharing/share/ShareAdminService.java b/server/opensharing-server/src/main/java/io/opensharing/share/ShareAdminService.java index 8bf8b78c..c75314ca 100644 --- a/server/opensharing-server/src/main/java/io/opensharing/share/ShareAdminService.java +++ b/server/opensharing-server/src/main/java/io/opensharing/share/ShareAdminService.java @@ -15,6 +15,7 @@ import com.linecorp.armeria.server.annotation.StatusCode; import io.opensharing.auth.ProviderTokenDecorator; import io.opensharing.http.ListResponse; +import io.opensharing.http.Pagination; /** * Provider HTTP API for share CRUD and recipient grants, behind {@link ProviderTokenDecorator}. @@ -45,9 +46,10 @@ public ShareResponse create(ServiceRequestContext ctx, CreateShareRequest reques @Get("/shares") public ListResponse list( ServiceRequestContext ctx, - @Param @Nullable Integer maxResults, + @Param @Nullable String maxResults, @Param @Nullable String pageToken) { - return shares.list(ProviderTokenDecorator.user(ctx), maxResults, pageToken); + return shares.list( + ProviderTokenDecorator.user(ctx), Pagination.maxResults(maxResults), pageToken); } /** @@ -87,9 +89,10 @@ public HttpResponse delete(ServiceRequestContext ctx, @Param String share) { public ListResponse listPermissions( ServiceRequestContext ctx, @Param String share, - @Param @Nullable Integer maxResults, + @Param @Nullable String maxResults, @Param @Nullable String pageToken) { - return shares.listPermissions(ProviderTokenDecorator.user(ctx), share, maxResults, pageToken); + return shares.listPermissions( + ProviderTokenDecorator.user(ctx), share, Pagination.maxResults(maxResults), pageToken); } /** diff --git a/server/opensharing-server/src/test/java/io/opensharing/http/ProviderMaxResultsTest.java b/server/opensharing-server/src/test/java/io/opensharing/http/ProviderMaxResultsTest.java new file mode 100644 index 00000000..3de3ce54 --- /dev/null +++ b/server/opensharing-server/src/test/java/io/opensharing/http/ProviderMaxResultsTest.java @@ -0,0 +1,50 @@ +package io.opensharing.http; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.opensharing.TestServer; +import io.opensharing.TestServer.Response; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class ProviderMaxResultsTest { + + private static final String SHARES = "/api/1.0/opensharing/provider/shares"; + private static final String RECIPIENTS = "/api/1.0/opensharing/provider/recipients"; + + private static TestServer server; + + @BeforeAll + static void startServer() { + server = TestServer.start(); + } + + @AfterAll + static void stopServer() { + server.close(); + } + + @Test + void rejectsAMaxResultsThatIsNotAnInteger() { + assertEquals(201, server.post(SHARES, "alice-token", "{\"name\":\"max-results\"}").status()); + for (String list : List.of(SHARES, RECIPIENTS, SHARES + "/max-results/permissions")) { + for (String value : List.of("ten", "1.5", "3000000000")) { + Response invalid = server.get(list + "?maxResults=" + value, "alice-token"); + assertEquals(400, invalid.status(), list); + assertEquals(ErrorCodes.INVALID_PARAMETER_VALUE, invalid.errorCode()); + assertEquals("maxResults must be an integer", invalid.body().path("message").asText()); + } + } + } + + @Test + void treatsAnEmptyMaxResultsAsUnset() { + for (String list : List.of(SHARES, RECIPIENTS)) { + Response unset = server.get(list + "?maxResults=", "alice-token"); + assertEquals(200, unset.status(), list); + assertEquals(server.get(list, "alice-token").body(), unset.body()); + } + } +}