diff --git a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs index 5c54edf2827..ce11a24c7c0 100644 --- a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs +++ b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs @@ -353,6 +353,11 @@ public static class Paging /// public const string NoPagingBoundaries = "HC0052"; + /// + /// The requested number of values per page must be at least 0. + /// + public const string MinPaginationItems = "HC0079"; + /// /// The cursor format is invalid. /// diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs index 3f62a7dd66c..eb7030e39dd 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPagingHandler.cs @@ -70,6 +70,14 @@ public void ValidateContext(IResolverContext context) context.Path); } + if (first < 0) + { + throw ThrowHelper.PagingHandler_MinPageSize( + (int)first, + context.Selection.Field, + context.Path); + } + if (first > MaxPageSize) { throw ThrowHelper.PagingHandler_MaxPageSize( @@ -79,6 +87,14 @@ public void ValidateContext(IResolverContext context) context.Path); } + if (last < 0) + { + throw ThrowHelper.PagingHandler_MinPageSize( + (int)last, + context.Selection.Field, + context.Path); + } + if (last > MaxPageSize) { throw ThrowHelper.PagingHandler_MaxPageSize( diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.Designer.cs b/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.Designer.cs index d59be8c4bfc..f3f00774e22 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.Designer.cs @@ -104,5 +104,11 @@ internal static string ThrowHelper_InvalidIndexCursor_Message { return ResourceManager.GetString("ThrowHelper_InvalidIndexCursor_Message", resourceCulture); } } + + internal static string ThrowHelper_PagingHandler_MinPageSize { + get { + return ResourceManager.GetString("ThrowHelper_PagingHandler_MinPageSize", resourceCulture); + } + } } } diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.resx b/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.resx index 8a6cbdab568..3910005e7ae 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.resx +++ b/src/HotChocolate/Core/src/Types.CursorPagination/Properties/CursorResources.resx @@ -48,4 +48,7 @@ The cursor specified in `{0}` has an invalid format. + + The requested number of values per page must be at least 0. + diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/Utilities/ThrowHelper.cs b/src/HotChocolate/Core/src/Types.CursorPagination/Utilities/ThrowHelper.cs index a806f7844fd..eda54272e17 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/Utilities/ThrowHelper.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/Utilities/ThrowHelper.cs @@ -6,6 +6,19 @@ namespace HotChocolate.Utilities; internal static class ThrowHelper { + public static GraphQLException PagingHandler_MinPageSize( + int requestedItems, + IObjectField field, + Path path) + => new GraphQLException( + ErrorBuilder.New() + .SetMessage(ThrowHelper_PagingHandler_MinPageSize) + .SetCode(ErrorCodes.Paging.MinPaginationItems) + .SetPath(path) + .SetExtension(nameof(field), field.Coordinate.ToString()) + .SetExtension(nameof(requestedItems), requestedItems) + .Build()); + public static GraphQLException PagingHandler_MaxPageSize( int requestedItems, int maxAllowedItems, diff --git a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/IntegrationTests.cs index a930a4166f4..a5935561fd6 100644 --- a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/IntegrationTests.cs @@ -177,6 +177,39 @@ await executor .MatchSnapshotAsync(); } + [Fact] + public async Task MinPageSizeReached_First() + { + Snapshot.FullName(); + + var executor = + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .Services + .BuildServiceProvider() + .GetRequestExecutorAsync(); + + await executor + .ExecuteAsync(@" + { + letters(first: -1) { + edges { + node + cursor + } + nodes + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + }") + .MatchSnapshotAsync(); + } + [Fact] public async Task MaxPageSizeReached_First() { @@ -211,6 +244,39 @@ await executor .MatchSnapshotAsync(); } + [Fact] + public async Task MinPageSizeReached_Last() + { + Snapshot.FullName(); + + var executor = + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .Services + .BuildServiceProvider() + .GetRequestExecutorAsync(); + + await executor + .ExecuteAsync(@" + { + letters(last: -1) { + edges { + node + cursor + } + nodes + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + }") + .MatchSnapshotAsync(); + } + [Fact] public async Task MaxPageSizeReached_Last() { diff --git a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_First.snap b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_First.snap new file mode 100644 index 00000000000..8a874453128 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_First.snap @@ -0,0 +1,18 @@ +{ + "errors": [ + { + "message": "The requested number of values per page must be at least 0.", + "path": [ + "letters" + ], + "extensions": { + "code": "HC0079", + "field": "Query.letters", + "requestedItems": -1 + } + } + ], + "data": { + "letters": null + } +} diff --git a/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_Last.snap b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_Last.snap new file mode 100644 index 00000000000..8a874453128 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.CursorPagination.Tests/__snapshots__/IntegrationTests.MinPageSizeReached_Last.snap @@ -0,0 +1,18 @@ +{ + "errors": [ + { + "message": "The requested number of values per page must be at least 0.", + "path": [ + "letters" + ], + "extensions": { + "code": "HC0079", + "field": "Query.letters", + "requestedItems": -1 + } + } + ], + "data": { + "letters": null + } +}