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/12 14:38:25 UTC

[sling-org-apache-sling-graphql-core] 01/01: SLING-10309 - GraphQL results pagination

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issue/SLING-10309
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git

commit d22fac658e088244a6f3e5ff1f12df2591cb0f83
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed May 12 16:37:58 2021 +0200

    SLING-10309 - GraphQL results pagination
    
    * added basic support for the connection directive
---
 .../graphql/core/engine/DefaultQueryExecutor.java  | 50 +++++++++++++++++++++-
 src/test/resources/paginated-humans-schema.txt     | 30 ++++---------
 2 files changed, 57 insertions(+), 23 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/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