You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/05/13 10:25:22 UTC
[sling-org-apache-sling-graphql-core] branch master updated:
SLING-10309 - GraphQL results pagination
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/master by this push:
new b8cb935 SLING-10309 - GraphQL results pagination
b8cb935 is described below
commit b8cb93580412bc09fb474b37e21f0363815064f3
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Thu May 13 12:25:17 2021 +0200
SLING-10309 - GraphQL results pagination
* added basic support for the connection directive
---
.../graphql/core/engine/DefaultQueryExecutor.java | 50 +++++++++++++++++++++-
.../apps/graphql/test/one/GQLschema.jsp | 30 ++++---------
src/test/resources/paginated-humans-schema.txt | 30 ++++---------
3 files changed, 65 insertions(+), 45 deletions(-)
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
index c4f70e2..843f281 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
@@ -23,6 +23,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -62,10 +63,13 @@ import graphql.language.Argument;
import graphql.language.Directive;
import graphql.language.FieldDefinition;
import graphql.language.InterfaceTypeDefinition;
+import graphql.language.ListType;
+import graphql.language.NonNullType;
import graphql.language.ObjectTypeDefinition;
import graphql.language.SourceLocation;
import graphql.language.StringValue;
import graphql.language.TypeDefinition;
+import graphql.language.TypeName;
import graphql.language.UnionTypeDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLScalarType;
@@ -94,6 +98,12 @@ public class DefaultQueryExecutor implements QueryExecutor {
public static final String RESOLVER_OPTIONS = "options";
public static final String RESOLVER_SOURCE = "source";
+ public static final String CONNECTION_FOR = "for";
+ public static final String CONNECTION_FETCHER = "fetcher";
+ public static final String TYPE_STRING = "String";
+ public static final String TYPE_BOOLEAN = "Boolean";
+ public static final String TYPE_PAGE_INFO = "PageInfo";
+
private static final LogSanitizer cleanLog = new LogSanitizer();
private Map<String, String> resourceToHashMap;
@@ -239,6 +249,7 @@ public class DefaultQueryExecutor implements QueryExecutor {
throw new SlingGraphQLException("Exception while building wiring.", e);
}
}
+ handleConnectionTypes(type, typeRegistry);
return typeWiring;
});
}
@@ -251,7 +262,6 @@ public class DefaultQueryExecutor implements QueryExecutor {
for (InterfaceTypeDefinition type : interfaceTypes) {
wireTypeResolver(builder, type, r);
}
-
return builder.build();
}
@@ -380,6 +390,44 @@ public class DefaultQueryExecutor implements QueryExecutor {
return resource.getPath() + ":" + String.join(".", selectors);
}
+ private void handleConnectionTypes(ObjectTypeDefinition typeDefinition, TypeDefinitionRegistry typeRegistry) {
+ for (FieldDefinition fieldDefinition : typeDefinition.getFieldDefinitions()) {
+ Directive directive = fieldDefinition.getDirective("connection");
+ if (directive != null) {
+ if (directive.getArgument(CONNECTION_FOR) != null) {
+ String forType = ((StringValue) directive.getArgument(CONNECTION_FOR).getValue()).getValue();
+ Optional<TypeDefinition> forTypeDefinition = typeRegistry.getType(forType);
+ if (!forTypeDefinition.isPresent()) {
+ throw new SlingGraphQLException("Type '" + forType + "' has not been defined.");
+ }
+ ObjectTypeDefinition forOTD = (ObjectTypeDefinition) forTypeDefinition.get();
+ ObjectTypeDefinition edge = ObjectTypeDefinition.newObjectTypeDefinition().name(forOTD.getName() + "Edge")
+ .fieldDefinition(new FieldDefinition("cursor", new TypeName(TYPE_STRING)))
+ .fieldDefinition(new FieldDefinition("node", new TypeName(forOTD.getName())))
+ .build();
+ ObjectTypeDefinition connection = ObjectTypeDefinition.newObjectTypeDefinition().name(forOTD.getName() +
+ "Connection")
+ .fieldDefinition(new FieldDefinition("edges", new ListType(new TypeName(forType + "Edge"))))
+ .fieldDefinition(new FieldDefinition("pageInfo", new TypeName(TYPE_PAGE_INFO)))
+ .build();
+ if (!typeRegistry.getType(TYPE_PAGE_INFO).isPresent()) {
+ ObjectTypeDefinition pageInfo = ObjectTypeDefinition.newObjectTypeDefinition().name(TYPE_PAGE_INFO)
+ .fieldDefinition(new FieldDefinition("hasPreviousPage", new NonNullType(new TypeName(TYPE_BOOLEAN))))
+ .fieldDefinition(new FieldDefinition("hasNextPage", new NonNullType(new TypeName(TYPE_BOOLEAN))))
+ .fieldDefinition(new FieldDefinition("startCursor", new TypeName(TYPE_STRING)))
+ .fieldDefinition(new FieldDefinition("endCursor", new TypeName(TYPE_STRING)))
+ .build();
+ typeRegistry.add(pageInfo);
+ }
+ typeRegistry.add(edge);
+ typeRegistry.add(connection);
+ } else {
+ throw new SlingGraphQLException("The connection directive requires a 'for' argument.");
+ }
+ }
+ }
+ }
+
private static class LRUCache<T> extends LinkedHashMap<String, T> {
private final int capacity;
diff --git a/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp b/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
index dbe2a5a..fca94ba 100644
--- a/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
+++ b/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
@@ -24,33 +24,19 @@ directive @fetcher(
source : String = ""
) on FIELD_DEFINITION
+# This directive will generate the additional types for a Connection,
+# according to the Relay Connections specification from
+# https://relay.dev/graphql/connections.htm
+directive @connection(
+ for : String
+) on FIELD_DEFINITION
+
type Query {
oneSchemaResource : SlingResource @fetcher(name:"test/pipe" source:"$")
- oneSchemaQuery : SlingResourceConnection @fetcher(name:"test/query")
+ oneSchemaQuery : SlingResourceConnection @connection(for: "SlingResource") @fetcher(name:"test/query")
}
type SlingResource {
path: String
resourceType: String
}
-
-# The connection-specific parts of the schema might be
-# generated based on a schema directive later, but
-# writing them "by hand" also works
-
-type PageInfo {
- startCursor : String
- endCursor : String
- hasPreviousPage : Boolean
- hasNextPage : Boolean
-}
-
-type SlingResourceEdge {
- cursor: String
- node: SlingResource
-}
-
-type SlingResourceConnection {
- edges : [SlingResourceEdge]
- pageInfo : PageInfo
-}
\ No newline at end of file
diff --git a/src/test/resources/paginated-humans-schema.txt b/src/test/resources/paginated-humans-schema.txt
index 62c1e40..3fecc3d 100644
--- a/src/test/resources/paginated-humans-schema.txt
+++ b/src/test/resources/paginated-humans-schema.txt
@@ -22,8 +22,15 @@ directive @fetcher(
source : String = ""
) on FIELD_DEFINITION
+# This directive will generate the additional types for a Connection,
+# according to the Relay Connections specification from
+# https://relay.dev/graphql/connections.htm
+directive @connection(
+ for : String
+) on FIELD_DEFINITION
+
type Query {
- paginatedHumans (after : String, limit : Int) : HumanConnection @fetcher(name:"humans/connection")
+ paginatedHumans (after : String, limit : Int) : HumanConnection @connection(for: "Human") @fetcher(name:"humans/connection")
}
type Human {
@@ -31,24 +38,3 @@ type Human {
name: String!
address: String
}
-
-# The connection-specific parts of the schema might be
-# generated based on a schema directive later, but
-# writing them "by hand" also works
-
-type PageInfo {
- startCursor : String
- endCursor : String
- hasPreviousPage : Boolean
- hasNextPage : Boolean
-}
-
-type HumanEdge {
- cursor: String
- node: Human
-}
-
-type HumanConnection {
- edges : [HumanEdge]
- pageInfo : PageInfo
-}
\ No newline at end of file