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
+ }
+}