diff --git a/src/main/java/com/fauna/client/FaunaClient.java b/src/main/java/com/fauna/client/FaunaClient.java index d1478515..6a0b809e 100644 --- a/src/main/java/com/fauna/client/FaunaClient.java +++ b/src/main/java/com/fauna/client/FaunaClient.java @@ -8,17 +8,13 @@ import com.fauna.event.FeedIterator; import com.fauna.event.StreamOptions; import com.fauna.exception.ClientException; -import com.fauna.exception.ErrorHandler; import com.fauna.exception.FaunaException; -import com.fauna.exception.ProtocolException; import com.fauna.exception.ServiceException; import com.fauna.event.EventSource; import com.fauna.event.FeedOptions; import com.fauna.event.FeedPage; import com.fauna.query.AfterToken; import com.fauna.query.QueryOptions; -import com.fauna.response.QueryFailure; -import com.fauna.event.StreamRequest; import com.fauna.event.EventSourceResponse; import com.fauna.query.builder.Query; import com.fauna.response.QueryResponse; @@ -144,15 +140,6 @@ private Supplier>> makeAsyncFeedRequest(HttpCl return () -> client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply( response -> { logResponse(response); - if (response.statusCode() >= 400) { - // There are possibly some different error cases to handle for feeds. This seems like - // a comprehensive solution for now. In the future we could rename QueryFailure et. al. to - // something like FaunaFailure, or implement "FeedFailure". - QueryFailure failure = new QueryFailure(response.statusCode(), QueryResponse.builder(codec)); - ErrorHandler.handleQueryFailure(response.statusCode(), failure); - // Fall back on ProtocolException. - throw new ProtocolException(response.statusCode(), failure); - } return FeedPage.parseResponse(response, codec, statsCollector); }).whenComplete(this::completeFeedRequest); } diff --git a/src/main/java/com/fauna/event/FeedPage.java b/src/main/java/com/fauna/event/FeedPage.java index be1df2a4..1dbf5fce 100644 --- a/src/main/java/com/fauna/event/FeedPage.java +++ b/src/main/java/com/fauna/event/FeedPage.java @@ -5,6 +5,7 @@ import com.fauna.client.StatsCollector; import com.fauna.codec.Codec; import com.fauna.exception.ClientResponseException; +import com.fauna.response.QueryResponse; import com.fauna.response.QueryStats; import java.io.IOException; @@ -125,7 +126,7 @@ public Builder parseField(JsonParser parser) throws IOException { case FEED_HAS_NEXT_FIELD_NAME: return hasNext(parser.nextBooleanValue()); default: - throw new ClientResponseException("Unknown StreamEvent field: " + fieldName); + throw new ClientResponseException("Unknown FeedPage field: " + fieldName); } } @@ -137,16 +138,24 @@ public static Builder builder(Codec elementCodec, StatsCollector stats public static FeedPage parseResponse(HttpResponse response, Codec elementCodec, StatsCollector statsCollector) { try { + // If you want to inspect the request body before it's parsed, you can uncomment this. + // String body = new BufferedReader(new InputStreamReader(response.body())) + // .lines().collect(Collectors.joining("\n")); + if (response.statusCode() >= 400) { + // It's not ideal to use the QueryResponse parser in the Feed API. But for error cases, the + // error response that the Feed API throws is a terser (i.e. subset) version of QueryFailure, and the + // parser gracefully handles it. A FaunaException will be thrown by parseResponse. + QueryResponse.parseResponse(response, elementCodec, statsCollector); + } JsonParser parser = JSON_FACTORY.createParser(response.body()); - if (parser.nextToken() == START_OBJECT) { - Builder builder = FeedPage.builder(elementCodec, statsCollector); - while (parser.nextToken() == FIELD_NAME) { - builder.parseField(parser); - } - return builder.build(); - } else { + if (parser.nextToken() != START_OBJECT) { throw new ClientResponseException("Invalid event starting with: " + parser.currentToken()); } + Builder builder = FeedPage.builder(elementCodec, statsCollector); + while (parser.nextToken() == FIELD_NAME) { + builder.parseField(parser); + } + return builder.build(); } catch (IOException e) { throw new ClientResponseException("Error parsing Feed response.", e); } diff --git a/src/main/java/com/fauna/exception/ServiceException.java b/src/main/java/com/fauna/exception/ServiceException.java index 66dd4593..8f090de4 100644 --- a/src/main/java/com/fauna/exception/ServiceException.java +++ b/src/main/java/com/fauna/exception/ServiceException.java @@ -90,7 +90,7 @@ public Optional getTxnTs() { * * @return the schema version as a long value */ - public long getSchemaVersion() { + public Long getSchemaVersion() { return this.response.getSchemaVersion(); } diff --git a/src/main/java/com/fauna/response/QueryFailure.java b/src/main/java/com/fauna/response/QueryFailure.java index 96de37fd..0143eaed 100644 --- a/src/main/java/com/fauna/response/QueryFailure.java +++ b/src/main/java/com/fauna/response/QueryFailure.java @@ -27,7 +27,6 @@ public String getMessage() { public Optional getAbort(Class clazz) { return errorInfo.getAbort(clazz); - } public String getFullMessage() { diff --git a/src/test/java/com/fauna/e2e/E2EFeedsTest.java b/src/test/java/com/fauna/e2e/E2EFeedsTest.java index 90e2a513..3f0f75b8 100644 --- a/src/test/java/com/fauna/e2e/E2EFeedsTest.java +++ b/src/test/java/com/fauna/e2e/E2EFeedsTest.java @@ -9,6 +9,8 @@ import com.fauna.event.FeedOptions; import com.fauna.event.FeedPage; import com.fauna.event.EventSourceResponse; +import com.fauna.exception.InvalidRequestException; +import com.fauna.response.QueryFailure; import com.fauna.response.QuerySuccess; import com.fauna.event.FaunaEvent; import org.junit.jupiter.api.BeforeAll; @@ -18,6 +20,7 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; @@ -28,7 +31,9 @@ import static com.fauna.event.FaunaEvent.EventType.ERROR; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNull; public class E2EFeedsTest { @@ -92,8 +97,33 @@ public void manualFeed() { @Test public void feedError() { + // Fauna can throw a HTTP error in some cases. In this case it's bad request to the feed API. Presumably + // some of the others like ThrottlingException, and AuthenticationException can also be thrown. + CompletableFuture> future= client.poll(EventSource.fromToken("badToken"), FeedOptions.DEFAULT, Product.class); + CompletionException ce = assertThrows(CompletionException.class, () -> future.join()); + InvalidRequestException ire = (InvalidRequestException) ce.getCause(); + + assertEquals("invalid_request", ire.getErrorCode()); + assertEquals(400, ire.getStatusCode()); + assertEquals("400 (invalid_request): Invalid request body: invalid event source provided.", ire.getMessage()); + assertTrue(ire.getTxnTs().isEmpty()); + assertNull(ire.getStats()); + assertNull(ire.getSchemaVersion()); + assertNull(ire.getSummary()); + assertNull(ire.getCause()); + assertNull(ire.getQueryTags()); + + QueryFailure failure = ire.getResponse(); + assertTrue(failure.getConstraintFailures().isEmpty()); + assertTrue(failure.getAbort(null).isEmpty()); + } + + @Test + public void feedEventError() { + // Fauna can also return a valid feed page, with HTTP 200, but an "error" event type. FeedOptions options = FeedOptions.builder().startTs(0L).build(); - FeedIterator iter = client.feed(fql("Product.all().eventSource()"), options, Product.class); + QuerySuccess sourceQuery = client.query(fql("Product.all().eventSource()"), EventSourceResponse.class); + FeedIterator iter = client.feed(EventSource.fromResponse(sourceQuery.getData()), options, Product.class); FeedPage pageOne = iter.next(); assertFalse(pageOne.hasNext()); assertEquals(1, pageOne.getEvents().size());