Skip to content

Commit

Permalink
Refactor error handling for Feeds, and add feedError/testFeedEventError.
Browse files Browse the repository at this point in the history
  • Loading branch information
David Griffin committed Nov 5, 2024
1 parent a13a086 commit 3cb6d20
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 24 deletions.
13 changes: 0 additions & 13 deletions src/main/java/com/fauna/client/FaunaClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -144,15 +140,6 @@ private <E> Supplier<CompletableFuture<FeedPage<E>>> 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);
}
Expand Down
25 changes: 17 additions & 8 deletions src/main/java/com/fauna/event/FeedPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -137,16 +138,24 @@ public static <E> Builder<E> builder(Codec<E> elementCodec, StatsCollector stats

public static <E> FeedPage<E> parseResponse(HttpResponse<InputStream> response, Codec<E> 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<E> 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<E> 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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/fauna/exception/ServiceException.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public Optional<Long> getTxnTs() {
*
* @return the schema version as a long value
*/
public long getSchemaVersion() {
public Long getSchemaVersion() {
return this.response.getSchemaVersion();
}

Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/fauna/response/QueryFailure.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public String getMessage() {

public <T> Optional<T> getAbort(Class<T> clazz) {
return errorInfo.getAbort(clazz);

}

public String getFullMessage() {
Expand Down
32 changes: 31 additions & 1 deletion src/test/java/com/fauna/e2e/E2EFeedsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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<FeedPage<Product>> 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<Product> iter = client.feed(fql("Product.all().eventSource()"), options, Product.class);
QuerySuccess<EventSourceResponse> sourceQuery = client.query(fql("Product.all().eventSource()"), EventSourceResponse.class);
FeedIterator<Product> iter = client.feed(EventSource.fromResponse(sourceQuery.getData()), options, Product.class);
FeedPage<Product> pageOne = iter.next();
assertFalse(pageOne.hasNext());
assertEquals(1, pageOne.getEvents().size());
Expand Down

0 comments on commit 3cb6d20

Please sign in to comment.