Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,32 @@ public class Neo4jException extends RuntimeException {
private final String code;
/**
* The GQLSTATUS as defined by the GQL standard.
*
* @since 5.26.0
*/
private final String gqlStatus;
/**
* The GQLSTATUS description.
*
* @since 5.26.0
*/
private final String statusDescription;
/**
* The diagnostic record.
*
* @since 5.26.0
*/
@SuppressWarnings("serial")
private final Map<String, Value> diagnosticRecord;
/**
* The GQLSTATUS error classification.
*
* @since 5.26.0
*/
private final GqlStatusErrorClassification classification;
/**
* The GQLSTATUS error classification as raw String.
*
* @since 5.26.0
*/
private final String rawClassification;
Expand Down Expand Up @@ -117,12 +122,13 @@ public Neo4jException(String code, String message, Throwable cause) {

/**
* Creates a new instance.
* @param gqlStatus the GQLSTATUS as defined by the GQL standard
*
* @param gqlStatus the GQLSTATUS as defined by the GQL standard
* @param statusDescription the status description
* @param code the code
* @param message the message
* @param diagnosticRecord the diagnostic record
* @param cause the cause
* @param code the code
* @param message the message
* @param diagnosticRecord the diagnostic record
* @param cause the cause
* @since 5.26.0
*/
@Preview(name = "GQL-error")
Expand Down Expand Up @@ -236,19 +242,56 @@ public Optional<String> rawClassification() {
*/
@Preview(name = "GQL-error")
public Optional<Neo4jException> gqlCause() {
return findFirstGqlCause(this, Neo4jException.class);
return Optional.ofNullable(findFirstGqlCause(this));
}

/**
* Returns whether there is an error with the given GQLSTATUS in this GQL error chain, beginning the search from
* this exception.
*
* @param gqlStatus the GQLSTATUS
* @return {@literal true} if yes or {@literal false} otherwise
* @since 5.28.8
*/
@Preview(name = "GQL-error")
public boolean containsGqlStatus(String gqlStatus) {
return findByGqlStatus(this, gqlStatus) != null;
}

/**
* Finds the first {@link Neo4jException} that has the given GQLSTATUS in this GQL error chain, beginning the search
* from this exception.
*
* @param gqlStatus the GQLSTATUS
* @return an {@link Optional} of {@link Neo4jException} or {@link Optional#empty()} otherwise
* @since 5.28.8
*/
@Preview(name = "GQL-error")
public Optional<Neo4jException> findByGqlStatus(String gqlStatus) {
return Optional.ofNullable(findByGqlStatus(this, gqlStatus));
}

@SuppressWarnings("DuplicatedCode")
private static <T extends Throwable> Optional<T> findFirstGqlCause(Throwable throwable, Class<T> targetCls) {
private static Neo4jException findFirstGqlCause(Throwable throwable) {
var cause = throwable.getCause();
if (cause == null) {
return Optional.empty();
}
if (targetCls.isAssignableFrom(cause.getClass())) {
return Optional.of(targetCls.cast(cause));
if (cause instanceof Neo4jException neo4jException) {
return neo4jException;
} else {
return Optional.empty();
return null;
}
}

private static Neo4jException findByGqlStatus(Neo4jException neo4jException, String gqlStatus) {
Objects.requireNonNull(gqlStatus);
Neo4jException result = null;
var gqlError = neo4jException;
while (gqlError != null) {
if (gqlError.gqlStatus().equals(gqlStatus)) {
result = gqlError;
break;
}
gqlError = findFirstGqlCause(gqlError);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.neo4j.driver.exceptions;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Collections;
import java.util.Map;
Expand Down Expand Up @@ -84,6 +85,71 @@ void shouldFindGqlCauseOnNonInterruptedChainOnly() {
assertError(exception2, exception3, null);
}

@ParameterizedTest
@MethodSource("gqlErrorsArgs")
void shouldFindByGqlStatus(Neo4jException exception, int statusIndex, boolean shouldBeFound) {
// given
var status = "status" + statusIndex;

// when
var exceptionWithStatus = exception.findByGqlStatus(status);

// then
if (shouldBeFound) {
assertTrue(exceptionWithStatus.isPresent());
assertEquals(getByIndex(exception, statusIndex), exceptionWithStatus.get());
} else {
assertTrue(exceptionWithStatus.isEmpty());
}
}

@ParameterizedTest
@MethodSource("gqlErrorsArgs")
void shouldReturnIfErrorContainsGqlStatus(Neo4jException exception, int statusIndex, boolean shouldBeFound) {
// given
var status = "status" + statusIndex;

// when
var contains = exception.containsGqlStatus(status);

// then
assertEquals(shouldBeFound, contains);
}

static Stream<Arguments> gqlErrorsArgs() {
return Stream.of(
Arguments.of(buildGqlErrors(1, 1), 0, true),
Arguments.of(buildGqlErrors(1, 1), -1, false),
Arguments.of(buildGqlErrors(20, 20), 0, true),
Arguments.of(buildGqlErrors(20, 20), 10, true),
Arguments.of(buildGqlErrors(20, 20), 19, true),
Arguments.of(buildGqlErrors(20, 20), -1, false),
Arguments.of(buildGqlErrors(20, 15), 0, true),
Arguments.of(buildGqlErrors(20, 15), 14, true),
Arguments.of(buildGqlErrors(20, 15), -1, false));
}

@SuppressWarnings("DataFlowIssue")
static Neo4jException buildGqlErrors(int totalSize, int gqlErrorsSize) {
Exception exception = null;
for (var i = totalSize - 1; i >= gqlErrorsSize; i--) {
exception = new IllegalStateException("illegal state" + i, exception);
}
for (var i = gqlErrorsSize - 1; i >= 0; i--) {
exception = new Neo4jException(
"status" + i, "description" + i, "code", "message", Collections.emptyMap(), exception);
}
return (Neo4jException) exception;
}

static Throwable getByIndex(Throwable exception, int depth) {
var result = exception;
for (var i = 0; i < depth; i++) {
result = result.getCause();
}
return result;
}

private void assertError(Neo4jException exception, Throwable expectedCause, Neo4jException expectedGqlCause) {
assertEquals(expectedCause, exception.getCause());
assertEquals(expectedGqlCause, exception.gqlCause().orElse(null));
Expand Down