Skip to content

Commit ea6d569

Browse files
authored
feat(gql-error): add GQLSTATUS finders (#1671)
* feat(gql-error): add GQLSTATUS finders 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)` * Improve name and documentation * Shorten name * Update documentation * Fix article
1 parent f0fc36d commit ea6d569

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,27 +40,32 @@ 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
@SuppressWarnings("serial")
5659
private final Map<String, Value> diagnosticRecord;
5760
/**
5861
* The GQLSTATUS error classification.
62+
*
5963
* @since 5.26.0
6064
*/
6165
private final GqlStatusErrorClassification classification;
6266
/**
6367
* The GQLSTATUS error classification as raw String.
68+
*
6469
* @since 5.26.0
6570
*/
6671
private final String rawClassification;
@@ -117,12 +122,13 @@ public Neo4jException(String code, String message, Throwable cause) {
117122

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

242274
@SuppressWarnings("DuplicatedCode")
243-
private static <T extends Throwable> Optional<T> findFirstGqlCause(Throwable throwable, Class<T> targetCls) {
275+
private static Neo4jException findFirstGqlCause(Throwable throwable) {
244276
var cause = throwable.getCause();
245-
if (cause == null) {
246-
return Optional.empty();
247-
}
248-
if (targetCls.isAssignableFrom(cause.getClass())) {
249-
return Optional.of(targetCls.cast(cause));
277+
if (cause instanceof Neo4jException neo4jException) {
278+
return neo4jException;
250279
} else {
251-
return Optional.empty();
280+
return null;
281+
}
282+
}
283+
284+
private static Neo4jException findByGqlStatus(Neo4jException neo4jException, String gqlStatus) {
285+
Objects.requireNonNull(gqlStatus);
286+
Neo4jException result = null;
287+
var gqlError = neo4jException;
288+
while (gqlError != null) {
289+
if (gqlError.gqlStatus().equals(gqlStatus)) {
290+
result = gqlError;
291+
break;
292+
}
293+
gqlError = findFirstGqlCause(gqlError);
252294
}
295+
return result;
253296
}
254297
}

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)