Skip to content

Commit 1bd71fd

Browse files
authored
feat(gql-error): add GQLSTATUS finders (#1672)
In some situations it might be needed to check if GQL error or any error in its GQL error chain has a specific GQLSTATUS. This update adds 2 new methods to `Neo4jException` to make it easier: - `boolean containsGqlStatus(String gqlStatus)` - `Optional<Neo4jException> findByGqlStatus(String gqlStatus)`
1 parent 816522a commit 1bd71fd

File tree

2 files changed

+122
-13
lines changed

2 files changed

+122
-13
lines changed

driver/src/main/java/org/neo4j/driver/exceptions/Neo4jException.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,31 @@ public class Neo4jException extends RuntimeException {
4040
private final String code;
4141
/**
4242
* The GQLSTATUS as defined by the GQL standard.
43+
*
4344
* @since 5.26.0
4445
*/
4546
private final String gqlStatus;
4647
/**
4748
* The GQLSTATUS description.
49+
*
4850
* @since 5.26.0
4951
*/
5052
private final String statusDescription;
5153
/**
5254
* The diagnostic record.
55+
*
5356
* @since 5.26.0
5457
*/
5558
private final Map<String, Value> diagnosticRecord;
5659
/**
5760
* The GQLSTATUS error classification.
61+
*
5862
* @since 5.26.0
5963
*/
6064
private final GqlStatusErrorClassification classification;
6165
/**
6266
* The GQLSTATUS error classification as raw String.
67+
*
6368
* @since 5.26.0
6469
*/
6570
private final String rawClassification;
@@ -116,12 +121,13 @@ public Neo4jException(String code, String message, Throwable cause) {
116121

117122
/**
118123
* Creates a new instance.
119-
* @param gqlStatus the GQLSTATUS as defined by the GQL standard
124+
*
125+
* @param gqlStatus the GQLSTATUS as defined by the GQL standard
120126
* @param statusDescription the status description
121-
* @param code the code
122-
* @param message the message
123-
* @param diagnosticRecord the diagnostic record
124-
* @param cause the cause
127+
* @param code the code
128+
* @param message the message
129+
* @param diagnosticRecord the diagnostic record
130+
* @param cause the cause
125131
* @since 5.26.0
126132
*/
127133
@Preview(name = "GQL-error")
@@ -235,19 +241,56 @@ public Optional<String> rawClassification() {
235241
*/
236242
@Preview(name = "GQL-error")
237243
public Optional<Neo4jException> gqlCause() {
238-
return findFirstGqlCause(this, Neo4jException.class);
244+
return Optional.ofNullable(findFirstGqlCause(this));
245+
}
246+
247+
/**
248+
* Returns whether there is an error with the given GQLSTATUS in this GQL error chain, beginning the search from
249+
* this exception.
250+
*
251+
* @param gqlStatus the GQLSTATUS
252+
* @return {@literal true} if yes or {@literal false} otherwise
253+
* @since 5.28.8
254+
*/
255+
@Preview(name = "GQL-error")
256+
public boolean containsGqlStatus(String gqlStatus) {
257+
return findByGqlStatus(this, gqlStatus) != null;
258+
}
259+
260+
/**
261+
* Finds the first {@link Neo4jException} that has the given GQLSTATUS in this GQL error chain, beginning the search
262+
* from this exception.
263+
*
264+
* @param gqlStatus the GQLSTATUS
265+
* @return an {@link Optional} of {@link Neo4jException} or {@link Optional#empty()} otherwise
266+
* @since 5.28.8
267+
*/
268+
@Preview(name = "GQL-error")
269+
public Optional<Neo4jException> findByGqlStatus(String gqlStatus) {
270+
return Optional.ofNullable(findByGqlStatus(this, gqlStatus));
239271
}
240272

241273
@SuppressWarnings("DuplicatedCode")
242-
private static <T extends Throwable> Optional<T> findFirstGqlCause(Throwable throwable, Class<T> targetCls) {
274+
private static Neo4jException findFirstGqlCause(Throwable throwable) {
243275
var cause = throwable.getCause();
244-
if (cause == null) {
245-
return Optional.empty();
246-
}
247-
if (targetCls.isAssignableFrom(cause.getClass())) {
248-
return Optional.of(targetCls.cast(cause));
276+
if (cause instanceof Neo4jException neo4jException) {
277+
return neo4jException;
249278
} else {
250-
return Optional.empty();
279+
return null;
280+
}
281+
}
282+
283+
private static Neo4jException findByGqlStatus(Neo4jException neo4jException, String gqlStatus) {
284+
Objects.requireNonNull(gqlStatus);
285+
Neo4jException result = null;
286+
var gqlError = neo4jException;
287+
while (gqlError != null) {
288+
if (gqlError.gqlStatus().equals(gqlStatus)) {
289+
result = gqlError;
290+
break;
291+
}
292+
gqlError = findFirstGqlCause(gqlError);
251293
}
294+
return result;
252295
}
253296
}

driver/src/test/java/org/neo4j/driver/exceptions/Neo4jExceptionTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.neo4j.driver.exceptions;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
2021

2122
import java.util.Collections;
2223
import java.util.Map;
@@ -84,6 +85,71 @@ void shouldFindGqlCauseOnNonInterruptedChainOnly() {
8485
assertError(exception2, exception3, null);
8586
}
8687

88+
@ParameterizedTest
89+
@MethodSource("gqlErrorsArgs")
90+
void shouldFindByGqlStatus(Neo4jException exception, int statusIndex, boolean shouldBeFound) {
91+
// given
92+
var status = "status" + statusIndex;
93+
94+
// when
95+
var exceptionWithStatus = exception.findByGqlStatus(status);
96+
97+
// then
98+
if (shouldBeFound) {
99+
assertTrue(exceptionWithStatus.isPresent());
100+
assertEquals(getByIndex(exception, statusIndex), exceptionWithStatus.get());
101+
} else {
102+
assertTrue(exceptionWithStatus.isEmpty());
103+
}
104+
}
105+
106+
@ParameterizedTest
107+
@MethodSource("gqlErrorsArgs")
108+
void shouldReturnIfErrorContainsGqlStatus(Neo4jException exception, int statusIndex, boolean shouldBeFound) {
109+
// given
110+
var status = "status" + statusIndex;
111+
112+
// when
113+
var contains = exception.containsGqlStatus(status);
114+
115+
// then
116+
assertEquals(shouldBeFound, contains);
117+
}
118+
119+
static Stream<Arguments> gqlErrorsArgs() {
120+
return Stream.of(
121+
Arguments.of(buildGqlErrors(1, 1), 0, true),
122+
Arguments.of(buildGqlErrors(1, 1), -1, false),
123+
Arguments.of(buildGqlErrors(20, 20), 0, true),
124+
Arguments.of(buildGqlErrors(20, 20), 10, true),
125+
Arguments.of(buildGqlErrors(20, 20), 19, true),
126+
Arguments.of(buildGqlErrors(20, 20), -1, false),
127+
Arguments.of(buildGqlErrors(20, 15), 0, true),
128+
Arguments.of(buildGqlErrors(20, 15), 14, true),
129+
Arguments.of(buildGqlErrors(20, 15), -1, false));
130+
}
131+
132+
@SuppressWarnings("DataFlowIssue")
133+
static Neo4jException buildGqlErrors(int totalSize, int gqlErrorsSize) {
134+
Exception exception = null;
135+
for (var i = totalSize - 1; i >= gqlErrorsSize; i--) {
136+
exception = new IllegalStateException("illegal state" + i, exception);
137+
}
138+
for (var i = gqlErrorsSize - 1; i >= 0; i--) {
139+
exception = new Neo4jException(
140+
"status" + i, "description" + i, "code", "message", Collections.emptyMap(), exception);
141+
}
142+
return (Neo4jException) exception;
143+
}
144+
145+
static Throwable getByIndex(Throwable exception, int depth) {
146+
var result = exception;
147+
for (var i = 0; i < depth; i++) {
148+
result = result.getCause();
149+
}
150+
return result;
151+
}
152+
87153
private void assertError(Neo4jException exception, Throwable expectedCause, Neo4jException expectedGqlCause) {
88154
assertEquals(expectedCause, exception.getCause());
89155
assertEquals(expectedGqlCause, exception.gqlCause().orElse(null));

0 commit comments

Comments
 (0)