Skip to content

Commit

Permalink
Add PagingOptions.IncludeNodesField option (#7396)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler authored Aug 22, 2024
1 parent 277615a commit efe5acc
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 36 deletions.
70 changes: 41 additions & 29 deletions src/HotChocolate/Core/src/Types.CursorPagination/ConnectionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ internal sealed class ConnectionType
internal ConnectionType(
string connectionName,
TypeReference nodeType,
bool withTotalCount)
bool includeTotalCount,
bool includeNodesField)
{
if (nodeType is null)
{
Expand All @@ -38,31 +39,35 @@ internal ConnectionType(
TypeContext.Output,
factory: _ => new EdgeType(connectionName, nodeType));

Definition = CreateTypeDefinition(withTotalCount, edgesType);
Definition = CreateTypeDefinition(includeTotalCount, includeNodesField, edgesType);
Definition.Name = NameHelper.CreateConnectionName(connectionName);
Definition.Dependencies.Add(new(nodeType));
Definition.Configurations.Add(
new CompleteConfiguration(
(c, d) =>
{
var definition = (ObjectTypeDefinition)d;
var nodes = definition.Fields.First(IsNodesField);
nodes.Type = TypeReference.Parse(
$"[{c.GetType<IType>(nodeType).Print()}]",
TypeContext.Output);
},
Definition,
ApplyConfigurationOn.BeforeNaming,
nodeType,
TypeDependencyFulfilled.Named));
Definition.Configurations.Add(
new CompleteConfiguration(
(c, _) => EdgeType = c.GetType<IEdgeType>(TypeReference.Create(edgeTypeName)),
Definition,
ApplyConfigurationOn.BeforeCompletion));

if (includeNodesField)
{
Definition.Configurations.Add(
new CompleteConfiguration(
(c, d) =>
{
var definition = (ObjectTypeDefinition)d;
var nodes = definition.Fields.First(IsNodesField);
nodes.Type = TypeReference.Parse(
$"[{c.GetType<IType>(nodeType).Print()}]",
TypeContext.Output);
},
Definition,
ApplyConfigurationOn.BeforeNaming,
nodeType,
TypeDependencyFulfilled.Named));
}
}

internal ConnectionType(TypeReference nodeType, bool withTotalCount)
internal ConnectionType(TypeReference nodeType, bool includeTotalCount, bool includeNodesField)
{
if (nodeType is null)
{
Expand All @@ -78,7 +83,7 @@ internal ConnectionType(TypeReference nodeType, bool withTotalCount)

// the property is set later in the configuration
ConnectionName = default!;
Definition = CreateTypeDefinition(withTotalCount);
Definition = CreateTypeDefinition(includeTotalCount, includeNodesField);
Definition.Dependencies.Add(new(nodeType));
Definition.Dependencies.Add(new(edgeType));
Definition.NeedsNameCompletion = true;
Expand All @@ -92,16 +97,19 @@ internal ConnectionType(TypeReference nodeType, bool withTotalCount)

var definition = (ObjectTypeDefinition)d;
var edges = definition.Fields.First(IsEdgesField);
var nodes = definition.Fields.First(IsNodesField);

definition.Name = NameHelper.CreateConnectionName(ConnectionName);
edges.Type = TypeReference.Parse(
$"[{NameHelper.CreateEdgeName(ConnectionName)}!]",
TypeContext.Output);

nodes.Type = TypeReference.Parse(
$"[{type.Print()}]",
TypeContext.Output);
if (includeNodesField)
{
var nodes = definition.Fields.First(IsNodesField);
nodes.Type = TypeReference.Parse(
$"[{type.Print()}]",
TypeContext.Output);
}
},
Definition,
ApplyConfigurationOn.BeforeNaming,
Expand Down Expand Up @@ -151,7 +159,8 @@ private bool IsOfTypeWithRuntimeType(
result is null || RuntimeType.IsInstanceOfType(result);

private static ObjectTypeDefinition CreateTypeDefinition(
bool withTotalCount,
bool includeTotalCount,
bool includeNodesField,
TypeReference? edgesType = null)
{
var definition = new ObjectTypeDefinition
Expand All @@ -173,13 +182,16 @@ private static ObjectTypeDefinition CreateTypeDefinition(
pureResolver: GetEdges)
{ CustomSettings = { ContextDataKeys.Edges } });

definition.Fields.Add(new(
Names.Nodes,
ConnectionType_Nodes_Description,
pureResolver: GetNodes)
{ CustomSettings = { ContextDataKeys.Nodes } });
if (includeNodesField)
{
definition.Fields.Add(new(
Names.Nodes,
ConnectionType_Nodes_Description,
pureResolver: GetNodes)
{ CustomSettings = { ContextDataKeys.Nodes } });
}

if (withTotalCount)
if (includeTotalCount)
{
definition.Fields.Add(new(
Names.TotalCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ private static TypeReference CreateConnectionTypeRef(
return CreateConnectionType(
connectionName,
nodeType,
options.IncludeTotalCount ?? false);
options.IncludeTotalCount ?? false,
options.IncludeNodesField ?? IncludeNodesField);
}

private static CursorPagingProvider ResolvePagingProvider(
Expand Down Expand Up @@ -420,21 +421,23 @@ providerName is null
private static TypeReference CreateConnectionType(
string? connectionName,
TypeReference nodeType,
bool withTotalCount)
bool includeTotalCount,
bool includeNodesField)
{
return connectionName is null
? TypeReference.Create(
"HotChocolate_Types_Connection",
nodeType,
_ => new ConnectionType(nodeType, withTotalCount),
_ => new ConnectionType(nodeType, includeTotalCount, includeNodesField),
TypeContext.Output)
: TypeReference.Create(
connectionName + "Connection",
TypeContext.Output,
factory: _ => new ConnectionType(
connectionName,
nodeType,
withTotalCount));
includeTotalCount,
includeNodesField));
}

private static string EnsureConnectionNameCasing(string connectionName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public static class PagingDefaults
public const bool InferCollectionSegmentNameFromField = true;

public const bool RequirePagingBoundaries = false;

public const bool IncludeNodesField = true;
}
16 changes: 13 additions & 3 deletions src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ public class PagingOptions
public int? MaxPageSize { get; set; }

/// <summary>
/// Defines if the total count of the paged data set
/// shall be included into the paging result type.
/// Defines whether a <c>totalCount</c> field shall be
/// exposed on the Connection type, returning the total
/// count of items in the paginated data set.
/// </summary>
public bool? IncludeTotalCount { get; set; }

Expand Down Expand Up @@ -51,6 +52,13 @@ public class PagingOptions
/// </summary>
public string? ProviderName { get; set; }

/// <summary>
/// Defines whether a <c>nodes</c> field shall be
/// exposed on the Connection type, returning the
/// flattened nodes of the <c>edges</c> field.
/// </summary>
public bool? IncludeNodesField { get; set; }

/// <summary>
/// Merges the <paramref name="other"/> options into this options instance wherever
/// a property is not set.
Expand All @@ -68,6 +76,7 @@ internal void Merge(PagingOptions other)
InferConnectionNameFromField ??= other.InferConnectionNameFromField;
InferCollectionSegmentNameFromField ??= other.InferCollectionSegmentNameFromField;
ProviderName ??= other.ProviderName;
IncludeNodesField ??= other.IncludeNodesField;
}

/// <summary>
Expand All @@ -83,6 +92,7 @@ internal PagingOptions Copy()
RequirePagingBoundaries = RequirePagingBoundaries,
InferConnectionNameFromField = InferConnectionNameFromField,
InferCollectionSegmentNameFromField = InferCollectionSegmentNameFromField,
ProviderName = ProviderName
ProviderName = ProviderName,
IncludeNodesField = IncludeNodesField
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ public async Task Simple_StringList_Schema()
executor.Schema.Print().MatchSnapshot();
}

[Fact]
public async Task IncludeNodesField_False()
{
var executor =
await new ServiceCollection()
.AddGraphQL()
.AddQueryType<QueryType>()
.ModifyPagingOptions(o => o.IncludeNodesField = false)
.Services
.BuildServiceProvider()
.GetRequestExecutorAsync();

executor.Schema.Print().MatchSnapshot();
}

[Fact]
public async Task SetPagingOptionsIsStillApplied()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
schema {
query: Query
}

"A connection to a list of items."
type ExplicitTypeConnection {
"Information to aid in pagination."
pageInfo: PageInfo!
"A list of edges."
edges: [ExplicitTypeEdge!]
}

"An edge in a connection."
type ExplicitTypeEdge {
"A cursor for use in pagination."
cursor: String!
"The item at the end of the edge."
node: String!
}

type Foo {
bar: String!
}

"A connection to a list of items."
type LettersConnection {
"Information to aid in pagination."
pageInfo: PageInfo!
"A list of edges."
edges: [LettersEdge!]
}

"An edge in a connection."
type LettersEdge {
"A cursor for use in pagination."
cursor: String!
"The item at the end of the edge."
node: String!
}

"A connection to a list of items."
type NestedObjectListConnection {
"Information to aid in pagination."
pageInfo: PageInfo!
"A list of edges."
edges: [NestedObjectListEdge!]
"Identifies the total count of items in the connection."
totalCount: Int!
}

"An edge in a connection."
type NestedObjectListEdge {
"A cursor for use in pagination."
cursor: String!
"The item at the end of the edge."
node: [Foo!]!
}

"Information about pagination in a connection."
type PageInfo {
"Indicates whether more edges exist following the set defined by the clients arguments."
hasNextPage: Boolean!
"Indicates whether more edges exist prior the set defined by the clients arguments."
hasPreviousPage: Boolean!
"When paginating backwards, the cursor to continue."
startCursor: String
"When paginating forwards, the cursor to continue."
endCursor: String
}

type Query {
letters("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): LettersConnection
explicitType("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): ExplicitTypeConnection
nestedObjectList("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): NestedObjectListConnection
}

0 comments on commit efe5acc

Please sign in to comment.