diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 374441474d..591545f48d 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -19,6 +19,8 @@ public interface IJsonApiOptions : ILinksConfiguration, ISerializerOptions /// bool IncludeTotalRecordCount { get; set; } int DefaultPageSize { get; } + int? MaximumPageSize { get; } + int? MaximumPageNumber { get; } bool ValidateModelState { get; } bool AllowClientGeneratedIds { get; } bool AllowCustomQueryParameters { get; set; } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 0952a7fb0f..88e281fd5f 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -70,6 +70,22 @@ public class JsonApiOptions : IJsonApiOptions /// public int DefaultPageSize { get; set; } + /// + /// Optional. When set, limits the maximum page size for all resources. + /// + /// + /// options.MaximumPageSize = 50; + /// + public int? MaximumPageSize { get; set; } + + /// + /// Optional. When set, limits the maximum page number for all resources. + /// + /// + /// options.MaximumPageNumber = 100; + /// + public int? MaximumPageNumber { get; set; } + /// /// Whether or not the total-record count should be included in all document /// level meta objects. diff --git a/src/JsonApiDotNetCore/QueryParameterServices/PageService.cs b/src/JsonApiDotNetCore/QueryParameterServices/PageService.cs index 16883f1909..95179d85b0 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/PageService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/PageService.cs @@ -83,6 +83,10 @@ public virtual void Parse(KeyValuePair queryParameter) { ThrowBadPagingRequest(queryParameter, "value needs to be greater than zero"); } + else if (size > _options.MaximumPageSize) + { + ThrowBadPagingRequest(queryParameter, $"page size cannot be higher than {_options.MaximumPageSize}."); + } else { RequestedPageSize = size; @@ -98,6 +102,10 @@ public virtual void Parse(KeyValuePair queryParameter) { ThrowBadPagingRequest(queryParameter, "page index is not zero-based"); } + else if (number > _options.MaximumPageNumber) + { + ThrowBadPagingRequest(queryParameter, $"page index cannot be higher than {_options.MaximumPageNumber}."); + } else { Backwards = (number < 0); diff --git a/test/UnitTests/QueryParameters/PageServiceTests.cs b/test/UnitTests/QueryParameters/PageServiceTests.cs index eafafbcec3..c59b8a6e82 100644 --- a/test/UnitTests/QueryParameters/PageServiceTests.cs +++ b/test/UnitTests/QueryParameters/PageServiceTests.cs @@ -9,9 +9,13 @@ namespace UnitTests.QueryParameters { public class PageServiceTests : QueryParametersUnitTestCollection { - public PageService GetService() + public PageService GetService(int? maximumPageSize = null, int? maximumPageNumber = null) { - return new PageService(new JsonApiOptions()); + return new PageService(new JsonApiOptions + { + MaximumPageSize = maximumPageSize, + MaximumPageNumber = maximumPageNumber + }); } [Fact] @@ -28,14 +32,16 @@ public void Name_PageService_IsCorrect() } [Theory] - [InlineData("1", 1, false)] - [InlineData("abcde", 0, true)] - [InlineData("", 0, true)] - public void Parse_PageSize_CanParse(string value, int expectedValue, bool shouldThrow) + [InlineData("1", 1, null, false)] + [InlineData("abcde", 0, null, true)] + [InlineData("", 0, null, true)] + [InlineData("5", 5, 10, false)] + [InlineData("5", 5, 3, true)] + public void Parse_PageSize_CanParse(string value, int expectedValue, int? maximumPageSize, bool shouldThrow) { // Arrange var query = new KeyValuePair($"page[size]", new StringValues(value)); - var service = GetService(); + var service = GetService(maximumPageSize: maximumPageSize); // Act if (shouldThrow) @@ -51,15 +57,16 @@ public void Parse_PageSize_CanParse(string value, int expectedValue, bool should } [Theory] - [InlineData("1", 1, false)] - [InlineData("abcde", 0, true)] - [InlineData("", 0, true)] - public void Parse_PageNumber_CanParse(string value, int expectedValue, bool shouldThrow) + [InlineData("1", 1, null, false)] + [InlineData("abcde", 0, null, true)] + [InlineData("", 0, null, true)] + [InlineData("5", 5, 10, false)] + [InlineData("5", 5, 3, true)] + public void Parse_PageNumber_CanParse(string value, int expectedValue, int? maximumPageNumber, bool shouldThrow) { // Arrange var query = new KeyValuePair($"page[number]", new StringValues(value)); - var service = GetService(); - + var service = GetService(maximumPageNumber: maximumPageNumber); // Act if (shouldThrow)