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 13:25:53 UTC

[sling-org-apache-sling-graphql-core] branch issue/SLING-10309 created (now 9dde6fa)

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

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


      at 9dde6fa  SLING-10309 - GraphQL results pagination

This branch includes the following new commits:

     new 9dde6fa  SLING-10309 - GraphQL results pagination

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


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

Posted by ra...@apache.org.
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 9dde6fa07a1230f097d4a947cac53758fb8090ad
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu May 13 15:24:31 2021 +0200

    SLING-10309 - GraphQL results pagination
    
    * cleaned the API docs and made them point to the corresponding section
    from https://relay.dev/graphql/connections.htm
    * removed some methods from the Cursor class from the public space
---
 .../sling/graphql/api/pagination/Connection.java   | 15 ++++--
 .../sling/graphql/api/pagination/Cursor.java       | 51 +++++++++++++------
 .../apache/sling/graphql/api/pagination/Edge.java  |  9 +++-
 .../sling/graphql/api/pagination/PageInfo.java     | 19 +++++---
 .../core/helpers/pagination/GenericConnection.java | 25 +++++-----
 .../engine => api/pagination}/CursorTest.java      | 57 +++++++++++-----------
 6 files changed, 110 insertions(+), 66 deletions(-)

diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/Connection.java b/src/main/java/org/apache/sling/graphql/api/pagination/Connection.java
index b2c2998..6e247ae 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/Connection.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/Connection.java
@@ -19,13 +19,18 @@
 
 package org.apache.sling.graphql.api.pagination;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ConsumerType;
 
-/** As per https://relay.dev/graphql/connections.htm a "connection"
- *  is a page of results for a paginated query.
-*/
+/**
+ * The {@code Connection} interface provides support for implementing the Connection Types, according to the specification from
+ *
+ * <a href="https://relay.dev/graphql/connections.htm#sec-Connection-Types">https://relay.dev/graphql/connections.htm#sec-Connection-Types</a>.
+ */
 @ConsumerType
 public interface Connection<T> {
-    Iterable<Edge<T>> getEdges();
-    PageInfo getPageInfo();
+
+    @NotNull Iterable<Edge<T>> getEdges();
+
+    @NotNull PageInfo getPageInfo();
 }
diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/Cursor.java b/src/main/java/org/apache/sling/graphql/api/pagination/Cursor.java
index 7b21839..7fe0308 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/Cursor.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/Cursor.java
@@ -20,39 +20,60 @@
  package org.apache.sling.graphql.api.pagination;
 
 import java.util.Base64;
+import java.util.Objects;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.graphql.api.SlingGraphQLException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.osgi.annotation.versioning.ProviderType;
 
-/** Cursor for our paginated results.
- *  Base64-encoded to express the fact that it's meant to
- *  be opaque.
+/**
+ * This class provides a Base64-encoded cursor which is used for paginated results, according to the specification from
+ * <a href="https://relay.dev/graphql/connections.htm#sec-Cursor">https://relay.dev/graphql/connections.htm#sec-Cursor</a>.
  */
- @ProviderType
+@ProviderType
 public class Cursor {
+
     private final String rawValue;
     private final String encoded;
 
-    public Cursor(@Nullable String rawValue) {
-        this.rawValue = rawValue == null ? "" : rawValue;
+    /**
+     * Creates a cursor from a {@link String}. The passed {@code rawValue} should not be {@code null}, nor an empty {@link String}.
+     *
+     * @param rawValue the raw value from which to generate a cursor
+     */
+    public Cursor(@NotNull String rawValue) {
+        if (StringUtils.isEmpty(rawValue)) {
+            throw new SlingGraphQLException("Cannot create a cursor from an empty string.");
+        }
+        this.rawValue = rawValue;
         this.encoded = encode(this.rawValue);
     }
 
     @Override
     public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
         if(!(obj instanceof Cursor)) {
             return false;
         }
         final Cursor other = (Cursor)obj;
-        return other.rawValue.equals(this.rawValue);
+        return Objects.equals(rawValue, other.rawValue) && Objects.equals(encoded, other.encoded);
     }
 
     @Override
     public int hashCode() {
-        return rawValue == null ? null : rawValue.hashCode();
+        return Objects.hash(rawValue, encoded);
     }
 
+    /**
+     * Generates a new cursor based on an {@code encoded} value obtained via the same encoding mechanism {@code this} class uses.
+     *
+     * @param encoded the encoded value from which to generate a new cursor
+     * @return a new cursor, if one can be generated; {@code null} otherwise
+     */
     public static Cursor fromEncodedString(@Nullable String encoded) {
         if(encoded == null) {
             return null;
@@ -64,17 +85,13 @@ public class Cursor {
         return new Cursor(decode(encoded));
     }
 
-    public boolean isEmpty() {
-        return rawValue == null || rawValue.length() == 0;
-    }
-
     @NotNull
-    public static String encode(String rawValue) {
+    static String encode(String rawValue) {
         return Base64.getEncoder().encodeToString(rawValue.getBytes());
     }
 
     @NotNull
-    public static String decode(String encodedValue) {
+    static String decode(String encodedValue) {
         return new String(Base64.getDecoder().decode(encodedValue));
     }
 
@@ -83,7 +100,13 @@ public class Cursor {
         return encoded;
     }
 
+    @NotNull
     public String getRawValue() {
         return rawValue;
     }
+
+    @NotNull
+    public String getEncoded() {
+        return encoded;
+    }
 }
diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java b/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java
index d1e96d7..b9efc08 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java
@@ -22,9 +22,16 @@
 import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ConsumerType;
 
-/** Edges provide paginated data as per https://relay.dev/graphql/connections.htm */
+/**
+ * The {@code Edge} interface provides support for implementing the Edge Types, according to the specification from
+ * <a href="https://relay.dev/graphql/connections.htm#sec-Edge-Types">https://relay.dev/graphql/connections.htm#sec-Edge-Types</a>.
+ *
+ * @param <T> the edge's {@code node} type
+ */
 @ConsumerType
 public interface Edge<T> {
+
     @NotNull T getNode();
+
     @NotNull Cursor getCursor();
 }
diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/PageInfo.java b/src/main/java/org/apache/sling/graphql/api/pagination/PageInfo.java
index 3bf9d27..efb4e26 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/PageInfo.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/PageInfo.java
@@ -19,14 +19,21 @@
 
  package org.apache.sling.graphql.api.pagination;
 
-import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ConsumerType;
 
-/** Information about a results page as per https://relay.dev/graphql/connections.htm */
- @ConsumerType
- public interface PageInfo {
-    @Nullable Cursor getStartCursor();
-    @Nullable Cursor getEndCursor();
+/**
+ * The {@code PageInfo} interface provides support for implementing the identically named type from
+ *
+ * <a href="https://relay.dev/graphql/connections.htm#sec-Connection-Types.Fields.PageInfo">https://relay.dev/graphql/connections.htm#sec-Connection-Types.Fields.PageInfo</a>.
+ */
+@ConsumerType
+public interface PageInfo {
+    @NotNull Cursor getStartCursor();
+
+    @NotNull Cursor getEndCursor();
+
     boolean isHasPreviousPage();
+
     boolean isHasNextPage();
 }
diff --git a/src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java b/src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java
index cd4c4c1..5d103fd 100644
--- a/src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java
+++ b/src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.function.Function;
 
+import org.apache.sling.graphql.api.SlingGraphQLException;
 import org.apache.sling.graphql.api.pagination.Connection;
 import org.apache.sling.graphql.api.pagination.Cursor;
 import org.apache.sling.graphql.api.pagination.Edge;
@@ -35,12 +36,12 @@ import org.osgi.annotation.versioning.ConsumerType;
 /** As per https://relay.dev/graphql/connections.htm a "connection"
  *  is a page of results for a paginated query.
  * 
- *  Use the {@link #Builder} class to build a Connection that outputs
+ *  Use the {@link Builder} class to build a Connection that outputs
  *  the supplied data, optionally sliced based on a "start after" cursor
  *  and a limit on the number of items output.
 */
 @ConsumerType
-public class GenericConnection<T> implements Connection<T>, PageInfo {
+public final class GenericConnection<T> implements Connection<T>, PageInfo {
 
     public static final int DEFAULT_LIMIT = 10;
 
@@ -111,7 +112,7 @@ public class GenericConnection<T> implements Connection<T>, PageInfo {
         }
 
         if(!inRange && limit > 0) {
-            throw new RuntimeException("Start cursor not found in supplied data:" + startAfter);
+            throw new SlingGraphQLException("Start cursor not found in supplied data:" + startAfter);
         }
         if(hasPreviousPage == null) {
             hasPreviousPage = false;
@@ -121,7 +122,7 @@ public class GenericConnection<T> implements Connection<T>, PageInfo {
         }
     }
 
-    static private void checkNotNull(Object o, String whatIsThat) {
+    private static void checkNotNull(Object o, String whatIsThat) {
         if(o == null) {
             throw new IllegalArgumentException(whatIsThat + " is null");
         }
@@ -130,34 +131,34 @@ public class GenericConnection<T> implements Connection<T>, PageInfo {
     private Edge<T> newEdge(final T node, final Function<T, String> cursorStringProvider) {
         return new Edge<T>() {
             @Override
-            public T getNode() {
+            public @NotNull T getNode() {
                 return node;
             }
 
             @Override
-            public Cursor getCursor() {
+            public @NotNull Cursor getCursor() {
                 return new Cursor(cursorStringProvider.apply(node));
             }
         };
     }
 
     @Override
-    public Iterable<Edge<T>> getEdges() {
-        return edges::iterator;
+    public @NotNull Iterable<Edge<T>> getEdges() {
+        return edges;
     }
 
     @Override
-    public PageInfo getPageInfo() {
+    public @NotNull PageInfo getPageInfo() {
         return this;
     }
 
     @Override
-    public Cursor getStartCursor() {
+    public @NotNull Cursor getStartCursor() {
         return startCursor;
     }
 
     @Override
-    public Cursor getEndCursor() {
+    public @NotNull Cursor getEndCursor() {
         return endCursor;
     }
 
@@ -181,7 +182,7 @@ public class GenericConnection<T> implements Connection<T>, PageInfo {
          *      Cursor is set, but can contain less items that set by the "limit" parameter.          
          *  @param cursorStringProvider extracts a String from an object of type T to create a Cursor
         */
-        public Builder(Iterator<T> dataIterator, Function<T, String> cursorStringProvider) {
+        public Builder(@NotNull Iterator<T> dataIterator, @NotNull Function<T, String> cursorStringProvider) {
             connection = new GenericConnection<>(dataIterator, cursorStringProvider);
         }
 
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java b/src/test/java/org/apache/sling/graphql/api/pagination/CursorTest.java
similarity index 57%
rename from src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java
rename to src/test/java/org/apache/sling/graphql/api/pagination/CursorTest.java
index 72bd83e..68e16be 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java
+++ b/src/test/java/org/apache/sling/graphql/api/pagination/CursorTest.java
@@ -1,22 +1,22 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.graphql.core.engine;
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.api.pagination;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNull;
 
 import java.util.UUID;
 
+import org.apache.sling.graphql.api.SlingGraphQLException;
 import org.apache.sling.graphql.api.pagination.Cursor;
 import org.junit.Test;
 
@@ -41,10 +42,6 @@ public class CursorTest {
 
     private void assertWithValue(String value, boolean isEmpty) {
         final Cursor c = new Cursor(value);
-        if(value == null) {
-            value = "";
-        }
-        assertEquals("Expecting empty Cursor", isEmpty, c.isEmpty());
         assertEquals(isEmpty, c.toString().length() == 0);
         assertEquals(value, c.getRawValue());
         assertEquals(Cursor.encode(value), c.toString());
@@ -55,12 +52,12 @@ public class CursorTest {
         assertWithValue(testValue, false);
     }
 
-    @Test
+    @Test(expected = SlingGraphQLException.class)
     public void testNullValue() {
         assertWithValue(null, true);
     }
 
-    @Test
+    @Test(expected = SlingGraphQLException.class)
     public void testEmptyValue() {
         assertWithValue("", true);
     }
@@ -88,8 +85,12 @@ public class CursorTest {
 
     @Test
     public void testHashCode() {
-        final String key = UUID.randomUUID().toString();
-        final Cursor c = new Cursor(key);
-        assertEquals(key.hashCode(), c.hashCode());
+        final String key1 = UUID.randomUUID().toString();
+        final Cursor c1 = new Cursor(key1);
+        final Cursor c2 = new Cursor(key1);
+        final String key2 = UUID.randomUUID().toString();
+        final Cursor c3 = new Cursor(key2);
+        assertEquals(c1.hashCode(), c2.hashCode());
+        assertNotEquals(c1.hashCode(), c3.hashCode());
     }
 }