You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2021/05/11 08:48:39 UTC
[sling-org-apache-sling-graphql-core] branch SLING-10309/experiment
updated: SLING-10309 - add base64-encoded Cursor
This is an automated email from the ASF dual-hosted git repository.
bdelacretaz pushed a commit to branch SLING-10309/experiment
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/SLING-10309/experiment by this push:
new 1322a7e SLING-10309 - add base64-encoded Cursor
1322a7e is described below
commit 1322a7e9abe43a5ee7b45aebdfa486a7cf38baf6
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Tue May 11 10:48:20 2021 +0200
SLING-10309 - add base64-encoded Cursor
---
.../sling/graphql/api/pagination/Cursor.java | 60 ++++++++++++++++++++
.../apache/sling/graphql/api/pagination/Edge.java | 7 ++-
.../sling/graphql/api/pagination/PageInfo.java | 11 ++--
.../sling/graphql/api/pagination/ResultsPage.java | 3 +
.../pagination/{Edge.java => package-info.java} | 14 ++---
.../sling/graphql/core/engine/CursorTest.java | 66 ++++++++++++++++++++++
.../sling/graphql/core/engine/PaginationTest.java | 42 +++++++-------
7 files changed, 171 insertions(+), 32 deletions(-)
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
new file mode 100644
index 0000000..975502f
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/Cursor.java
@@ -0,0 +1,60 @@
+/*
+ * 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 java.util.Base64;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/** Edges provide paginated data as per https://relay.dev/graphql/connections.htm */
+ @ProviderType
+public class Cursor {
+ private final String rawValue;
+ private final String encoded;
+
+ public Cursor(String rawValue) {
+ this.rawValue = rawValue == null ? "" : rawValue;
+ this.encoded = encode(this.rawValue);
+ }
+
+ public boolean isEmpty() {
+ return rawValue == null || rawValue.length() == 0;
+ }
+
+ @NotNull
+ public static String encode(String rawValue) {
+ return Base64.getEncoder().encodeToString(rawValue.getBytes());
+ }
+
+ @NotNull
+ public static String decode(String encodedValue) {
+ return new String(Base64.getDecoder().decode(encodedValue));
+ }
+
+ @Override
+ public String toString() {
+ return encoded;
+ }
+
+ public String getRawValue() {
+ return rawValue;
+ }
+}
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 9b234db..62fbde7 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
@@ -19,8 +19,11 @@
package org.apache.sling.graphql.api.pagination;
- /** Edges provide paginated data as per https://relay.dev/graphql/connections.htm */
+import org.osgi.annotation.versioning.ConsumerType;
+
+/** Edges provide paginated data as per https://relay.dev/graphql/connections.htm */
+@ConsumerType
public interface Edge<T> {
T getNode();
- String getCursor();
+ 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 dd07408..06a6d51 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,10 +19,13 @@
package org.apache.sling.graphql.api.pagination;
- /** Information about a results page as per https://relay.dev/graphql/connections.htm */
-public interface PageInfo {
- String getStartCursor();
- String getEndCursor();
+import org.osgi.annotation.versioning.ConsumerType;
+
+/** Information about a results page as per https://relay.dev/graphql/connections.htm */
+ @ConsumerType
+ public interface PageInfo {
+ Cursor getStartCursor();
+ Cursor getEndCursor();
boolean isHasPreviousPage();
boolean isHasNextPage();
}
diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/ResultsPage.java b/src/main/java/org/apache/sling/graphql/api/pagination/ResultsPage.java
index a946407..db8a433 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/ResultsPage.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/ResultsPage.java
@@ -21,9 +21,12 @@
import java.util.List;
+import org.osgi.annotation.versioning.ConsumerType;
+
/** This is what https://relay.dev/graphql/connections.htm calls a "connection".
* It actually represents a page of results, so why not call it that?
*/
+@ConsumerType
public interface ResultsPage<T> {
List<Edge<T>> getEdges();
PageInfo getPageInfo();
diff --git a/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java b/src/main/java/org/apache/sling/graphql/api/pagination/package-info.java
similarity index 76%
copy from src/main/java/org/apache/sling/graphql/api/pagination/Edge.java
copy to src/main/java/org/apache/sling/graphql/api/pagination/package-info.java
index 9b234db..ef9663c 100644
--- a/src/main/java/org/apache/sling/graphql/api/pagination/Edge.java
+++ b/src/main/java/org/apache/sling/graphql/api/pagination/package-info.java
@@ -17,10 +17,10 @@
* under the License.
*/
- package org.apache.sling.graphql.api.pagination;
-
- /** Edges provide paginated data as per https://relay.dev/graphql/connections.htm */
-public interface Edge<T> {
- T getNode();
- String getCursor();
-}
+ /**
+ * This package contains APIs which are independent of
+ * a specific implementation of the underlying graphQL engine.
+ */
+@Version("0.0.1")
+package org.apache.sling.graphql.api.pagination;
+import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java
new file mode 100644
index 0000000..db62524
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/engine/CursorTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import java.util.UUID;
+
+import org.apache.sling.graphql.api.pagination.Cursor;
+import org.junit.Test;
+
+public class CursorTest {
+ private final String testValue = UUID.randomUUID().toString();
+
+ @Test
+ public void testEncoding() {
+ final String encoded = Cursor.encode(testValue);
+ assertNotEquals(encoded, testValue);
+ assertEquals(testValue, Cursor.decode(encoded));
+ assertFalse("Encoded cursor should not contain raw value in clear text", encoded.contains(testValue));
+ }
+
+ 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());
+ }
+
+ @Test
+ public void testNonEmptyValue() {
+ assertWithValue(testValue, false);
+ }
+
+ @Test
+ public void testNullValue() {
+ assertWithValue(null, true);
+ }
+
+ @Test
+ public void testEmptyValue() {
+ assertWithValue("", true);
+ }
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java b/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java
index 285ca45..dcee1f3 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/PaginationTest.java
@@ -33,6 +33,7 @@ import org.apache.sling.graphql.core.mocks.CharacterTypeResolver;
import org.apache.sling.graphql.core.mocks.HumanDTO;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
+import org.apache.sling.graphql.api.pagination.Cursor;
import org.apache.sling.graphql.api.pagination.Edge;
import org.apache.sling.graphql.api.pagination.PageInfo;
import org.apache.sling.graphql.api.pagination.ResultsPage;
@@ -49,7 +50,7 @@ public class PaginationTest extends ResourceQueryTestBase {
@Override
public @Nullable Object get(@NotNull SlingDataFetcherEnvironment e) throws Exception {
- final String cursor = e.getArgument("after", null);
+ final String cursor = e.getArgument("after", "");
final int limit = e.getArgument("limit", 2);
return new HumansResultPage(humans, cursor, limit);
}
@@ -57,14 +58,14 @@ public class PaginationTest extends ResourceQueryTestBase {
static class HumansResultPage implements ResultsPage<HumanDTO>,PageInfo {
- private final String startCursor;
- private String endCursor;
+ private final Cursor startCursor;
+ private Cursor endCursor;
private boolean hasPreviousPage;
private boolean hasNextPage;
private final List<Edge<HumanDTO>> edges = new ArrayList<>();
HumansResultPage(List<HumanDTO> humans, String cursor, int limit) {
- this.startCursor = cursor == null ? "" : cursor;
+ this.startCursor = new Cursor(Cursor.decode(cursor));
// TODO this should be generalized so that data can be provided as
// a data source with "skip after cursor" functionality, and the
@@ -86,16 +87,16 @@ public class PaginationTest extends ResourceQueryTestBase {
}
@Override
- public String getCursor() {
- return dto.getId();
+ public Cursor getCursor() {
+ return new Cursor(dto.getId());
}
});
- endCursor = dto.getId();
+ endCursor = new Cursor(dto.getId());
} else if(startCursor.isEmpty()) {
inRange = true;
hasPreviousPage = false;
- } else if(startCursor.toString().equals(dto.getId())) {
+ } else if(startCursor.getRawValue().equals(dto.getId())) {
inRange = true;
hasPreviousPage = true;
}
@@ -113,12 +114,12 @@ public class PaginationTest extends ResourceQueryTestBase {
}
@Override
- public String getStartCursor() {
+ public Cursor getStartCursor() {
return startCursor;
}
@Override
- public String getEndCursor() {
+ public Cursor getEndCursor() {
return endCursor;
}
@@ -144,9 +145,9 @@ public class PaginationTest extends ResourceQueryTestBase {
TestUtil.registerSlingDataFetcher(context.bundleContext(), "humans/paginated", new ConnectionDataFetcher(humans));
}
- private void assertPageInfo(String json, String startCursor, String endCursor, Boolean hasPreviousPage, Boolean hasNextPage) {
- assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.startCursor", equalTo(startCursor)));
- assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.endCursor", equalTo(endCursor)));
+ private void assertPageInfo(String json, Cursor startCursor, Cursor endCursor, Boolean hasPreviousPage, Boolean hasNextPage) {
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.startCursor", equalTo(startCursor.toString())));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.endCursor", equalTo(endCursor.toString())));
assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.hasPreviousPage", equalTo(hasPreviousPage)));
assertThat(json, hasJsonPath("$.data.paginatedQuery.pageInfo.hasNextPage", equalTo(hasNextPage)));
}
@@ -155,8 +156,9 @@ public class PaginationTest extends ResourceQueryTestBase {
int dataIndex = 0;
for(int i=startIndex; i <= endIndex; i++) {
final String id = "human-" + i;
+ final Cursor c = new Cursor(id);
final String name = "Luke-" + i;
- assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].cursor", equalTo(id)));
+ assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].cursor", equalTo(c.toString())));
assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].node.id", equalTo(id)));
assertThat(json, hasJsonPath("$.data.paginatedQuery.edges[" + dataIndex + "].node.name", equalTo(name)));
dataIndex++;
@@ -171,27 +173,29 @@ public class PaginationTest extends ResourceQueryTestBase {
+ " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ " edges { cursor node { id name }}"
+"}}");
- assertPageInfo(json, "", "human-2", false, true );
+ assertPageInfo(json, new Cursor(null), new Cursor("human-2"), false, true );
assertEdges(json, 1, 2);
}
@Test
public void startCursorAndLimit() throws Exception {
- final String json = queryJSON("{ paginatedQuery(after:\"human-5\", limit:6) {"
+ final Cursor start = new Cursor("human-5");
+ final String json = queryJSON("{ paginatedQuery(after:\"" + start + "\", limit:6) {"
+ " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ " edges { cursor node { id name }}"
+"}}");
- assertPageInfo(json, "human-5", "human-11", true, true);
+ assertPageInfo(json, new Cursor("human-5"), new Cursor("human-11"), true, true);
assertEdges(json, 6, 11);
}
@Test
public void startCursorNearEnd() throws Exception {
- final String json = queryJSON("{ paginatedQuery(after:\"human-94\", limit:60) {"
+ final Cursor start = new Cursor("human-94");
+ final String json = queryJSON("{ paginatedQuery(after:\"" + start + "\", limit:60) {"
+ " pageInfo { startCursor endCursor hasPreviousPage hasNextPage }"
+ " edges { cursor node { id name }}"
+"}}");
- assertPageInfo(json, "human-94", "human-99", true, false);
+ assertPageInfo(json, new Cursor("human-94"), new Cursor("human-99"), true, false);
assertEdges(json, 95, 99);
}
}