From af9ba25f93397d7753d85cfca67728007e853487 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 30 Jul 2025 15:59:16 +0200 Subject: [PATCH 01/10] wip: WeaviateApiException --- .../client6/v1/api/WeaviateApiException.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java new file mode 100644 index 000000000..28bd5977f --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -0,0 +1,13 @@ +package io.weaviate.client6.v1.api; + +/** + * Exception class thrown by client API message when the request's reached the + * server, but the operation did not complete successfully either either due to + * a bad request or a server error. + */ +public class WeaviateApiException extends RuntimeException { + // TODO: rather than storing bare values (status code, response body), + // store "Response" object and provide accessors to .status(), .error(). + + private final String endpoint; +} From 88bba02edd0a5cc96dc2b1eb8bd466de5f733beb Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 4 Aug 2025 20:05:10 +0200 Subject: [PATCH 02/10] feat: throw WeaviateApiException on unexpected error codes --- .../client6/v1/api/WeaviateApiException.java | 18 +++- .../collections/CreateCollectionRequest.java | 10 +- .../collections/DeleteCollectionRequest.java | 10 +- .../v1/api/collections/GetConfigRequest.java | 16 ++-- .../collections/ListCollectionRequest.java | 7 +- .../config/AddPropertyRequest.java | 9 +- .../collections/config/GetShardsRequest.java | 9 +- .../config/UpdateCollectionRequest.java | 9 +- .../config/UpdateShardStatusRequest.java | 9 +- .../collections/data/DeleteObjectRequest.java | 10 +- .../collections/data/InsertObjectRequest.java | 12 +-- .../data/ReferenceAddManyRequest.java | 10 +- .../collections/data/ReferenceAddRequest.java | 9 +- .../data/ReferenceDeleteRequest.java | 9 +- .../data/ReferenceReplaceRequest.java | 9 +- .../data/ReplaceObjectRequest.java | 9 +- .../collections/data/UpdateObjectRequest.java | 9 +- .../v1/internal/rest/BooleanEndpoint.java | 24 +++++ .../internal/rest/DefaultRestTransport.java | 91 +++++++++++++------ .../client6/v1/internal/rest/Endpoint.java | 52 +---------- .../v1/internal/rest/EndpointBase.java | 60 ++++++++++++ .../v1/internal/rest/JsonEndpoint.java | 7 ++ .../v1/internal/rest/OptionalEndpoint.java | 38 ++++++++ .../v1/internal/rest/RestTransport.java | 5 +- .../v1/internal/rest/SimpleEndpoint.java | 54 +++++++++++ .../client6/v1/api/AuthorizationTest.java | 11 +-- .../rest/DefaultRestTransportTest.java | 18 +--- 27 files changed, 332 insertions(+), 202 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/BooleanEndpoint.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/JsonEndpoint.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/OptionalEndpoint.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/SimpleEndpoint.java diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index 28bd5977f..6e9dc96c1 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -6,8 +6,20 @@ * a bad request or a server error. */ public class WeaviateApiException extends RuntimeException { - // TODO: rather than storing bare values (status code, response body), - // store "Response" object and provide accessors to .status(), .error(). - private final String endpoint; + private final int statusCode; + + public WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) { + super("%s %s: %s".formatted(method, endpoint, errorMessage)); + this.endpoint = endpoint; + this.statusCode = statusCode; + } + + public String endpoint() { + return endpoint; + } + + public int statusCode() { + return statusCode; + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/CreateCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/CreateCollectionRequest.java index 4573a4d06..a22957328 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/CreateCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/CreateCollectionRequest.java @@ -2,17 +2,15 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - 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 CreateCollectionRequest(CollectionConfig collection) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = new SimpleEndpoint<>( request -> "POST", request -> "/schema/", - (gson, request) -> JSON.serialize(request.collection), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> JSON.deserialize(response, CollectionConfig.class)); + request -> JSON.serialize(request.collection), + (statusCode, response) -> JSON.deserialize(response, CollectionConfig.class)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/DeleteCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/DeleteCollectionRequest.java index e49b52317..02d184986 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/DeleteCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/DeleteCollectionRequest.java @@ -2,16 +2,12 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record DeleteCollectionRequest(String collectionName) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( request -> "DELETE", request -> "/schema/" + request.collectionName, - (gson, request) -> null, - request -> Collections.emptyMap(), - status -> status != HttpStatus.SC_SUCCESS, - (gson, resopnse) -> null); + request -> Collections.emptyMap()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/GetConfigRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/GetConfigRequest.java index 39914ec99..d4c73aa58 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/GetConfigRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/GetConfigRequest.java @@ -3,17 +3,15 @@ import java.util.Collections; import java.util.Optional; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.OptionalEndpoint; public record GetConfigRequest(String collectionName) { - public static final Endpoint> _ENDPOINT = Endpoint.of( - request -> "GET", - request -> "/schema/" + request.collectionName, - (gson, request) -> null, - request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> Optional.ofNullable(JSON.deserialize(response, CollectionConfig.class))); + public static final Endpoint> _ENDPOINT = OptionalEndpoint + .noBodyOptional( + request -> "GET", + request -> "/schema/" + request.collectionName, + request -> Collections.emptyMap(), + (statusCode, response) -> JSON.deserialize(response, CollectionConfig.class)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/ListCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/ListCollectionRequest.java index 46652fe3a..9cc6f87e8 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/ListCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/ListCollectionRequest.java @@ -3,17 +3,14 @@ import java.util.Collections; import java.util.List; -import org.apache.hc.core5.http.HttpStatus; - 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 ListCollectionRequest() { - public static final Endpoint> _ENDPOINT = Endpoint.of( + public static final Endpoint> _ENDPOINT = SimpleEndpoint.noBody( request -> "GET", request -> "/schema", - (gson, request) -> null, request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, (gson, response) -> JSON.deserialize(response, ListCollectionResponse.class).collections()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/AddPropertyRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/AddPropertyRequest.java index ec670a13c..3aab45dfa 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/AddPropertyRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/AddPropertyRequest.java @@ -2,18 +2,15 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.api.collections.Property; 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 AddPropertyRequest(String collectionName, Property property) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( request -> "POST", request -> "/schema/" + request.collectionName + "/properties", - (gson, request) -> JSON.serialize(request.property), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(request.property)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java index aa5638ff2..7d76a32a6 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java @@ -3,22 +3,19 @@ import java.util.Collections; import java.util.List; -import org.apache.hc.core5.http.HttpStatus; - import com.google.gson.reflect.TypeToken; 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 GetShardsRequest(String collectionName) { @SuppressWarnings("unchecked") - public static final Endpoint> _ENDPOINT = Endpoint.of( + public static final Endpoint> _ENDPOINT = SimpleEndpoint.noBody( request -> "GET", request -> "/schema/" + request.collectionName + "/shards", // TODO: tenant support - (gson, request) -> null, request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> (List) JSON.deserialize(response, TypeToken.getParameterized( + (statusCode, response) -> (List) JSON.deserialize(response, TypeToken.getParameterized( List.class, Shard.class))); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java index 41b8b5aea..e3962e739 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java @@ -5,8 +5,6 @@ import java.util.Map; import java.util.function.Function; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.Generative; import io.weaviate.client6.v1.api.collections.InvertedIndex; @@ -16,16 +14,15 @@ 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 UpdateCollectionRequest(CollectionConfig collection) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( request -> "PUT", request -> "/schema/" + request.collection.collectionName(), - (gson, request) -> JSON.serialize(request.collection), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(request.collection)); public static UpdateCollectionRequest of(CollectionConfig collection, Function> fn) { diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateShardStatusRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateShardStatusRequest.java index 95431d273..53415c202 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateShardStatusRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateShardStatusRequest.java @@ -3,17 +3,14 @@ import java.util.Collections; import java.util.Map; -import org.apache.hc.core5.http.HttpStatus; - 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 UpdateShardStatusRequest(String collection, String shard, ShardStatus status) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( request -> "PUT", request -> "/schema/" + request.collection + "/shards/" + request.shard, - (gson, request) -> JSON.serialize(Map.of("status", request.status)), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(Map.of("status", request.status))); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java index a425fc2a8..217a27682 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java @@ -2,17 +2,13 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record DeleteObjectRequest(String collectionName, String uuid) { - public static final Endpoint _ENDPOINT = Endpoint.of( + public static final Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( request -> "DELETE", request -> "/objects/" + request.collectionName + "/" + request.uuid, - (gson, request) -> null, - request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_NO_CONTENT, - (gson, response) -> null); + request -> Collections.emptyMap()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java index f44515536..b1b460b11 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java @@ -3,8 +3,6 @@ import java.util.Collections; import java.util.function.Function; -import org.apache.hc.core5.http.HttpStatus; - import com.google.gson.reflect.TypeToken; import io.weaviate.client6.v1.api.collections.ObjectMetadata; @@ -14,20 +12,20 @@ import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record InsertObjectRequest(WeaviateObject object) { @SuppressWarnings("unchecked") public static final Endpoint, WeaviateObject> endpoint( CollectionDescriptor descriptor) { - return Endpoint.of( + return new SimpleEndpoint<>( request -> "POST", request -> "/objects/", - (gson, request) -> JSON.serialize(request.object, TypeToken.getParameterized( - WeaviateObject.class, descriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> JSON.deserialize(response, + request -> JSON.serialize(request.object, TypeToken.getParameterized( + WeaviateObject.class, descriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), + (statusCode, response) -> JSON.deserialize(response, (TypeToken>) TypeToken.getParameterized( WeaviateObject.class, descriptor.typeToken().getType(), Object.class, ObjectMetadata.class))); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddManyRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddManyRequest.java index 808a158c5..0530d23e9 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddManyRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddManyRequest.java @@ -4,22 +4,20 @@ import java.util.Collections; import java.util.List; -import org.apache.hc.core5.http.HttpStatus; - 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 ReferenceAddManyRequest(List references) { public static final Endpoint endpoint( List references) { - return Endpoint.of( + return new SimpleEndpoint<>( request -> "POST", request -> "/batch/references", - (gson, request) -> JSON.serialize(request.references), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> { + request -> JSON.serialize(request.references), + (statusCode, response) -> { var result = JSON.deserialize(response, ReferenceAddManyResponse.class); var errors = new ArrayList(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java index 834bc8e46..5da29e0fe 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java @@ -2,22 +2,19 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record ReferenceAddRequest(String fromUuid, String fromProperty, Reference reference) { public static final Endpoint endpoint( CollectionDescriptor descriptor) { - return Endpoint.of( + return SimpleEndpoint.sideEffect( request -> "POST", request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, - (gson, request) -> JSON.serialize(request.reference), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(request.reference)); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java index b43e491db..f7f037e23 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java @@ -2,22 +2,19 @@ import java.util.Collections; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record ReferenceDeleteRequest(String fromUuid, String fromProperty, Reference reference) { public static final Endpoint endpoint( CollectionDescriptor descriptor) { - return Endpoint.of( + return SimpleEndpoint.sideEffect( request -> "DELETE", request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, - (gson, request) -> JSON.serialize(request.reference), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(request.reference)); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java index bf148499c..746fe6966 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java @@ -3,22 +3,19 @@ import java.util.Collections; import java.util.List; -import org.apache.hc.core5.http.HttpStatus; - import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record ReferenceReplaceRequest(String fromUuid, String fromProperty, Reference reference) { public static final Endpoint endpoint( CollectionDescriptor descriptor) { - return Endpoint.of( + return SimpleEndpoint.sideEffect( request -> "PUT", request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, - (gson, request) -> JSON.serialize(List.of(request.reference)), request -> Collections.emptyMap(), - code -> code != HttpStatus.SC_SUCCESS, - (gson, response) -> null); + request -> JSON.serialize(List.of(request.reference))); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java index 1da3de392..09704839b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java @@ -12,18 +12,17 @@ import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record ReplaceObjectRequest(WeaviateObject object) { static final Endpoint, Void> endpoint(CollectionDescriptor collectionDescriptor) { - return Endpoint.of( + return SimpleEndpoint.sideEffect( request -> "PUT", request -> "/objects/" + collectionDescriptor.name() + "/" + request.object.metadata().uuid(), - (gson, request) -> JSON.serialize(request.object, TypeToken.getParameterized( - WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), request -> Collections.emptyMap(), - code -> code != 200, - (gson, response) -> null); + request -> JSON.serialize(request.object, TypeToken.getParameterized( + WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class))); } public static ReplaceObjectRequest of(String collectionName, String uuid, diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java index d9ccc0a09..f1f64022d 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java @@ -12,18 +12,17 @@ import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; public record UpdateObjectRequest(WeaviateObject object) { static final Endpoint, Void> endpoint(CollectionDescriptor collectionDescriptor) { - return Endpoint.of( + return SimpleEndpoint.sideEffect( request -> "PATCH", request -> "/objects/" + collectionDescriptor.name() + "/" + request.object.metadata().uuid(), - (gson, request) -> JSON.serialize(request.object, TypeToken.getParameterized( - WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), request -> Collections.emptyMap(), - code -> code != 204, - (gson, response) -> null); + request -> JSON.serialize(request.object, TypeToken.getParameterized( + WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class))); } public static UpdateObjectRequest of(String collectionName, String uuid, diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/BooleanEndpoint.java b/src/main/java/io/weaviate/client6/v1/internal/rest/BooleanEndpoint.java new file mode 100644 index 000000000..b0b20665e --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/BooleanEndpoint.java @@ -0,0 +1,24 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.util.Map; +import java.util.function.Function; + +public class BooleanEndpoint extends EndpointBase { + + public BooleanEndpoint( + Function method, + Function requestUrl, + Function> queryParameters, + Function body) { + super(method, requestUrl, queryParameters, body); + } + + @Override + public boolean isError(int statusCode) { + return statusCode != 404 && super.isError(statusCode); + } + + public boolean getResult(int statusCode) { + return statusCode < 400; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java index 396eb3b1f..d89d96036 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java @@ -20,22 +20,20 @@ import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.io.CloseMode; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import io.weaviate.client6.v1.api.WeaviateApiException; public class DefaultRestTransport implements RestTransport { private final CloseableHttpClient httpClient; private final CloseableHttpAsyncClient httpClientAsync; private final RestTransportOptions transportOptions; - // TODO: retire - private static final Gson gson = new GsonBuilder().create(); - public DefaultRestTransport(RestTransportOptions transportOptions) { this.transportOptions = transportOptions; @@ -78,18 +76,38 @@ public DefaultRestTransport(RestTransportOptions transportOptions) { } @Override - public ResponseT performRequest(RequestT request, Endpoint endpoint) + public ResponseT performRequest(RequestT request, + Endpoint endpoint) throws IOException { var req = prepareClassicRequest(request, endpoint); - // FIXME: we need to differentiate between "no body" and "soumething's wrong" - return this.httpClient.execute(req, - response -> response.getEntity() != null - ? endpoint.deserializeResponse(gson, EntityUtils.toString(response.getEntity())) - : null); + return this.httpClient.execute(req, r -> this.handleResponse(endpoint, req.getMethod(), req.getRequestUri(), r)); + } + + private ClassicHttpRequest prepareClassicRequest(RequestT request, + Endpoint endpoint) { + var method = endpoint.method(request); + var uri = transportOptions.baseUrl() + endpoint.requestUrl(request); + + // TODO: apply options; + var req = ClassicRequestBuilder.create(method).setUri(uri); + var body = endpoint.body(request); + if (body != null) { + req.setEntity(body, ContentType.APPLICATION_JSON); + } + return req.build(); + } + + private ResponseT handleResponse(Endpoint endpoint, String method, String url, + ClassicHttpResponse httpResponse) throws IOException, ParseException { + var statusCode = httpResponse.getCode(); + var body = httpResponse.getEntity() != null + ? EntityUtils.toString(httpResponse.getEntity()) + : ""; + return _handleResponse(endpoint, method, url, statusCode, body); } @Override - public CompletableFuture performRequestAsync(RequestT request, + public CompletableFuture performRequestAsync(RequestT request, Endpoint endpoint) { var req = prepareSimpleRequest(request, endpoint); @@ -112,18 +130,18 @@ public void cancelled() { } }); - // FIXME: we need to differentiate between "no body" and "soumething's wrong" - return completable.thenApply(r -> r.getBody() != null - ? endpoint.deserializeResponse(gson, r.getBody().getBodyText()) - : null); + return completable + .thenApply(r -> (ResponseT) handleResponseAsync(endpoint, + req.getMethod(), req.getRequestUri(), r)); } - private SimpleHttpRequest prepareSimpleRequest(RequestT request, Endpoint endpoint) { + private SimpleHttpRequest prepareSimpleRequest(RequestT request, + Endpoint endpoint) { var method = endpoint.method(request); var uri = transportOptions.baseUrl() + endpoint.requestUrl(request); // TODO: apply options; - var body = endpoint.body(gson, request); + var body = endpoint.body(request); var req = SimpleHttpRequest.create(method, uri); if (body != null) { req.setBody(body.getBytes(), ContentType.APPLICATION_JSON); @@ -131,17 +149,36 @@ private SimpleHttpRequest prepareSimpleRequest(RequestT request, Endp return req; } - private ClassicHttpRequest prepareClassicRequest(RequestT request, Endpoint endpoint) { - var method = endpoint.method(request); - var uri = transportOptions.baseUrl() + endpoint.requestUrl(request); + private ResponseT handleResponseAsync( + Endpoint endpoint, + String method, String url, + SimpleHttpResponse httpResponse) { + var statusCode = httpResponse.getCode(); + var body = httpResponse.getBody() != null + ? httpResponse.getBody().getBodyText() + : ""; + return _handleResponse(endpoint, method, url, statusCode, body); + } - // TODO: apply options; - var req = ClassicRequestBuilder.create(method).setUri(uri); - var body = endpoint.body(gson, request); - if (body != null) { - req.setEntity(body, ContentType.APPLICATION_JSON); + private ResponseT _handleResponse(Endpoint endpoint, String method, String url, + int statusCode, String body) { + if (endpoint.isError(statusCode)) { + var message = endpoint.deserializeError(statusCode, body); + throw new WeaviateApiException(method, url, statusCode, message); } - return req.build(); + + if (endpoint instanceof JsonEndpoint json) { + @SuppressWarnings("unchecked") + ResponseT response = (ResponseT) json.deserializeResponse(statusCode, body); + return response; + } else if (endpoint instanceof BooleanEndpoint bool) { + @SuppressWarnings("unchecked") + ResponseT response = (ResponseT) ((Boolean) bool.getResult(statusCode)); + return response; + } + + // TODO: make it a WeaviateTransportException + throw new RuntimeException("Unhandled endpoint type " + endpoint.getClass().getSimpleName()); } @Override diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/Endpoint.java b/src/main/java/io/weaviate/client6/v1/internal/rest/Endpoint.java index 7c8998a61..52cc37c3b 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/Endpoint.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/Endpoint.java @@ -1,10 +1,6 @@ package io.weaviate.client6.v1.internal.rest; import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -import com.google.gson.Gson; public interface Endpoint { @@ -12,54 +8,12 @@ public interface Endpoint { String requestUrl(RequestT request); - // Gson is leaking. - String body(Gson gson, RequestT request); + String body(RequestT request); Map queryParameters(RequestT request); /** Should this status code be considered an error? */ - boolean isError(int code); - - ResponseT deserializeResponse(Gson gson, String response); - - public static Endpoint of( - Function method, - Function requestUrl, - BiFunction body, - Function> queryParameters, - Function isError, - BiFunction deserialize) { - return new Endpoint() { - - @Override - public String method(RequestT request) { - return method.apply(request); - } - - @Override - public String requestUrl(RequestT request) { - return requestUrl.apply(request); - } - - @Override - public String body(Gson gson, RequestT request) { - return body.apply(gson, request); - } - - @Override - public Map queryParameters(RequestT request) { - return queryParameters.apply(request); - } - - @Override - public ResponseT deserializeResponse(Gson gson, String response) { - return deserialize.apply(gson, response); - } + boolean isError(int statusCode); - @Override - public boolean isError(int code) { - return isError.apply(code); - } - }; - } + String deserializeError(int statusCode, String responseBody); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java b/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java new file mode 100644 index 000000000..673790b8c --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java @@ -0,0 +1,60 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.util.Map; +import java.util.function.Function; + +public abstract class EndpointBase implements Endpoint { + private static final Function NULL_BODY = __ -> null; + + protected final Function method; + protected final Function requestUrl; + protected final Function body; + protected final Function> queryParameters; + + @SuppressWarnings("unchecked") + protected static Function nullBody() { + return (Function) NULL_BODY; + } + + public EndpointBase( + Function method, + Function requestUrl, + Function> queryParameters, + Function body) { + this.method = method; + this.requestUrl = requestUrl; + this.body = body; + this.queryParameters = queryParameters; + } + + @Override + public String method(RequestT request) { + return method.apply(request); + } + + @Override + public String requestUrl(RequestT request) { + return requestUrl.apply(request); + } + + @Override + public Map queryParameters(RequestT request) { + return queryParameters.apply(request); + } + + @Override + public String body(RequestT request) { + return body.apply(request); + } + + @Override + public boolean isError(int statusCode) { + return statusCode >= 400; + } + + @Override + public String deserializeError(int statusCode, String responseBody) { + // TODO: deserialize + return responseBody; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/JsonEndpoint.java b/src/main/java/io/weaviate/client6/v1/internal/rest/JsonEndpoint.java new file mode 100644 index 000000000..9e04896c4 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/JsonEndpoint.java @@ -0,0 +1,7 @@ +package io.weaviate.client6.v1.internal.rest; + +/** An Endpoint which expects a JSON response body. */ +public interface JsonEndpoint + extends Endpoint { + ResponseT deserializeResponse(int statusCode, String responseBody); +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/OptionalEndpoint.java b/src/main/java/io/weaviate/client6/v1/internal/rest/OptionalEndpoint.java new file mode 100644 index 000000000..0b6052573 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/OptionalEndpoint.java @@ -0,0 +1,38 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class OptionalEndpoint extends SimpleEndpoint> { + + public static OptionalEndpoint noBodyOptional( + Function method, + Function requestUrl, + Function> queryParameters, + BiFunction deserializeResponse) { + return new OptionalEndpoint<>(method, requestUrl, queryParameters, nullBody(), deserializeResponse); + } + + public OptionalEndpoint( + Function method, + Function requestUrl, + Function> queryParameters, + Function body, + BiFunction deserializeResponse) { + super(method, requestUrl, queryParameters, body, optional(deserializeResponse)); + } + + private static BiFunction> optional( + BiFunction deserializeResponse) { + return (statusCode, responseBody) -> statusCode == 404 + ? Optional.empty() + : Optional.ofNullable(deserializeResponse.apply(statusCode, responseBody)); + } + + @Override + public boolean isError(int statusCode) { + return statusCode != 404 && super.isError(statusCode); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransport.java index b20c98fbd..1fc25836b 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransport.java @@ -5,9 +5,10 @@ import java.util.concurrent.CompletableFuture; public interface RestTransport extends Closeable { - ResponseT performRequest(RequestT request, Endpoint endpoint) + ResponseT performRequest(RequestT request, + Endpoint endpoint) throws IOException; - CompletableFuture performRequestAsync(RequestT request, + CompletableFuture performRequestAsync(RequestT request, Endpoint endpoint); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/SimpleEndpoint.java b/src/main/java/io/weaviate/client6/v1/internal/rest/SimpleEndpoint.java new file mode 100644 index 000000000..9f5c6fa9c --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/SimpleEndpoint.java @@ -0,0 +1,54 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class SimpleEndpoint extends EndpointBase + implements JsonEndpoint { + private static final BiFunction NULL_RESPONSE = (__code, __body) -> null; + + private final BiFunction deserializeResponse; + + protected static BiFunction nullResponse() { + return NULL_RESPONSE; + } + + public static SimpleEndpoint noBody( + Function method, + Function requestUrl, + Function> queryParameters, + BiFunction deserializeResponse) { + return new SimpleEndpoint<>(method, requestUrl, queryParameters, nullBody(), deserializeResponse); + } + + public static SimpleEndpoint sideEffect( + Function method, + Function requestUrl, + Function> queryParameters, + Function body) { + return new SimpleEndpoint<>(method, requestUrl, queryParameters, body, nullResponse()); + } + + public static SimpleEndpoint sideEffect( + Function method, + Function requestUrl, + Function> queryParameters) { + return new SimpleEndpoint<>(method, requestUrl, queryParameters, nullBody(), nullResponse()); + } + + public SimpleEndpoint( + Function method, + Function requestUrl, + Function> queryParameters, + Function body, + BiFunction deserializeResponse) { + super(method, requestUrl, queryParameters, body); + this.deserializeResponse = deserializeResponse; + } + + @Override + public ResponseT deserializeResponse(int statusCode, String responseBody) { + return deserializeResponse.apply(statusCode, responseBody); + } +} diff --git a/src/test/java/io/weaviate/client6/v1/api/AuthorizationTest.java b/src/test/java/io/weaviate/client6/v1/api/AuthorizationTest.java index fcd74fe44..8c4d375e0 100644 --- a/src/test/java/io/weaviate/client6/v1/api/AuthorizationTest.java +++ b/src/test/java/io/weaviate/client6/v1/api/AuthorizationTest.java @@ -10,7 +10,7 @@ import org.mockserver.model.HttpRequest; import io.weaviate.client6.v1.internal.rest.DefaultRestTransport; -import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.OptionalEndpoint; import io.weaviate.client6.v1.internal.rest.RestTransportOptions; public class AuthorizationTest { @@ -36,13 +36,8 @@ public void testAuthorization_apiKey() throws IOException { Collections.emptyMap(), Authorization.apiKey("my-api-key"), null); try (final var restClient = new DefaultRestTransport(transportOptions)) { - restClient.performRequest(null, Endpoint.of( - request -> "GET", - request -> "/", - (gson, request) -> null, - request -> null, - code -> code != 200, - (gson, response) -> null)); + restClient.performRequest(null, OptionalEndpoint.noBodyOptional( + request -> "GET", request -> "/", request -> null, (code, response) -> null)); } mockServer.verify( diff --git a/src/test/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransportTest.java b/src/test/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransportTest.java index ab58ac5be..cf6de61a8 100644 --- a/src/test/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransportTest.java +++ b/src/test/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransportTest.java @@ -42,13 +42,8 @@ public void setUp() throws IOException { @Test public void testCustomTrustStore_sync() throws IOException { - transport.performRequest(null, Endpoint.of( - request -> "GET", - request -> "/", - (gson, request) -> null, - request -> null, - code -> code != 200, - (gson, response) -> null)); + transport.performRequest(null, OptionalEndpoint.noBodyOptional( + request -> "GET", request -> "/", request -> null, (code, response) -> null)); mockServer.verify( HttpRequest.request() @@ -63,13 +58,8 @@ public void testCustomTrustStore_sync() throws IOException { @Test public void testCustomTrustStore_async() throws IOException, ExecutionException, InterruptedException { - transport.performRequestAsync(null, Endpoint.of( - request -> "GET", - request -> "/", - (gson, request) -> null, - request -> null, - code -> code != 200, - (gson, response) -> null)).get(); + transport.performRequestAsync(null, OptionalEndpoint.noBodyOptional( + request -> "GET", request -> "/", request -> null, (code, response) -> null)).get(); mockServer.verify( HttpRequest.request() From 51fe2964f56741606443ce66e0f35b6e67499650 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 12:10:51 +0200 Subject: [PATCH 03/10] test: expect WeaviateApiException on a bad request --- .../weaviate/integration/CollectionsITest.java | 6 ++++++ .../io/weaviate/integration/DataITest.java | 14 ++++++++++++++ .../client6/v1/api/WeaviateApiException.java | 2 +- .../client6/v1/internal/rest/EndpointBase.java | 18 ++++++++++++++++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/it/java/io/weaviate/integration/CollectionsITest.java b/src/it/java/io/weaviate/integration/CollectionsITest.java index 8037deb84..072bb30cb 100644 --- a/src/it/java/io/weaviate/integration/CollectionsITest.java +++ b/src/it/java/io/weaviate/integration/CollectionsITest.java @@ -7,6 +7,7 @@ import org.junit.Test; import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.InvertedIndex; @@ -185,4 +186,9 @@ public void testShards() throws IOException { .extracting(Shard::status) .containsOnly(wantStatus.name()); } + + @Test(expected = WeaviateApiException.class) + public void testInvalidCollectionName() throws IOException { + client.collections.create("^collection@weaviate.io$"); + } } diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index 8b5736fe6..22071e321 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -9,6 +9,7 @@ import org.junit.Test; import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.Vectorizers; @@ -393,4 +394,17 @@ public void testReferenceAddMany() throws IOException { .extracting(WeaviateObject::uuid) .contains(alpha, bravo, charlie); } + + @Test(expected = WeaviateApiException.class) + public void testDuplicateUuid() throws IOException { + // Arrange + var nsThings = ns("Things"); + + client.collections.create(nsThings); + var things = client.collections.use(nsThings); + var thing_1 = things.data.insert(Map.of()); + + // Act + things.data.insert(Map.of(), thing -> thing.uuid(thing_1.uuid())); + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index 6e9dc96c1..75cb22e3a 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -10,7 +10,7 @@ public class WeaviateApiException extends RuntimeException { private final int statusCode; public WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) { - super("%s %s: %s".formatted(method, endpoint, errorMessage)); + super("HTTP %d: %s %s: %s".formatted(statusCode, method, endpoint, errorMessage)); this.endpoint = endpoint; this.statusCode = statusCode; } diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java b/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java index 673790b8c..2ebe61d6d 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/EndpointBase.java @@ -1,8 +1,13 @@ package io.weaviate.client6.v1.internal.rest; +import java.util.List; import java.util.Map; import java.util.function.Function; +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.internal.json.JSON; + public abstract class EndpointBase implements Endpoint { private static final Function NULL_BODY = __ -> null; @@ -54,7 +59,16 @@ public boolean isError(int statusCode) { @Override public String deserializeError(int statusCode, String responseBody) { - // TODO: deserialize - return responseBody; + var response = JSON.deserialize(responseBody, ErrorResponse.class); + if (response.errors.isEmpty()) { + return ""; + + } + return response.errors.get(0).text(); + } + + static record ErrorResponse(@SerializedName("error") List errors) { + private static record ErrorMessage(@SerializedName("message") String text) { + } } } From 01856dbc0479ee12137a2cbbec6f9b18d81208a9 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 13:25:56 +0200 Subject: [PATCH 04/10] feat: rethrow io.grpc.StatusRuntimeException as WeaviateApiException --- .../io/weaviate/integration/SearchITest.java | 40 +++++++++++++++++++ .../client6/v1/api/WeaviateApiException.java | 34 ++++++++++++++-- .../internal/grpc/DefaultGrpcTransport.java | 14 ++++++- .../internal/rest/DefaultRestTransport.java | 2 +- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index ed57a6795..48cde4347 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -16,6 +16,7 @@ import org.junit.rules.TestRule; import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.Vectorizers; @@ -374,4 +375,43 @@ public void testHybrid() throws IOException { Assertions.assertThat(first.metadata().explainScore()) .as("metadata::explainScore").isNotNull(); } + + @Test(expected = WeaviateApiException.class) + public void testBadRequest() throws IOException { + // Arrange + var nsThings = ns("Things"); + + client.collections.create(nsThings, + collection -> collection + .properties(Property.text("name")) + .vectors(Vectorizers.text2vecContextionary())); + + var things = client.collections.use(nsThings); + var balloon = things.data.insert(Map.of("name", "balloon")); + + things.query.nearObject(balloon.uuid(), q -> q.limit(-1)); + } + + @Test(expected = WeaviateApiException.class) + public void testBadRequest_async() throws Throwable { + // Arrange + var nsThings = ns("Things"); + + try (final var async = client.async()) { + async.collections.create(nsThings, + collection -> collection + .properties(Property.text("name")) + .vectors(Vectorizers.text2vecContextionary())) + .get(); + + var things = async.collections.use(nsThings); + var balloon = things.data.insert(Map.of("name", "balloon")).get(); + + try { + things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).get(); + } catch (ExecutionException e) { + throw e.getCause(); + } + } + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index 75cb22e3a..d86f85dd9 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -6,20 +6,48 @@ * a bad request or a server error. */ public class WeaviateApiException extends RuntimeException { + private final String errorMessage; + private final Source source; private final String endpoint; - private final int statusCode; + private final Integer statusCode; + private final String grpcStatus; - public WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) { + private enum Source { + HTTP, GRPC; + }; + + public static WeaviateApiException http(String method, String endpoint, int statusCode, String errorMessage) { + return new WeaviateApiException(method, endpoint, statusCode, errorMessage); + } + + public static WeaviateApiException gRPC(io.grpc.StatusRuntimeException ex) { + var status = ex.getStatus(); + return new WeaviateApiException(status.getCode().toString(), status.getDescription()); + } + + private WeaviateApiException(String status, String errorMessage) { + super("%s: %s".formatted(status, errorMessage)); + this.source = Source.GRPC; + this.errorMessage = errorMessage; + this.grpcStatus = status; + this.endpoint = null; + this.statusCode = null; + } + + private WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) { super("HTTP %d: %s %s: %s".formatted(statusCode, method, endpoint, errorMessage)); + this.source = Source.HTTP; + this.errorMessage = errorMessage; this.endpoint = endpoint; this.statusCode = statusCode; + this.grpcStatus = null; } public String endpoint() { return endpoint; } - public int statusCode() { + public Integer statusCode() { return statusCode; } } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java index 75893b06b..7655db36a 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java @@ -10,10 +10,12 @@ import com.google.common.util.concurrent.ListenableFuture; import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.stub.MetadataUtils; +import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc.WeaviateBlockingStub; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc.WeaviateFutureStub; @@ -48,8 +50,12 @@ public ResponseT performRequest(RequestT Rpc rpc) { var message = rpc.marshal(request); var method = rpc.method(); - var reply = method.apply(blockingStub, message); - return rpc.unmarshal(reply); + try { + var reply = method.apply(blockingStub, message); + return rpc.unmarshal(reply); + } catch (io.grpc.StatusRuntimeException e) { + throw WeaviateApiException.gRPC(e); + } } @Override @@ -76,6 +82,10 @@ public void onSuccess(T result) { @Override public void onFailure(Throwable t) { + if (t instanceof StatusRuntimeException e) { + completable.completeExceptionally(WeaviateApiException.gRPC(e)); + return; + } completable.completeExceptionally(t); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java index d89d96036..bcd5a07ab 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java @@ -164,7 +164,7 @@ private ResponseT _handleResponse(Endpoint endpoint, S int statusCode, String body) { if (endpoint.isError(statusCode)) { var message = endpoint.deserializeError(statusCode, body); - throw new WeaviateApiException(method, url, statusCode, message); + throw WeaviateApiException.http(method, url, statusCode, message); } if (endpoint instanceof JsonEndpoint json) { From 12bf8e601977dd6e33a2e1fd3e2e93ba173496da Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 13:26:33 +0200 Subject: [PATCH 05/10] chore: add debug util for printing protobuf messages --- .../weaviate/client6/v1/internal/Debug.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/io/weaviate/client6/v1/internal/Debug.java diff --git a/src/main/java/io/weaviate/client6/v1/internal/Debug.java b/src/main/java/io/weaviate/client6/v1/internal/Debug.java new file mode 100644 index 000000000..8d3702d61 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/Debug.java @@ -0,0 +1,27 @@ +package io.weaviate.client6.v1.internal; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.util.JsonFormat; + +/** Debug utilities. */ +public final class Debug { + public static final void printProto(Object proto) { + System.out.println(proto2json((MessageOrBuilder) proto)); + } + + public static final void printProto(Object proto, String message, Object... args) { + System.out.println(message.formatted(args) + ": " + proto2json((MessageOrBuilder) proto)); + } + + private static final String proto2json(MessageOrBuilder proto) { + String out; + try { + out = JsonFormat.printer().print(proto); + } catch (InvalidProtocolBufferException e) { + out = e.getMessage(); + } + + return out; + } +} From 73dfa06db8472dedf9a156b24615de880c6495ae Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 16:18:43 +0200 Subject: [PATCH 06/10] feat: add additional information for exceptions during pagination --- .../weaviate/integration/PaginationITest.java | 34 ++++++++++++++++- .../io/weaviate/integration/SearchITest.java | 11 +++--- .../client6/v1/api/WeaviateApiException.java | 38 +++++++++++++------ .../api/collections/pagination/AsyncPage.java | 9 +++++ .../pagination/AsyncPaginator.java | 27 +++++++++---- .../api/collections/pagination/Paginator.java | 9 ++++- .../WeaviatePaginationException.java | 31 +++++++++++++++ 7 files changed, 133 insertions(+), 26 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java diff --git a/src/it/java/io/weaviate/integration/PaginationITest.java b/src/it/java/io/weaviate/integration/PaginationITest.java index 961ad4df1..a68672245 100644 --- a/src/it/java/io/weaviate/integration/PaginationITest.java +++ b/src/it/java/io/weaviate/integration/PaginationITest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -18,6 +19,7 @@ import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.WeaviateMetadata; import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.api.collections.pagination.WeaviatePaginationException; import io.weaviate.containers.Container; public class PaginationITest extends ConcurrentTest { @@ -89,7 +91,7 @@ public void testResumePagination() throws IOException { .reduce((prev, next) -> next).get(); // Act - var remaining = things.paginate(p -> p.resumeFrom(lastId)).stream().count(); + var remaining = things.paginate(p -> p.fromCursor(lastId)).stream().count(); // Assert Assertions.assertThat(remaining).isEqualTo(5); @@ -157,4 +159,34 @@ public void testAsyncPaginator() throws IOException, InterruptedException, Execu .isEqualTo(count); } } + + @Test(expected = WeaviatePaginationException.class) + public void testFailedPagination() throws IOException { + var things = client.collections.use("Unknown"); + things.paginate().forEach(System.out::println); + } + + @Test(expected = WeaviatePaginationException.class) + public void testFailedAsyncPagination_forEach() throws Throwable { + try (final var async = client.async()) { + var things = async.collections.use("Unknown"); + try { + things.paginate().forEach(__ -> System.out.println("called once")).join(); + } catch (CompletionException e) { + throw e.getCause(); // CompletableFuture exceptions are always wrapped + } + } + } + + @Test(expected = WeaviatePaginationException.class) + public void testFailedAsyncPagination_forPage() throws Throwable { + try (final var async = client.async()) { + var things = async.collections.use("Unknown"); + try { + things.paginate().forPage(__ -> System.out.println("called once")).join(); + } catch (CompletionException e) { + throw e.getCause(); // CompletableFuture exceptions are always wrapped + } + } + } } diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 48cde4347..d4c132e0e 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import org.assertj.core.api.Assertions; @@ -402,15 +403,15 @@ public void testBadRequest_async() throws Throwable { collection -> collection .properties(Property.text("name")) .vectors(Vectorizers.text2vecContextionary())) - .get(); + .join(); var things = async.collections.use(nsThings); - var balloon = things.data.insert(Map.of("name", "balloon")).get(); + var balloon = things.data.insert(Map.of("name", "balloon")).join(); try { - things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).get(); - } catch (ExecutionException e) { - throw e.getCause(); + things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).join(); + } catch (CompletionException e) { + throw e.getCause(); // CompletableFuture exceptions are always wrapped } } } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index d86f85dd9..a6207b199 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -9,8 +9,8 @@ public class WeaviateApiException extends RuntimeException { private final String errorMessage; private final Source source; private final String endpoint; - private final Integer statusCode; - private final String grpcStatus; + private final Integer httpStatusCode; + private final io.grpc.Status.Code grpcStatusCode; private enum Source { HTTP, GRPC; @@ -22,16 +22,16 @@ public static WeaviateApiException http(String method, String endpoint, int stat public static WeaviateApiException gRPC(io.grpc.StatusRuntimeException ex) { var status = ex.getStatus(); - return new WeaviateApiException(status.getCode().toString(), status.getDescription()); + return new WeaviateApiException(status.getCode(), status.getDescription()); } - private WeaviateApiException(String status, String errorMessage) { - super("%s: %s".formatted(status, errorMessage)); + private WeaviateApiException(io.grpc.Status.Code code, String errorMessage) { + super("%s: %s".formatted(code, errorMessage)); this.source = Source.GRPC; this.errorMessage = errorMessage; - this.grpcStatus = status; + this.grpcStatusCode = code; this.endpoint = null; - this.statusCode = null; + this.httpStatusCode = null; } private WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) { @@ -39,15 +39,31 @@ private WeaviateApiException(String method, String endpoint, int statusCode, Str this.source = Source.HTTP; this.errorMessage = errorMessage; this.endpoint = endpoint; - this.statusCode = statusCode; - this.grpcStatus = null; + this.httpStatusCode = statusCode; + this.grpcStatusCode = null; + } + + public boolean isGPRC() { + return source == Source.GRPC; + } + + public String grpcStatusCode() { + return grpcStatusCode.toString(); + } + + public boolean isHTTP() { + return source == Source.HTTP; } public String endpoint() { return endpoint; } - public Integer statusCode() { - return statusCode; + public Integer httpStatusCode() { + return httpStatusCode; + } + + public String getError() { + return errorMessage; } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPage.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPage.java index 54f45f945..b4deb0bf2 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPage.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPage.java @@ -40,6 +40,15 @@ public boolean isEmpty() { return this.currentPage.isEmpty(); } + /** + * Fetch an {@link AsyncPage} containing the next {@code pageSize} results + * and advance the cursor. + * + *

+ * The returned stage may complete exceptionally in case the underlying + * query fails. Callers are advised to use exception-aware + * {@link CompletableFuture#handle} to process page results. + */ public CompletableFuture> fetchNextPage() { return fetch.apply(cursor, pageSize) .thenApply(nextPage -> { diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java index 308a0327e..c5de3bf0e 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java @@ -31,9 +31,16 @@ public AsyncPaginator(Builder builder) { var rs = new AsyncPage( cursor, pageSize, - (after, limit) -> { - var fn = ObjectBuilder.partial(queryOptions, q -> q.after(after).limit(limit)); - return this.query.fetchObjects(fn).thenApply(QueryResponse::objects); + (cursor, pageSize) -> { + var fn = ObjectBuilder.partial(queryOptions, q -> q.after(cursor).limit(pageSize)); + return this.query.fetchObjects(fn) + .handle((response, ex) -> { + if (ex != null) { + throw WeaviatePaginationException.after(cursor, pageSize, ex); + } + return response; + }) + .thenApply(QueryResponse::objects); }); this.resultSet = builder.prefetch ? rs.fetchNextPage() : CompletableFuture.completedFuture(rs); @@ -51,17 +58,17 @@ public CompletableFuture forPage(Consumer, CompletableFuture> processEachAndAdvance( + private static Function, CompletableFuture> processEachAndAdvance( Consumer> action) { return processAndAdvanceFunc(rs -> rs.forEach(action)); } - public Function, CompletableFuture> processPageAndAdvance( + private static Function, CompletableFuture> processPageAndAdvance( Consumer>> action) { return processAndAdvanceFunc(rs -> action.accept(rs.items())); } - public Function, CompletableFuture> processAndAdvanceFunc( + private static Function, CompletableFuture> processAndAdvanceFunc( Consumer> action) { return rs -> { // Empty result set means there were no more objects to fetch. @@ -105,11 +112,17 @@ public Builder pageSize(int pageSize) { return this; } - public Builder resumeFrom(String uuid) { + /** Set a cursor (object UUID) to start pagination from. */ + public Builder fromCursor(String uuid) { this.cursor = uuid; return this; } + /** + * When prefetch is enabled, the first page is retrieved before any of the + * terminating methods ({@link AsyncPaginator#forEach}, + * {@link AsyncPaginator#forPage}) are called on the paginator. + */ public Builder prefetch(boolean enable) { this.prefetch = enable; return this; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java index 213448e34..edfb8bcd2 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java @@ -37,7 +37,11 @@ public Spliterator> spliterat return new CursorSpliterator(cursor, pageSize, (after, limit) -> { var fn = ObjectBuilder.partial(queryOptions, q -> q.after(after).limit(limit)); - return query.fetchObjects(fn).objects(); + try { + return query.fetchObjects(fn).objects(); + } catch (Exception e) { + throw WeaviatePaginationException.after(cursor, pageSize, e); + } }); } @@ -75,7 +79,8 @@ public Builder pageSize(int pageSize) { return this; } - public Builder resumeFrom(String uuid) { + /** Set a cursor (object UUID) to start pagination from. */ + public Builder fromCursor(String uuid) { this.cursor = uuid; return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java new file mode 100644 index 000000000..92ee97e8d --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java @@ -0,0 +1,31 @@ +package io.weaviate.client6.v1.api.collections.pagination; + +/** + * WeaviatePaginationException is thrown then the client encouters an exception + * while fetching the next page. This exception preserves the original exception + * (see {@link #getCause} and the information about the last cursor and page + * size used (see {@link #cursor()} and {@link #pageSize()} respectively). + */ +public class WeaviatePaginationException extends RuntimeException { + private final String cursor; + private final int pageSize; + + public static WeaviatePaginationException after(String cursor, int pageSize, Throwable cause) { + return new WeaviatePaginationException(cursor, pageSize, cause); + } + + private WeaviatePaginationException(String cursor, int pageSize, Throwable cause) { + super("fetch next page, page_size=%d cursor=%s".formatted(pageSize, cursor), cause); + this.cursor = cursor; + this.pageSize = pageSize; + } + + /** A null-cursor means the error happened while fetching the first page. */ + public String cursor() { + return cursor; + } + + public int pageSize() { + return pageSize; + } +} From bbb43773c79f2355ccf29e32062dcebb8b099544 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 16:46:16 +0200 Subject: [PATCH 07/10] chore: remove deprecated vectorizer parameter --- .../vectorizers/Multi2VecClipVectorizer.java | 8 ------ .../Text2VecContextionaryVectorizer.java | 26 ++++++++++++++----- .../Text2VecWeaviateVectorizer.java | 9 +------ .../client6/v1/internal/json/JSONTest.java | 13 +++------- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java index 440ce7a31..60fc5b87b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java @@ -13,7 +13,6 @@ import io.weaviate.client6.v1.internal.ObjectBuilder; public record Multi2VecClipVectorizer( - @SerializedName("vectorizeClassName") boolean vectorizeCollectionName, @SerializedName("inferenceUrl") String inferenceUrl, @SerializedName("imageFields") List imageFields, @SerializedName("textFields") List textFields, @@ -45,7 +44,6 @@ public static Multi2VecClipVectorizer of(Function { private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; - private boolean vectorizeCollectionName = false; private String inferenceUrl; private Map imageFields = new HashMap<>(); private Map textFields = new HashMap<>(); @@ -95,11 +92,6 @@ public Builder textField(String field, float weight) { return this; } - public Builder vectorizeCollectionName(boolean enable) { - this.vectorizeCollectionName = enable; - return this; - } - public Builder vectorIndex(VectorIndex vectorIndex) { this.vectorIndex = vectorIndex; return this; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java index aa2550e30..aa53dc085 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java @@ -9,7 +9,15 @@ import io.weaviate.client6.v1.internal.ObjectBuilder; public record Text2VecContextionaryVectorizer( - @SerializedName("vectorizeClassName") boolean vectorizeCollectionName, + /** + * Weaviate defaults to {@code true} if the value is not provided. + * Because text2vec-contextionary cannot handle understores in collection names, + * this quickly becomes inconvenient. + * + * To avoid that we send "vectorizeClassName": false all the time + * and make it impossible to enable this feature, as it is deprecated. + */ + @Deprecated @SerializedName("vectorizeClassName") boolean vectorizeCollectionName, VectorIndex vectorIndex) implements Vectorizer { @Override @@ -31,18 +39,22 @@ public static Text2VecContextionaryVectorizer of( return fn.apply(new Builder()).build(); } + /** + * Canonical constructor always sets {@link #vectorizeCollectionName} to false. + */ + public Text2VecContextionaryVectorizer(boolean vectorizeCollectionName, VectorIndex vectorIndex) { + this.vectorizeCollectionName = false; + this.vectorIndex = vectorIndex; + } + public Text2VecContextionaryVectorizer(Builder builder) { this(builder.vectorizeCollectionName, builder.vectorIndex); } public static class Builder implements ObjectBuilder { - private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; - private boolean vectorizeCollectionName = false; + private final boolean vectorizeCollectionName = false; - public Builder vectorizeCollectionName(boolean enable) { - this.vectorizeCollectionName = enable; - return this; - } + private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; public Builder vectorIndex(VectorIndex vectorIndex) { this.vectorIndex = vectorIndex; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java index 5d50ade0b..a8c9e7bd3 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java @@ -9,7 +9,6 @@ import io.weaviate.client6.v1.internal.ObjectBuilder; public record Text2VecWeaviateVectorizer( - @SerializedName("vectorizeClassName") boolean vectorizeCollectionName, @SerializedName("baseUrl") String inferenceUrl, @SerializedName("dimensions") Integer dimensions, @SerializedName("model") String model, @@ -34,7 +33,7 @@ public static Text2VecWeaviateVectorizer of(Function { private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; - private boolean vectorizeCollectionName = false; private String inferenceUrl; private Integer dimensions; private String model; - public Builder vectorizeCollectionName(boolean enable) { - this.vectorizeCollectionName = enable; - return this; - } - public Builder inferenceUrl(String inferenceUrl) { this.inferenceUrl = inferenceUrl; return this; diff --git a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java index a4b7f5621..56e189f2e 100644 --- a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java +++ b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java @@ -73,8 +73,7 @@ public static Object[][] testCases() { Multi2VecClipVectorizer.of(m2v -> m2v .inferenceUrl("http://example.com") .imageField("img", 1f) - .textField("txt", 2f) - .vectorizeCollectionName(true)), + .textField("txt", 2f)), """ { "vectorIndexType": "hnsw", @@ -82,7 +81,6 @@ public static Object[][] testCases() { "vectorizer": { "multi2vec-clip": { "inferenceUrl": "http://example.com", - "vectorizeClassName": true, "imageFields": ["img"], "textFields": ["txt"], "weights": { @@ -96,15 +94,14 @@ public static Object[][] testCases() { }, { Vectorizer.class, - Text2VecContextionaryVectorizer.of(t2v -> t2v - .vectorizeCollectionName(true)), + Text2VecContextionaryVectorizer.of(), """ { "vectorIndexType": "hnsw", "vectorIndexConfig": {}, "vectorizer": { "text2vec-contextionary": { - "vectorizeClassName": true + "vectorizeClassName": false } } } @@ -115,8 +112,7 @@ public static Object[][] testCases() { Text2VecWeaviateVectorizer.of(t2v -> t2v .inferenceUrl("http://example.com") .dimensions(4) - .model("very-good-model") - .vectorizeCollectionName(true)), + .model("very-good-model")), """ { "vectorIndexType": "hnsw", @@ -124,7 +120,6 @@ public static Object[][] testCases() { "vectorizer": { "text2vec-weaviate": { "baseUrl": "http://example.com", - "vectorizeClassName": true, "dimensions": 4, "model": "very-good-model" } From a667584f025d4b9391b282b29420eb66d0c07069 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 5 Aug 2025 18:01:24 +0200 Subject: [PATCH 08/10] feat: provide base WeaviateException class for ergonomics --- .../weaviate/integration/PaginationITest.java | 9 +++-- .../client6/v1/api/WeaviateApiException.java | 2 +- .../client6/v1/api/WeaviateException.java | 38 +++++++++++++++++++ .../pagination/AsyncPaginator.java | 2 +- ...xception.java => PaginationException.java} | 10 +++-- .../api/collections/pagination/Paginator.java | 2 +- 6 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/WeaviateException.java rename src/main/java/io/weaviate/client6/v1/api/collections/pagination/{WeaviatePaginationException.java => PaginationException.java} (69%) diff --git a/src/it/java/io/weaviate/integration/PaginationITest.java b/src/it/java/io/weaviate/integration/PaginationITest.java index a68672245..3d97d0a21 100644 --- a/src/it/java/io/weaviate/integration/PaginationITest.java +++ b/src/it/java/io/weaviate/integration/PaginationITest.java @@ -16,10 +16,11 @@ import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; +import io.weaviate.client6.v1.api.WeaviateException; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.WeaviateMetadata; import io.weaviate.client6.v1.api.collections.WeaviateObject; -import io.weaviate.client6.v1.api.collections.pagination.WeaviatePaginationException; +import io.weaviate.client6.v1.api.collections.pagination.PaginationException; import io.weaviate.containers.Container; public class PaginationITest extends ConcurrentTest { @@ -160,13 +161,13 @@ public void testAsyncPaginator() throws IOException, InterruptedException, Execu } } - @Test(expected = WeaviatePaginationException.class) + @Test(expected = PaginationException.class) public void testFailedPagination() throws IOException { var things = client.collections.use("Unknown"); things.paginate().forEach(System.out::println); } - @Test(expected = WeaviatePaginationException.class) + @Test(expected = PaginationException.class) public void testFailedAsyncPagination_forEach() throws Throwable { try (final var async = client.async()) { var things = async.collections.use("Unknown"); @@ -178,7 +179,7 @@ public void testFailedAsyncPagination_forEach() throws Throwable { } } - @Test(expected = WeaviatePaginationException.class) + @Test(expected = WeaviateException.class) public void testFailedAsyncPagination_forPage() throws Throwable { try (final var async = client.async()) { var things = async.collections.use("Unknown"); diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index a6207b199..84fffeaee 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -5,7 +5,7 @@ * server, but the operation did not complete successfully either either due to * a bad request or a server error. */ -public class WeaviateApiException extends RuntimeException { +public class WeaviateApiException extends WeaviateException { private final String errorMessage; private final Source source; private final String endpoint; diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateException.java new file mode 100644 index 000000000..3ded81fa3 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateException.java @@ -0,0 +1,38 @@ +package io.weaviate.client6.v1.api; + +/** + * WeaviateException is the base class for other library exceptions + * to provide an ergonomic way of handling any Weaviate-related exceptions. + * + *

+ * Some parts of the API may still throw other standard exceptions, like + * {@link java.io.IOException} or {@link java.lang.IllegalArgumentException}, + * which will not be wrapped into a WeaviateException. + * + *

+ * Usage: + * + *

{@code
+ *  var thigns = client.collections.use("Things");
+ *  try {
+ *    things.paginate(...)
+ *    things.query.bm25(...);
+ *    things.aggregate.overAll(...);
+ *  } catch (WeaviateException e) {
+ *    System.out.println(e);
+ *  }
+ * }
+ */ +public abstract class WeaviateException extends RuntimeException { + public WeaviateException(String message) { + super(message); + } + + public WeaviateException(Throwable cause) { + super(cause); + } + + public WeaviateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java index c5de3bf0e..771b6e4b6 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java @@ -36,7 +36,7 @@ public AsyncPaginator(Builder builder) { return this.query.fetchObjects(fn) .handle((response, ex) -> { if (ex != null) { - throw WeaviatePaginationException.after(cursor, pageSize, ex); + throw PaginationException.after(cursor, pageSize, ex); } return response; }) diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/PaginationException.java similarity index 69% rename from src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java rename to src/main/java/io/weaviate/client6/v1/api/collections/pagination/PaginationException.java index 92ee97e8d..27a119f59 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/WeaviatePaginationException.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/PaginationException.java @@ -1,20 +1,22 @@ package io.weaviate.client6.v1.api.collections.pagination; +import io.weaviate.client6.v1.api.WeaviateException; + /** * WeaviatePaginationException is thrown then the client encouters an exception * while fetching the next page. This exception preserves the original exception * (see {@link #getCause} and the information about the last cursor and page * size used (see {@link #cursor()} and {@link #pageSize()} respectively). */ -public class WeaviatePaginationException extends RuntimeException { +public class PaginationException extends WeaviateException { private final String cursor; private final int pageSize; - public static WeaviatePaginationException after(String cursor, int pageSize, Throwable cause) { - return new WeaviatePaginationException(cursor, pageSize, cause); + public static PaginationException after(String cursor, int pageSize, Throwable cause) { + return new PaginationException(cursor, pageSize, cause); } - private WeaviatePaginationException(String cursor, int pageSize, Throwable cause) { + private PaginationException(String cursor, int pageSize, Throwable cause) { super("fetch next page, page_size=%d cursor=%s".formatted(pageSize, cursor), cause); this.cursor = cursor; this.pageSize = pageSize; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java index edfb8bcd2..04b10bf11 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java @@ -40,7 +40,7 @@ public Spliterator> spliterat try { return query.fetchObjects(fn).objects(); } catch (Exception e) { - throw WeaviatePaginationException.after(cursor, pageSize, e); + throw PaginationException.after(cursor, pageSize, e); } }); } From 5dc3a1c825f573c855f6e9d79379414a808df993 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 6 Aug 2025 11:25:51 +0200 Subject: [PATCH 09/10] chore: document WeaviateApiException thrown in WeaviateCollectionsClient --- .../WeaviateCollectionsClient.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java index 03d4c6b33..6481a0033 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java @@ -6,6 +6,7 @@ import java.util.Optional; 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.grpc.GrpcTransport; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; @@ -20,42 +21,127 @@ public WeaviateCollectionsClient(RestTransport restTransport, GrpcTransport grpc this.grpcTransport = grpcTransport; } + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @returns a handle for a collection with {@code Map} + * properties. + */ public CollectionHandle> use(String collectionName) { return new CollectionHandle<>(restTransport, grpcTransport, CollectionDescriptor.ofMap(collectionName)); } + /** + * Create a new Weaviate collection with default configuration. + * + * @returns the configuration of the created collection. + * @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 CollectionConfig create(String name) throws IOException { return create(CollectionConfig.of(name)); } + /** + * Create and configure a new Weaviate collection. See + * {@link CollectionConfig.Builder} for available options. + * + * @returns the configuration of the created collection. + * @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 CollectionConfig create(String name, Function> fn) throws IOException { return create(CollectionConfig.of(name, fn)); } + /** + * Create a new Weaviate collection with {@link CollectionConfig}. + * + * @returns the configuration of the created collection. + * @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 CollectionConfig create(CollectionConfig collection) throws IOException { return this.restTransport.performRequest(new CreateCollectionRequest(collection), CreateCollectionRequest._ENDPOINT); } + /** + * Fetch Weaviate collection configuration. + * + * @returns the collection configuration if one with this name exists. + * @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 getConfig(String name) throws IOException { return this.restTransport.performRequest(new GetConfigRequest(name), GetConfigRequest._ENDPOINT); } + /** + * Fetch configurations for all collections in Weaviate. + * + * @returns a list of collection configurations. + * @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() throws IOException { return this.restTransport.performRequest(new ListCollectionRequest(), ListCollectionRequest._ENDPOINT); } + /** + * Delete a Weaviate collection. + * + * @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(String name) throws IOException { this.restTransport.performRequest(new DeleteCollectionRequest(name), DeleteCollectionRequest._ENDPOINT); } + /** + * Delete all collections in Weaviate. + * + * @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 { for (var collection : list()) { delete(collection.collectionName()); } } + /** + * Check if a collection with this name exists. + * + * @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 boolean exists(String name) throws IOException { return getConfig(name).isPresent(); } From 78a886403c34556c86b484391ff50a61fa1bda2d Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 7 Aug 2025 00:48:43 +0200 Subject: [PATCH 10/10] chore: fix broken javadoc --- .../api/collections/WeaviateCollectionsClient.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java index 6481a0033..d8c175692 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java @@ -25,8 +25,8 @@ public WeaviateCollectionsClient(RestTransport restTransport, GrpcTransport grpc * Obtain a handle to send requests to a particular collection. * The returned object is thread-safe. * - * @returns a handle for a collection with {@code Map} - * properties. + * @return a handle for a collection with {@code Map} + * properties. */ public CollectionHandle> use(String collectionName) { return new CollectionHandle<>(restTransport, grpcTransport, CollectionDescriptor.ofMap(collectionName)); @@ -35,7 +35,7 @@ public CollectionHandle> use(String collectionName) { /** * Create a new Weaviate collection with default configuration. * - * @returns the configuration of the created collection. + * @return the configuration of the created collection. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully @@ -50,7 +50,7 @@ public CollectionConfig create(String name) throws IOException { * Create and configure a new Weaviate collection. See * {@link CollectionConfig.Builder} for available options. * - * @returns the configuration of the created collection. + * @return the configuration of the created collection. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully @@ -65,7 +65,7 @@ public CollectionConfig create(String name, /** * Create a new Weaviate collection with {@link CollectionConfig}. * - * @returns the configuration of the created collection. + * @return the configuration of the created collection. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully @@ -80,7 +80,7 @@ public CollectionConfig create(CollectionConfig collection) throws IOException { /** * Fetch Weaviate collection configuration. * - * @returns the collection configuration if one with this name exists. + * @return the collection configuration if one with this name exists. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully @@ -94,7 +94,7 @@ public Optional getConfig(String name) throws IOException { /** * Fetch configurations for all collections in Weaviate. * - * @returns a list of collection configurations. + * @return a list of collection configurations. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully