diff --git a/src/it/java/io/weaviate/integration/ClusterITest.java b/src/it/java/io/weaviate/integration/ClusterITest.java index 58d49fab7..b7716717e 100644 --- a/src/it/java/io/weaviate/integration/ClusterITest.java +++ b/src/it/java/io/weaviate/integration/ClusterITest.java @@ -7,7 +7,12 @@ import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; +import io.weaviate.client6.v1.api.cluster.Node; +import io.weaviate.client6.v1.api.cluster.NodeVerbosity; import io.weaviate.client6.v1.api.cluster.ShardingState; +import io.weaviate.client6.v1.api.cluster.replication.Replication; +import io.weaviate.client6.v1.api.cluster.replication.ReplicationState; +import io.weaviate.client6.v1.api.cluster.replication.ReplicationType; import io.weaviate.containers.Weaviate; public class ClusterITest extends ConcurrentTest { @@ -50,4 +55,80 @@ public void test_listNodes() throws IOException { // Assert Assertions.assertThat(allNodes).as("total no. nodes").hasSize(3); } + + @Test + public void test_replicateLifecycle() throws IOException { + // Arrange + + // We must create the collection first before any shards exist on the nodes. + var nsThings = ns("Things"); + client.collections.create(nsThings); + + var nodes = client.cluster.listNodes(opt -> opt.verbosity(NodeVerbosity.VERBOSE)); + Assertions.assertThat(nodes) + .as("cluster at least 2 nodes").hasSizeGreaterThanOrEqualTo(2); + + Node source = null; + Node target = null; + for (var node : nodes) { + if (source == null && !node.shards().isEmpty()) { + source = node; + } else if (target == null) { + target = node; + } + } + + var wantShard = source.shards().get(0).name(); + var srcNode = source.name(); + var tgtNode = target.name(); + + // Act: start replication + var replication = client.cluster.replicate( + nsThings, + wantShard, + srcNode, + tgtNode, + ReplicationType.MOVE); + + var got = client.cluster.replication.get(replication.uuid()); + Assertions.assertThat(got).get() + .as("expected replication status") + .returns(nsThings, Replication::collection) + .returns(wantShard, Replication::shard) + .returns(srcNode, Replication::sourceNode) + .returns(tgtNode, Replication::targetNode) + .returns(ReplicationType.MOVE, Replication::type) + .returns(null, Replication::history) + .extracting(Replication::status).isNotNull(); + + var withHistory = client.cluster.replication.get( + replication.uuid(), + repl -> repl.includeHistory(true)); + Assertions.assertThat(withHistory).get() + .as("includes history") + .extracting(Replication::history).isNotNull(); + + // Act: query replications + var filtered = client.cluster.replication.list( + repl -> repl + .collection(nsThings) + .shard(wantShard) + .targetNode(tgtNode)); + + Assertions.assertThat(filtered) + .as("existing replications for %s-%s -> %s", nsThings, wantShard, tgtNode) + .hasSize(1); + + // Act: cancel + client.cluster.replication.cancel(replication.uuid()); + + eventually(() -> client.cluster.replication.get(replication.uuid()) + .orElseThrow() + .status().state() == ReplicationState.CANCELED, 1000, 25, "replication must be canceled"); + + // Act: delete replication + client.cluster.replication.delete(replication.uuid()); + + eventually(() -> client.cluster.replication.list().isEmpty(), 1000, 15, "replication must be deleted"); + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/NodeVerbosity.java b/src/main/java/io/weaviate/client6/v1/api/cluster/NodeVerbosity.java index abdcbe0e9..8ee09b363 100644 --- a/src/main/java/io/weaviate/client6/v1/api/cluster/NodeVerbosity.java +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/NodeVerbosity.java @@ -2,9 +2,27 @@ import com.google.gson.annotations.SerializedName; -public enum NodeVerbosity { +import io.weaviate.client6.v1.internal.json.JsonEnum; + +public enum NodeVerbosity implements JsonEnum { @SerializedName("minimal") - MINIMAL, + MINIMAL("minimal"), @SerializedName("verbose") - VERBOSE; + VERBOSE("verbose"); + + private final String jsonValue; + + private NodeVerbosity(String jsonValue) { + this.jsonValue = jsonValue; + } + + @Override + public String jsonValue() { + return jsonValue; + } + + @Override + public String toString() { + return jsonValue(); + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/WeaviateClusterClient.java b/src/main/java/io/weaviate/client6/v1/api/cluster/WeaviateClusterClient.java index f3deee2ab..bdd7cac27 100644 --- a/src/main/java/io/weaviate/client6/v1/api/cluster/WeaviateClusterClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/WeaviateClusterClient.java @@ -6,14 +6,25 @@ import java.util.function.Function; import io.weaviate.client6.v1.api.WeaviateApiException; +import io.weaviate.client6.v1.api.cluster.replication.CreateReplicationRequest; +import io.weaviate.client6.v1.api.cluster.replication.Replication; +import io.weaviate.client6.v1.api.cluster.replication.ReplicationType; +import io.weaviate.client6.v1.api.cluster.replication.WeaviateReplicationClient; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.rest.RestTransport; public class WeaviateClusterClient { private final RestTransport restTransport; + /** + * Client for {@code /replication/replicate} endpoints for managing + * replications. + */ + public final WeaviateReplicationClient replication; + public WeaviateClusterClient(RestTransport restTransport) { this.restTransport = restTransport; + this.replication = new WeaviateReplicationClient(restTransport); } /** @@ -75,4 +86,29 @@ public List listNodes(Function> listNodes(Function replicate( + String collection, + String shard, + String sourceNode, + String targetNode, + ReplicationType type) { + return this.restTransport.performRequestAsync( + new CreateReplicationRequest(collection, shard, sourceNode, targetNode, type), + CreateReplicationRequest._ENDPOINT); + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CancelReplicationRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CancelReplicationRequest.java new file mode 100644 index 000000000..415341b4d --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CancelReplicationRequest.java @@ -0,0 +1,15 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.Collections; +import java.util.UUID; + +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record CancelReplicationRequest(UUID uuid) { + + static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + request -> "POST", + request -> "/replication/replicate/" + request.uuid() + "/cancel", + __ -> Collections.emptyMap()); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CreateReplicationRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CreateReplicationRequest.java new file mode 100644 index 000000000..c22a2f2ad --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/CreateReplicationRequest.java @@ -0,0 +1,24 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.Collections; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record CreateReplicationRequest( + @SerializedName("collection") String collection, + @SerializedName("shard") String shard, + @SerializedName("sourceNode") String sourceNode, + @SerializedName("targetNode") String targetNode, + @SerializedName("type") ReplicationType type) { + + public static final Endpoint _ENDPOINT = new SimpleEndpoint<>( + request -> "POST", + request -> "/replication/replicate", + request -> Collections.emptyMap(), + request -> JSON.serialize(request), + (__, response) -> JSON.deserialize(response, Replication.class)); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteAllReplicationsRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteAllReplicationsRequest.java new file mode 100644 index 000000000..649a22729 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteAllReplicationsRequest.java @@ -0,0 +1,14 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.Collections; + +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record DeleteAllReplicationsRequest() { + + static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + request -> "DELETE", + request -> "/replication/replicate", + __ -> Collections.emptyMap()); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteReplicationRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteReplicationRequest.java new file mode 100644 index 000000000..98666c80f --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/DeleteReplicationRequest.java @@ -0,0 +1,15 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.Collections; +import java.util.UUID; + +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record DeleteReplicationRequest(UUID uuid) { + + static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + request -> "DELETE", + request -> "/replication/replicate/" + request.uuid(), + __ -> Collections.emptyMap()); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/GetReplicationRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/GetReplicationRequest.java new file mode 100644 index 000000000..f56a33c82 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/GetReplicationRequest.java @@ -0,0 +1,56 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.Collections; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.OptionalEndpoint; + +public record GetReplicationRequest(UUID uuid, boolean includeHistory) { + + static final Endpoint> _ENDPOINT = OptionalEndpoint.noBodyOptional( + request -> "GET", + request -> "/replication/replicate/" + request.uuid(), + request -> Collections.singletonMap("includeHistory", request.includeHistory()), + Replication.class); + + public static GetReplicationRequest of(UUID uuid) { + return of(uuid, ObjectBuilder.identity()); + } + + public static GetReplicationRequest of(UUID uuid, Function> fn) { + return fn.apply(new Builder(uuid)).build(); + } + + public GetReplicationRequest(Builder builder) { + this(builder.uuid, builder.includeHistory); + } + + public static class Builder implements ObjectBuilder { + private final UUID uuid; + private boolean includeHistory = false; + + public Builder(UUID uuid) { + this.uuid = uuid; + } + + /** + * Include history of statuses for this replication. + * + * @see Replication#history + */ + public Builder includeHistory(boolean includeHistory) { + this.includeHistory = includeHistory; + return this; + } + + @Override + public GetReplicationRequest build() { + return new GetReplicationRequest(this); + } + } + +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ListReplicationsRequest.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ListReplicationsRequest.java new file mode 100644 index 000000000..032e331f7 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ListReplicationsRequest.java @@ -0,0 +1,76 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.HashMap; +import java.util.List; +import java.util.function.Function; + +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record ListReplicationsRequest(String collection, String shard, String targetNode, boolean includeHistory) { + + static final Endpoint> _ENDPOINT = SimpleEndpoint.noBody( + request -> "GET", + request -> "/replication/replicate/list", + request -> new HashMap<>() { + { + put("collection", request.collection); + put("shard", request.shard); + put("targetNode", request.targetNode); + put("includeHistory", request.includeHistory); + } + }, + (__, response) -> JSON.deserializeList(response, Replication.class)); + + public static ListReplicationsRequest of() { + return of(ObjectBuilder.identity()); + } + + public static ListReplicationsRequest of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public ListReplicationsRequest(Builder builder) { + this(builder.collection, builder.shard, builder.targetNode, builder.includeHistory); + } + + public static class Builder implements ObjectBuilder { + private String collection; + private String shard; + private String targetNode; + private boolean includeHistory = false; + + public Builder collection(String collection) { + this.collection = collection; + return this; + } + + public Builder shard(String shard) { + this.shard = shard; + return this; + } + + public Builder targetNode(String targetNode) { + this.targetNode = targetNode; + return this; + } + + /** + * Include history of statuses for this replication. + * + * @see Replication#history + */ + public Builder includeHistory(boolean includeHistory) { + this.includeHistory = includeHistory; + return this; + } + + @Override + public ListReplicationsRequest build() { + return new ListReplicationsRequest(this); + } + } + +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/Replication.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/Replication.java new file mode 100644 index 000000000..d55888b4d --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/Replication.java @@ -0,0 +1,19 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.List; +import java.util.UUID; + +import com.google.gson.annotations.SerializedName; + +public record Replication( + /** Operation UUID. */ + @SerializedName("id") UUID uuid, + @SerializedName("collection") String collection, + @SerializedName("shard") String shard, + @SerializedName("sourceNode") String sourceNode, + @SerializedName("targetNode") String targetNode, + @SerializedName("type") ReplicationType type, + @SerializedName("status") ReplicationStatus status, + /** Absent if {@code includeHistory} not enabled. */ + @SerializedName("statusHistory") List history) { +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationState.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationState.java new file mode 100644 index 000000000..61df883ee --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationState.java @@ -0,0 +1,19 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import com.google.gson.annotations.SerializedName; + +public enum ReplicationState { + @SerializedName("REGISTERED") + REGISTERED, + @SerializedName("HYDRATING") + HYDRATING, + @SerializedName("FINALIZING") + FINALIZING, + @SerializedName("DEHYDRATING") + DEHYDRATING, + @SerializedName("READY") + READY, + @SerializedName("CANCELLED") + CANCELED, + +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationStatus.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationStatus.java new file mode 100644 index 000000000..0972ddf68 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationStatus.java @@ -0,0 +1,10 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public record ReplicationStatus( + @SerializedName("state") ReplicationState state, + @SerializedName("errors") List errors) { +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationType.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationType.java new file mode 100644 index 000000000..f099eb00b --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/ReplicationType.java @@ -0,0 +1,12 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import com.google.gson.annotations.SerializedName; + +public enum ReplicationType { + /** A copy of the shard is created on the target node. */ + @SerializedName("COPY") + COPY, + /** Shard is moved to the target node and is deleted from the source node. */ + @SerializedName("MOVE") + MOVE; +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClient.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClient.java new file mode 100644 index 000000000..1b48eabb5 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClient.java @@ -0,0 +1,124 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +import io.weaviate.client6.v1.api.WeaviateApiException; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.rest.RestTransport; + +public class WeaviateReplicationClient { + private final RestTransport restTransport; + + public WeaviateReplicationClient(RestTransport restTransport) { + this.restTransport = restTransport; + } + + /** + * Get information about a replication operation. + * + * @param uuid Replication UUID. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public Optional get(UUID uuid) throws IOException { + return this.restTransport.performRequest(GetReplicationRequest.of(uuid), GetReplicationRequest._ENDPOINT); + } + + /** + * Get information about a replication operation. + * + * @param uuid Replication UUID. + * @param fn Lambda expression for optional parameters. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public Optional get(UUID uuid, + Function> fn) throws IOException { + return this.restTransport.performRequest(GetReplicationRequest.of(uuid, fn), GetReplicationRequest._ENDPOINT); + } + + /** + * List all replication operations. + * + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + * @see WeaviateReplicationClient#list(Function) for filtering replications by + * collection, shard, or target node. + */ + public List list() + throws IOException { + return this.restTransport.performRequest(ListReplicationsRequest.of(), ListReplicationsRequest._ENDPOINT); + } + + /** + * List all replication operations. + * + * @param fn Lambda expression for optional parameters. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public List list(Function> fn) + throws IOException { + return this.restTransport.performRequest(ListReplicationsRequest.of(fn), ListReplicationsRequest._ENDPOINT); + } + + /** + * Cancel a replication operation. + * + * @param uuid Replication UUID. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public void cancel(UUID uuid) + throws IOException { + this.restTransport.performRequest(new CancelReplicationRequest(uuid), CancelReplicationRequest._ENDPOINT); + } + + /** + * Delete a replication operation. + * + * @param uuid Replication UUID. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public void delete(UUID uuid) + throws IOException { + this.restTransport.performRequest(new DeleteReplicationRequest(uuid), DeleteReplicationRequest._ENDPOINT); + } + + /** + * Delete all replication operations. + * + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + */ + public void deleteAll() + throws IOException { + this.restTransport.performRequest(null, DeleteAllReplicationsRequest._ENDPOINT); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClientAsync.java new file mode 100644 index 000000000..058360124 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/cluster/replication/WeaviateReplicationClientAsync.java @@ -0,0 +1,90 @@ +package io.weaviate.client6.v1.api.cluster.replication; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.rest.RestTransport; + +public class WeaviateReplicationClientAsync { + private final RestTransport restTransport; + + public WeaviateReplicationClientAsync(RestTransport restTransport) { + this.restTransport = restTransport; + } + + /** + * Get information about a replication operation. + * + * @param uuid Replication UUID. + */ + public CompletableFuture> get(UUID uuid) throws IOException { + return this.restTransport.performRequestAsync(GetReplicationRequest.of(uuid), GetReplicationRequest._ENDPOINT); + } + + /** + * Get information about a replication operation. + * + * @param uuid Replication UUID. + * @param fn Lambda expression for optional parameters. + */ + public CompletableFuture> get(UUID uuid, + Function> fn) throws IOException { + return this.restTransport.performRequestAsync(GetReplicationRequest.of(uuid, fn), GetReplicationRequest._ENDPOINT); + } + + /** + * List all replication operations. + * + * @see WeaviateReplicationClientAsync#list(Function) for filtering replications + * by + * collection, shard, or target node. + */ + public CompletableFuture> list() + throws IOException { + return this.restTransport.performRequestAsync(ListReplicationsRequest.of(), ListReplicationsRequest._ENDPOINT); + } + + /** + * List all replication operations. + * + * @param fn Lambda expression for optional parameters. + */ + public CompletableFuture> list( + Function> fn) + throws IOException { + return this.restTransport.performRequestAsync(ListReplicationsRequest.of(fn), ListReplicationsRequest._ENDPOINT); + } + + /** + * Cancel a replication operation. + * + * @param uuid Replication UUID. + */ + public CompletableFuture cancel(UUID uuid) + throws IOException { + return this.restTransport.performRequestAsync(new CancelReplicationRequest(uuid), + CancelReplicationRequest._ENDPOINT); + } + + /** + * Delete a replication operation. + * + * @param uuid Replication UUID. + */ + public CompletableFuture delete(UUID uuid) + throws IOException { + return this.restTransport.performRequestAsync(new DeleteReplicationRequest(uuid), + DeleteReplicationRequest._ENDPOINT); + } + + /** Delete all replication operations. */ + public CompletableFuture deleteAll() + throws IOException { + return this.restTransport.performRequestAsync(null, DeleteAllReplicationsRequest._ENDPOINT); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java index d1dc1db55..344409495 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java +++ b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java @@ -1,5 +1,7 @@ package io.weaviate.client6.v1.internal.json; +import java.util.List; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; @@ -91,6 +93,11 @@ public static final T deserialize(String json, Class cls) { return gson.fromJson(json, cls); } + @SuppressWarnings("unchecked") + public static final List deserializeList(String json, Class cls) { + return (List) deserialize(json, TypeToken.getParameterized(List.class, cls)); + } + public static final T deserialize(String json, TypeToken token) { return gson.fromJson(json, token); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/UrlEncoder.java b/src/main/java/io/weaviate/client6/v1/internal/rest/UrlEncoder.java index 2bade27b3..3dfc8fd2e 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/UrlEncoder.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/UrlEncoder.java @@ -30,7 +30,7 @@ public static String encodeQuery(Map queryParams) { } var query = queryParams.entrySet().stream() .filter(qp -> { - if (qp == null) { + if (qp == null || qp.getValue() == null) { return false; } if (qp.getValue() instanceof String str) {