Skip to content

Commit

Permalink
Refactor error parsing.
Browse files Browse the repository at this point in the history
  * Add parse(..) methods to ConstraintFailure and ErrorInfo.
  * Add ErrorInfoTest.
  * Add E2EStreamingTest.handleStreamError.
  • Loading branch information
David Griffin committed Sep 13, 2024
1 parent a299a64 commit 39bcd08
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 140 deletions.
7 changes: 4 additions & 3 deletions src/main/java/com/fauna/exception/ServiceException.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fauna.exception;

import java.util.Map;
import java.util.Optional;

import com.fauna.response.QueryFailure;
import com.fauna.response.QueryStats;
Expand Down Expand Up @@ -76,12 +77,12 @@ public QueryStats getStats() {
}

/**
* Returns the faled query's last transaction timestamp.
* Returns the failed query's last transaction timestamp.
*
* @return the transaction timestamp as a long value
*/
public long getTxnTs() {
return this.response.getLastSeenTxn();
public Optional<Long> getTxnTs() {
return Optional.ofNullable(this.response.getLastSeenTxn());
}

/**
Expand Down
58 changes: 41 additions & 17 deletions src/main/java/com/fauna/response/ConstraintFailure.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import com.fasterxml.jackson.core.JsonToken;
import com.fauna.constants.ResponseFields;
import com.fauna.exception.ClientException;
import com.fauna.exception.ClientResponseException;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class ConstraintFailure {
private final String message;
Expand Down Expand Up @@ -53,6 +56,10 @@ public PathElement(Integer iVal) {
this.iVal = iVal;
}

public boolean isString() {
return sVal != null;
}

/**
* Note that this parser does not advance the parser.
* @param parser
Expand All @@ -67,6 +74,16 @@ public static PathElement parse(JsonParser parser) throws IOException {
}
}

@Override
public boolean equals(Object o) {
if (o instanceof PathElement) {
PathElement other = (PathElement) o;
return other.isString() == this.isString() && other.toString().equals(this.toString());
} else {
return false;
}
}

public String toString() {
return sVal == null ? String.valueOf(iVal) : sVal;
}
Expand Down Expand Up @@ -104,11 +121,8 @@ public static Builder builder() {
}

public static ConstraintFailure parse(JsonParser parser) throws IOException {
JsonToken firstToken = parser.nextToken();
if (firstToken == JsonToken.VALUE_NULL) {
return null;
} else if (firstToken != JsonToken.START_OBJECT) {
throw new ClientException("Constraint failure should be a JSON object or null, got" + firstToken);
if (parser.currentToken() != JsonToken.START_OBJECT && parser.nextToken() != JsonToken.START_OBJECT) {
throw new ClientResponseException("Constraint failure should be a JSON object.");
}
Builder builder = ConstraintFailure.builder();
while (parser.nextToken() == JsonToken.FIELD_NAME) {
Expand All @@ -119,23 +133,23 @@ public static ConstraintFailure parse(JsonParser parser) throws IOException {
break;
case ResponseFields.ERROR_NAME_FIELD_NAME:
builder.name(parser.nextTextValue());
break;
case ResponseFields.ERROR_PATHS_FIELD_NAME:
List<PathElement[]> paths = new ArrayList<>();
JsonToken firstPathToken = parser.nextToken();
if (firstPathToken != JsonToken.START_ARRAY || firstPathToken != JsonToken.VALUE_NULL) {
throw new ClientException("Constraint failure should be array or null, got: " + firstPathToken.toString());
}
while (parser.nextToken() != JsonToken.END_ARRAY) {
List<PathElement> path = new ArrayList<>();
if (parser.nextToken() == JsonToken.START_ARRAY) {
JsonToken pathToken = parser.nextToken();
while (pathToken != JsonToken.END_ARRAY) {
if (firstPathToken == JsonToken.START_ARRAY) {
while (parser.nextToken() == JsonToken.START_ARRAY) {
List<PathElement> path = new ArrayList<>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
path.add(PathElement.parse(parser));
}
paths.add(path.toArray(new PathElement[path.size()]));
}
paths.add(path.toArray(new PathElement[path.size()]));
builder.paths(paths.toArray(new PathElement[paths.size()][]));
} else if (firstPathToken != JsonToken.VALUE_NULL) {
throw new ClientException("Constraint failure path should be array or null, got: " + firstPathToken.toString());
}
builder.paths(paths.toArray(new PathElement[paths.size()][]));
break;
}
}
return builder.build();
Expand All @@ -150,8 +164,18 @@ public Optional<String> getName() {
return Optional.ofNullable(this.name);
}

public PathElement[][] getPaths() {
return paths;
public Optional<PathElement[][]> getPaths() {
return Optional.ofNullable(paths);
}

public Optional<List<String>> getPathStrings() {
if (paths == null) {
return Optional.empty();
} else {
return Optional.of(Arrays.stream(paths).map(
pathElements -> Arrays.stream(pathElements).map(PathElement::toString).collect(
Collectors.joining("."))).collect(Collectors.toList()));
}
}

}
99 changes: 98 additions & 1 deletion src/main/java/com/fauna/response/ErrorInfo.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package com.fauna.response;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fauna.exception.ClientException;
import com.fauna.exception.ClientResponseException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.fauna.constants.ResponseFields.ERROR_CODE_FIELD_NAME;
import static com.fauna.constants.ResponseFields.ERROR_CONSTRAINT_FAILURES_FIELD_NAME;
import static com.fauna.constants.ResponseFields.ERROR_MESSAGE_FIELD_NAME;

/**
* This class will encapsulate all the information Fauna returns about errors including constraint failures, and
* abort data, for now it just has the code and message.
*/
public class ErrorInfo {
private final String code;
private final String message;
private final ConstraintFailure[] constraintFailures;
private final String abort;

public ErrorInfo(String code, String message) {
public ErrorInfo(String code, String message, ConstraintFailure[] constraintFailures, String abort) {
this.code = code;
this.message = message;
this.constraintFailures = constraintFailures;
this.abort = abort;
}

public String getCode() {
Expand All @@ -20,4 +38,83 @@ public String getCode() {
public String getMessage() {
return message;
}

public Optional<ConstraintFailure[]> getConstraintFailures() {
return Optional.ofNullable(this.constraintFailures);
}
public String getAbort() {
return this.abort;
}

public static class Builder {
String code = null;
String message = null;
ConstraintFailure[] constraintFailures = null;
String abort = null;

public Builder code(String code) {
this.code = code;
return this;
}

public Builder message(String message) {
this.message = message;
return this;
}

public Builder abort(Object abort) {
this.abort = abort.toString();
return this;
}

public Builder constraintFailures(List<ConstraintFailure> constraintFailures) {
this.constraintFailures = constraintFailures.toArray(new ConstraintFailure[constraintFailures.size()]);
return this;
}

public ErrorInfo build() {
return new ErrorInfo(this.code, this.message, this.constraintFailures, "abort parsing not implemented");
}
}

public static Builder builder() {
return new Builder();
}

public static ErrorInfo parse(JsonParser parser) throws IOException {
if (parser.nextToken() != JsonToken.START_OBJECT) {
throw new ClientException("Error parsing error info, got token" + parser.currentToken());
}
Builder builder = ErrorInfo.builder();

while (parser.nextToken() == JsonToken.FIELD_NAME) {
String fieldName = parser.getCurrentName();
switch (fieldName) {
case ERROR_CODE_FIELD_NAME:
builder.code(parser.nextTextValue());
break;
case ERROR_MESSAGE_FIELD_NAME:
builder.message(parser.nextTextValue());
break;
case ERROR_CONSTRAINT_FAILURES_FIELD_NAME:
List<ConstraintFailure> failures = new ArrayList<>();
JsonToken token = parser.nextToken();
if (token == JsonToken.VALUE_NULL) {
break;
} else if (token == JsonToken.START_ARRAY) {
JsonToken nextToken = parser.nextToken();
while (nextToken == JsonToken.START_OBJECT) {
failures.add(ConstraintFailure.parse(parser));
nextToken = parser.nextToken();
}
builder.constraintFailures(failures);
break;
} else {
throw new ClientException("Unexpected for constraint failures: " + token);
}
default: throw new ClientResponseException("Unexpected token in error info: " + parser.currentToken());
}
}
return builder.build();
}
}
32 changes: 21 additions & 11 deletions src/main/java/com/fauna/response/QueryFailure.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,32 @@
import com.fauna.exception.ClientResponseException;
import com.fauna.exception.CodecException;
import com.fauna.response.wire.ConstraintFailureWire;
import com.fauna.response.wire.ErrorInfoWire;
import com.fauna.response.wire.QueryResponseWire;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public final class QueryFailure extends QueryResponse {

private final int statusCode;
private final ErrorInfo errorInfo;
private String errorCode = "";
private String message = "";

private List<ConstraintFailure> constraintFailures;
private String abortString;

private final String fullMessage;

public QueryFailure(int statusCode, ErrorInfo errorInfo, Long schemaVersion, Map<String, String> queryTags, QueryStats stats) {
super(null, null, schemaVersion, queryTags, stats);
this.statusCode = statusCode;
this.errorInfo = errorInfo;
}

/**
* Initializes a new instance of the {@link QueryFailure} class, parsing the provided raw
Expand All @@ -32,7 +41,13 @@ public final class QueryFailure extends QueryResponse {
* @param response The parsed response.
*/
public QueryFailure(int statusCode, QueryResponseWire response) {
super(response);
super(response.getTxnTs(), response.getSummary(), response.getSchemaVersion(), response.getQueryTags(), response.getStats());
ErrorInfoWire errorInfoWire = response.getError();
if (errorInfoWire != null) {
this.errorInfo = new ErrorInfo(errorCode, errorInfoWire.getMessage(), errorInfoWire.getConstraintFailureArray().orElse(null), errorInfoWire.getAbort().orElse(null));
} else {
this.errorInfo = new ErrorInfo(errorCode, null, null, null);
}

this.statusCode = statusCode;

Expand Down Expand Up @@ -61,14 +76,6 @@ public QueryFailure(int statusCode, QueryResponseWire response) {
}
}

var maybeSummary = this.getSummary() != null ? "\n---\n" + this.getSummary() : "";
fullMessage = String.format(
"%d (%s): %s%s",
this.getStatusCode(),
this.getErrorCode(),
this.getMessage(),
maybeSummary);

}

public int getStatusCode() {
Expand All @@ -84,7 +91,10 @@ public String getMessage() {
}

public String getFullMessage() {
return fullMessage;
String summarySuffix = this.getSummary() != null ? "\n---\n" + this.getSummary() : "";
return String.format("%d (%s): %s%s",
this.getStatusCode(), this.getErrorCode(), this.getMessage(), summarySuffix);

}

public Optional<List<ConstraintFailure>> getConstraintFailures() {
Expand Down
21 changes: 11 additions & 10 deletions src/main/java/com/fauna/response/StreamEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public enum EventType {
private final Long txn_ts;
private final E data;
private final QueryStats stats;
private final ErrorInfoWire error;
private final ErrorInfo error;


public StreamEvent(EventType type, String cursor, Long txn_ts, E data, QueryStats stats, ErrorInfoWire error) {
public StreamEvent(EventType type, String cursor, Long txn_ts, E data, QueryStats stats, ErrorInfo error) {
this.type = type;
this.cursor = cursor;
this.txn_ts = txn_ts;
Expand Down Expand Up @@ -65,7 +65,7 @@ public static <E> StreamEvent<E> parse(JsonParser parser, Codec<E> dataCodec) th
QueryStats stats = null;
E data = null;
Long txn_ts = null;
ErrorInfoWire errorInfo = null;
ErrorInfo errorInfo = null;
while (parser.nextToken() == FIELD_NAME) {
String fieldName = parser.getValueAsString();
switch (fieldName) {
Expand All @@ -80,18 +80,19 @@ public static <E> StreamEvent<E> parse(JsonParser parser, Codec<E> dataCodec) th
}
data = dataCodec.decode(faunaParser);
break;
case STREAM_TYPE_FIELD_NAME: eventType = parseEventType(parser);
break;
case STREAM_TYPE_FIELD_NAME:
eventType = parseEventType(parser);
break;
case STATS_FIELD_NAME:
stats = QueryStats.parseStats(parser);
break;
case LAST_SEEN_TXN_FIELD_NAME:
parser.nextToken();
txn_ts = parser.getValueAsLong();
txn_ts = parser.nextLongValue(0L);
break;
case ERROR_FIELD_NAME:
ObjectMapper mapper = new ObjectMapper();
errorInfo = mapper.readValue(parser, ErrorInfoWire.class);
errorInfo = ErrorInfo.parse(parser);
// Fauna does not always return a valid event type (e.g. if you pass an invalid cursor).
eventType = EventType.ERROR;
break;
}
}
Expand Down Expand Up @@ -122,7 +123,7 @@ public QueryStats getStats() {
}

public ErrorInfo getError() {
return new ErrorInfo(this.error.getCode(), this.error.getMessage());
return this.error;
}


Expand Down
Loading

0 comments on commit 39bcd08

Please sign in to comment.