Skip to content

Commit 71af20b

Browse files
authored
Merge pull request #437 from weaviate/v6-error-handling
v6: Throw WeaviateApiException for error status codes
2 parents 18eb328 + 78a8864 commit 71af20b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+751
-243
lines changed

src/it/java/io/weaviate/integration/CollectionsITest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.junit.Test;
88

99
import io.weaviate.ConcurrentTest;
10+
import io.weaviate.client6.v1.api.WeaviateApiException;
1011
import io.weaviate.client6.v1.api.WeaviateClient;
1112
import io.weaviate.client6.v1.api.collections.CollectionConfig;
1213
import io.weaviate.client6.v1.api.collections.InvertedIndex;
@@ -185,4 +186,9 @@ public void testShards() throws IOException {
185186
.extracting(Shard::status)
186187
.containsOnly(wantStatus.name());
187188
}
189+
190+
@Test(expected = WeaviateApiException.class)
191+
public void testInvalidCollectionName() throws IOException {
192+
client.collections.create("^[email protected]$");
193+
}
188194
}

src/it/java/io/weaviate/integration/DataITest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.junit.Test;
1010

1111
import io.weaviate.ConcurrentTest;
12+
import io.weaviate.client6.v1.api.WeaviateApiException;
1213
import io.weaviate.client6.v1.api.WeaviateClient;
1314
import io.weaviate.client6.v1.api.collections.Property;
1415
import io.weaviate.client6.v1.api.collections.Vectorizers;
@@ -393,4 +394,17 @@ public void testReferenceAddMany() throws IOException {
393394
.extracting(WeaviateObject::uuid)
394395
.contains(alpha, bravo, charlie);
395396
}
397+
398+
@Test(expected = WeaviateApiException.class)
399+
public void testDuplicateUuid() throws IOException {
400+
// Arrange
401+
var nsThings = ns("Things");
402+
403+
client.collections.create(nsThings);
404+
var things = client.collections.use(nsThings);
405+
var thing_1 = things.data.insert(Map.of());
406+
407+
// Act
408+
things.data.insert(Map.of(), thing -> thing.uuid(thing_1.uuid()));
409+
}
396410
}

src/it/java/io/weaviate/integration/PaginationITest.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.ArrayList;
77
import java.util.Collections;
88
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.CompletionException;
910
import java.util.concurrent.ExecutionException;
1011
import java.util.concurrent.atomic.AtomicInteger;
1112

@@ -15,9 +16,11 @@
1516

1617
import io.weaviate.ConcurrentTest;
1718
import io.weaviate.client6.v1.api.WeaviateClient;
19+
import io.weaviate.client6.v1.api.WeaviateException;
1820
import io.weaviate.client6.v1.api.collections.Property;
1921
import io.weaviate.client6.v1.api.collections.WeaviateMetadata;
2022
import io.weaviate.client6.v1.api.collections.WeaviateObject;
23+
import io.weaviate.client6.v1.api.collections.pagination.PaginationException;
2124
import io.weaviate.containers.Container;
2225

2326
public class PaginationITest extends ConcurrentTest {
@@ -89,7 +92,7 @@ public void testResumePagination() throws IOException {
8992
.reduce((prev, next) -> next).get();
9093

9194
// Act
92-
var remaining = things.paginate(p -> p.resumeFrom(lastId)).stream().count();
95+
var remaining = things.paginate(p -> p.fromCursor(lastId)).stream().count();
9396

9497
// Assert
9598
Assertions.assertThat(remaining).isEqualTo(5);
@@ -157,4 +160,34 @@ public void testAsyncPaginator() throws IOException, InterruptedException, Execu
157160
.isEqualTo(count);
158161
}
159162
}
163+
164+
@Test(expected = PaginationException.class)
165+
public void testFailedPagination() throws IOException {
166+
var things = client.collections.use("Unknown");
167+
things.paginate().forEach(System.out::println);
168+
}
169+
170+
@Test(expected = PaginationException.class)
171+
public void testFailedAsyncPagination_forEach() throws Throwable {
172+
try (final var async = client.async()) {
173+
var things = async.collections.use("Unknown");
174+
try {
175+
things.paginate().forEach(__ -> System.out.println("called once")).join();
176+
} catch (CompletionException e) {
177+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
178+
}
179+
}
180+
}
181+
182+
@Test(expected = WeaviateException.class)
183+
public void testFailedAsyncPagination_forPage() throws Throwable {
184+
try (final var async = client.async()) {
185+
var things = async.collections.use("Unknown");
186+
try {
187+
things.paginate().forPage(__ -> System.out.println("called once")).join();
188+
} catch (CompletionException e) {
189+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
190+
}
191+
}
192+
}
160193
}

src/it/java/io/weaviate/integration/SearchITest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.HashMap;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.concurrent.CompletionException;
910
import java.util.concurrent.ExecutionException;
1011

1112
import org.assertj.core.api.Assertions;
@@ -16,6 +17,7 @@
1617
import org.junit.rules.TestRule;
1718

1819
import io.weaviate.ConcurrentTest;
20+
import io.weaviate.client6.v1.api.WeaviateApiException;
1921
import io.weaviate.client6.v1.api.WeaviateClient;
2022
import io.weaviate.client6.v1.api.collections.Property;
2123
import io.weaviate.client6.v1.api.collections.Vectorizers;
@@ -374,4 +376,43 @@ public void testHybrid() throws IOException {
374376
Assertions.assertThat(first.metadata().explainScore())
375377
.as("metadata::explainScore").isNotNull();
376378
}
379+
380+
@Test(expected = WeaviateApiException.class)
381+
public void testBadRequest() throws IOException {
382+
// Arrange
383+
var nsThings = ns("Things");
384+
385+
client.collections.create(nsThings,
386+
collection -> collection
387+
.properties(Property.text("name"))
388+
.vectors(Vectorizers.text2vecContextionary()));
389+
390+
var things = client.collections.use(nsThings);
391+
var balloon = things.data.insert(Map.of("name", "balloon"));
392+
393+
things.query.nearObject(balloon.uuid(), q -> q.limit(-1));
394+
}
395+
396+
@Test(expected = WeaviateApiException.class)
397+
public void testBadRequest_async() throws Throwable {
398+
// Arrange
399+
var nsThings = ns("Things");
400+
401+
try (final var async = client.async()) {
402+
async.collections.create(nsThings,
403+
collection -> collection
404+
.properties(Property.text("name"))
405+
.vectors(Vectorizers.text2vecContextionary()))
406+
.join();
407+
408+
var things = async.collections.use(nsThings);
409+
var balloon = things.data.insert(Map.of("name", "balloon")).join();
410+
411+
try {
412+
things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).join();
413+
} catch (CompletionException e) {
414+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
415+
}
416+
}
417+
}
377418
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
/**
4+
* Exception class thrown by client API message when the request's reached the
5+
* server, but the operation did not complete successfully either either due to
6+
* a bad request or a server error.
7+
*/
8+
public class WeaviateApiException extends WeaviateException {
9+
private final String errorMessage;
10+
private final Source source;
11+
private final String endpoint;
12+
private final Integer httpStatusCode;
13+
private final io.grpc.Status.Code grpcStatusCode;
14+
15+
private enum Source {
16+
HTTP, GRPC;
17+
};
18+
19+
public static WeaviateApiException http(String method, String endpoint, int statusCode, String errorMessage) {
20+
return new WeaviateApiException(method, endpoint, statusCode, errorMessage);
21+
}
22+
23+
public static WeaviateApiException gRPC(io.grpc.StatusRuntimeException ex) {
24+
var status = ex.getStatus();
25+
return new WeaviateApiException(status.getCode(), status.getDescription());
26+
}
27+
28+
private WeaviateApiException(io.grpc.Status.Code code, String errorMessage) {
29+
super("%s: %s".formatted(code, errorMessage));
30+
this.source = Source.GRPC;
31+
this.errorMessage = errorMessage;
32+
this.grpcStatusCode = code;
33+
this.endpoint = null;
34+
this.httpStatusCode = null;
35+
}
36+
37+
private WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) {
38+
super("HTTP %d: %s %s: %s".formatted(statusCode, method, endpoint, errorMessage));
39+
this.source = Source.HTTP;
40+
this.errorMessage = errorMessage;
41+
this.endpoint = endpoint;
42+
this.httpStatusCode = statusCode;
43+
this.grpcStatusCode = null;
44+
}
45+
46+
public boolean isGPRC() {
47+
return source == Source.GRPC;
48+
}
49+
50+
public String grpcStatusCode() {
51+
return grpcStatusCode.toString();
52+
}
53+
54+
public boolean isHTTP() {
55+
return source == Source.HTTP;
56+
}
57+
58+
public String endpoint() {
59+
return endpoint;
60+
}
61+
62+
public Integer httpStatusCode() {
63+
return httpStatusCode;
64+
}
65+
66+
public String getError() {
67+
return errorMessage;
68+
}
69+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
/**
4+
* WeaviateException is the base class for other library exceptions
5+
* to provide an ergonomic way of handling any Weaviate-related exceptions.
6+
*
7+
* <p>
8+
* Some parts of the API may still throw other standard exceptions, like
9+
* {@link java.io.IOException} or {@link java.lang.IllegalArgumentException},
10+
* which will not be wrapped into a WeaviateException.
11+
*
12+
* <p>
13+
* Usage:
14+
*
15+
* <pre>{@code
16+
* var thigns = client.collections.use("Things");
17+
* try {
18+
* things.paginate(...)
19+
* things.query.bm25(...);
20+
* things.aggregate.overAll(...);
21+
* } catch (WeaviateException e) {
22+
* System.out.println(e);
23+
* }
24+
* }</pre>
25+
*/
26+
public abstract class WeaviateException extends RuntimeException {
27+
public WeaviateException(String message) {
28+
super(message);
29+
}
30+
31+
public WeaviateException(Throwable cause) {
32+
super(cause);
33+
}
34+
35+
public WeaviateException(String message, Throwable cause) {
36+
super(message, cause);
37+
}
38+
}

src/main/java/io/weaviate/client6/v1/api/collections/CreateCollectionRequest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22

33
import java.util.Collections;
44

5-
import org.apache.hc.core5.http.HttpStatus;
6-
75
import io.weaviate.client6.v1.internal.json.JSON;
86
import io.weaviate.client6.v1.internal.rest.Endpoint;
7+
import io.weaviate.client6.v1.internal.rest.SimpleEndpoint;
98

109
public record CreateCollectionRequest(CollectionConfig collection) {
11-
public static final Endpoint<CreateCollectionRequest, CollectionConfig> _ENDPOINT = Endpoint.of(
10+
public static final Endpoint<CreateCollectionRequest, CollectionConfig> _ENDPOINT = new SimpleEndpoint<>(
1211
request -> "POST",
1312
request -> "/schema/",
14-
(gson, request) -> JSON.serialize(request.collection),
1513
request -> Collections.emptyMap(),
16-
code -> code != HttpStatus.SC_SUCCESS,
17-
(gson, response) -> JSON.deserialize(response, CollectionConfig.class));
14+
request -> JSON.serialize(request.collection),
15+
(statusCode, response) -> JSON.deserialize(response, CollectionConfig.class));
1816
}

src/main/java/io/weaviate/client6/v1/api/collections/DeleteCollectionRequest.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@
22

33
import java.util.Collections;
44

5-
import org.apache.hc.core5.http.HttpStatus;
6-
75
import io.weaviate.client6.v1.internal.rest.Endpoint;
6+
import io.weaviate.client6.v1.internal.rest.SimpleEndpoint;
87

98
public record DeleteCollectionRequest(String collectionName) {
10-
public static final Endpoint<DeleteCollectionRequest, Void> _ENDPOINT = Endpoint.of(
9+
public static final Endpoint<DeleteCollectionRequest, Void> _ENDPOINT = SimpleEndpoint.sideEffect(
1110
request -> "DELETE",
1211
request -> "/schema/" + request.collectionName,
13-
(gson, request) -> null,
14-
request -> Collections.emptyMap(),
15-
status -> status != HttpStatus.SC_SUCCESS,
16-
(gson, resopnse) -> null);
12+
request -> Collections.emptyMap());
1713
}

src/main/java/io/weaviate/client6/v1/api/collections/GetConfigRequest.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@
33
import java.util.Collections;
44
import java.util.Optional;
55

6-
import org.apache.hc.core5.http.HttpStatus;
7-
86
import io.weaviate.client6.v1.internal.json.JSON;
97
import io.weaviate.client6.v1.internal.rest.Endpoint;
8+
import io.weaviate.client6.v1.internal.rest.OptionalEndpoint;
109

1110
public record GetConfigRequest(String collectionName) {
12-
public static final Endpoint<GetConfigRequest, Optional<CollectionConfig>> _ENDPOINT = Endpoint.of(
13-
request -> "GET",
14-
request -> "/schema/" + request.collectionName,
15-
(gson, request) -> null,
16-
request -> Collections.emptyMap(),
17-
code -> code != HttpStatus.SC_SUCCESS,
18-
(gson, response) -> Optional.ofNullable(JSON.deserialize(response, CollectionConfig.class)));
11+
public static final Endpoint<GetConfigRequest, Optional<CollectionConfig>> _ENDPOINT = OptionalEndpoint
12+
.noBodyOptional(
13+
request -> "GET",
14+
request -> "/schema/" + request.collectionName,
15+
request -> Collections.emptyMap(),
16+
(statusCode, response) -> JSON.deserialize(response, CollectionConfig.class));
1917
}

src/main/java/io/weaviate/client6/v1/api/collections/ListCollectionRequest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
import java.util.Collections;
44
import java.util.List;
55

6-
import org.apache.hc.core5.http.HttpStatus;
7-
86
import io.weaviate.client6.v1.internal.json.JSON;
97
import io.weaviate.client6.v1.internal.rest.Endpoint;
8+
import io.weaviate.client6.v1.internal.rest.SimpleEndpoint;
109

1110
public record ListCollectionRequest() {
12-
public static final Endpoint<ListCollectionRequest, List<CollectionConfig>> _ENDPOINT = Endpoint.of(
11+
public static final Endpoint<ListCollectionRequest, List<CollectionConfig>> _ENDPOINT = SimpleEndpoint.noBody(
1312
request -> "GET",
1413
request -> "/schema",
15-
(gson, request) -> null,
1614
request -> Collections.emptyMap(),
17-
code -> code != HttpStatus.SC_SUCCESS,
1815
(gson, response) -> JSON.deserialize(response, ListCollectionResponse.class).collections());
1916
}

0 commit comments

Comments
 (0)