Skip to content

Commit c1c2355

Browse files
committed
Some general improvements to integrate reports better
Signed-off-by: Simeon Widdis <sawiddis@amazon.com>
1 parent b397eac commit c1c2355

File tree

7 files changed

+354
-67
lines changed

7 files changed

+354
-67
lines changed

common/src/main/java/org/opensearch/sql/common/error/ErrorReport.java

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.LinkedHashMap;
1010
import java.util.List;
1111
import java.util.Map;
12+
import lombok.Getter;
1213

1314
/**
1415
* Error report that wraps exceptions and accumulates contextual information as errors bubble up
@@ -37,12 +38,12 @@
3738
public class ErrorReport extends RuntimeException {
3839

3940
private final Throwable cause;
40-
private final ErrorCode code;
41-
private final QueryProcessingStage stage;
41+
@Getter private final ErrorCode code;
42+
@Getter private final QueryProcessingStage stage;
4243
private final List<String> locationChain;
4344
private final Map<String, Object> context;
44-
private final String suggestion;
45-
private final String details;
45+
@Getter private final String suggestion;
46+
@Getter private final String details;
4647

4748
private ErrorReport(Builder builder) {
4849
super(builder.cause.getMessage(), builder.cause);
@@ -63,8 +64,7 @@ private ErrorReport(Builder builder) {
6364
* @return A builder for constructing the error report
6465
*/
6566
public static Builder wrap(Throwable cause) {
66-
if (cause instanceof ErrorReport) {
67-
ErrorReport existing = (ErrorReport) cause;
67+
if (cause instanceof ErrorReport existing) {
6868
return new Builder(existing.cause)
6969
.code(existing.code)
7070
.stage(existing.stage)
@@ -76,14 +76,6 @@ public static Builder wrap(Throwable cause) {
7676
return new Builder(cause);
7777
}
7878

79-
public ErrorCode getCode() {
80-
return code;
81-
}
82-
83-
public QueryProcessingStage getStage() {
84-
return stage;
85-
}
86-
8779
public List<String> getLocationChain() {
8880
return new ArrayList<>(locationChain);
8981
}
@@ -92,14 +84,6 @@ public Map<String, Object> getContext() {
9284
return new LinkedHashMap<>(context);
9385
}
9486

95-
public String getSuggestion() {
96-
return suggestion;
97-
}
98-
99-
public String getDetails() {
100-
return details;
101-
}
102-
10387
/** Get the original exception type name. */
10488
public String getExceptionType() {
10589
return cause.getClass().getSimpleName();
@@ -155,7 +139,7 @@ public Map<String, Object> toJsonMap() {
155139

156140
json.put("type", getExceptionType());
157141

158-
if (code != null && code != ErrorCode.UNKNOWN) {
142+
if (code != null) {
159143
json.put("code", code.name());
160144
}
161145

@@ -168,12 +152,12 @@ public Map<String, Object> toJsonMap() {
168152
}
169153

170154
// Build context with stage information included
171-
if (!context.isEmpty() || stage != null) {
172-
Map<String, Object> contextMap = new LinkedHashMap<>(context);
173-
if (stage != null) {
174-
contextMap.put("stage", stage.toJsonKey());
175-
contextMap.put("stage_description", stage.getDisplayName());
176-
}
155+
Map<String, Object> contextMap = new LinkedHashMap<>(context);
156+
if (stage != null) {
157+
contextMap.put("stage", stage.toJsonKey());
158+
contextMap.put("stage_description", stage.getDisplayName());
159+
}
160+
if (!contextMap.isEmpty()) {
177161
json.put("context", contextMap);
178162
}
179163

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.common.error;
7+
8+
import java.util.function.Predicate;
9+
10+
/**
11+
* Helper class for determining HTTP status codes from ErrorReport instances. Provides utilities to
12+
* check if an exception (potentially wrapped in ErrorReport) should result in a client error (4xx)
13+
* or server error (5xx) status code.
14+
*/
15+
public class ErrorReportStatusCodeHelper {
16+
17+
/**
18+
* Determines if an exception should be treated as a client error (400 BAD_REQUEST) based on:
19+
*
20+
* <ol>
21+
* <li>ErrorCode (if wrapped in ErrorReport)
22+
* <li>Underlying exception type (checked via provided predicate)
23+
* </ol>
24+
*
25+
* @param e the exception to check
26+
* @param exceptionTypeChecker predicate that checks if a given exception type is a client error
27+
* @return true if this should result in a 4xx status code, false for 5xx
28+
*/
29+
public static boolean isClientError(Exception e, Predicate<Exception> exceptionTypeChecker) {
30+
// If wrapped in ErrorReport, check both the ErrorCode and the underlying cause
31+
if (e instanceof ErrorReport) {
32+
ErrorReport report = (ErrorReport) e;
33+
34+
// Check if the ErrorCode indicates a client error
35+
if (isClientErrorCode(report.getCode())) {
36+
return true;
37+
}
38+
39+
// Fall through to check the underlying exception type
40+
Throwable cause = e.getCause();
41+
if (cause instanceof Exception) {
42+
return exceptionTypeChecker.test((Exception) cause);
43+
}
44+
}
45+
46+
// Check the exception type directly
47+
return exceptionTypeChecker.test(e);
48+
}
49+
50+
/**
51+
* Maps ErrorCode values to client error (400) vs server error (500) status codes.
52+
*
53+
* <p>Client errors (400):
54+
*
55+
* <ul>
56+
* <li>FIELD_NOT_FOUND - invalid field reference
57+
* <li>SYNTAX_ERROR - query parsing failed
58+
* <li>AMBIGUOUS_FIELD - multiple fields with same name
59+
* <li>SEMANTIC_ERROR - semantic validation failed
60+
* <li>TYPE_ERROR - type mismatch
61+
* <li>UNSUPPORTED_OPERATION - feature not supported
62+
* <li>INDEX_NOT_FOUND - index doesn't exist
63+
* </ul>
64+
*
65+
* <p>Server errors (500):
66+
*
67+
* <ul>
68+
* <li>EVALUATION_ERROR - runtime evaluation failed
69+
* <li>PLANNING_ERROR - query planning failed
70+
* <li>EXECUTION_ERROR - query execution failed
71+
* <li>RESOURCE_LIMIT_EXCEEDED - system resource limit
72+
* <li>UNKNOWN - unclassified error
73+
* </ul>
74+
*
75+
* @param code the ErrorCode to check
76+
* @return true if this ErrorCode indicates a client error (400), false for server error (500)
77+
*/
78+
private static boolean isClientErrorCode(ErrorCode code) {
79+
if (code == null) {
80+
return false;
81+
}
82+
83+
switch (code) {
84+
case FIELD_NOT_FOUND:
85+
case SYNTAX_ERROR:
86+
case AMBIGUOUS_FIELD:
87+
case SEMANTIC_ERROR:
88+
case TYPE_ERROR:
89+
case UNSUPPORTED_OPERATION:
90+
case INDEX_NOT_FOUND:
91+
return true;
92+
93+
case EVALUATION_ERROR:
94+
case PLANNING_ERROR:
95+
case EXECUTION_ERROR:
96+
case RESOURCE_LIMIT_EXCEEDED:
97+
case UNKNOWN:
98+
default:
99+
return false;
100+
}
101+
}
102+
}

core/src/main/java/org/opensearch/sql/calcite/QualifiedNameResolver.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import org.apache.logging.log4j.LogManager;
1717
import org.apache.logging.log4j.Logger;
1818
import org.opensearch.sql.ast.expression.QualifiedName;
19+
import org.opensearch.sql.common.error.ErrorCode;
20+
import org.opensearch.sql.common.error.ErrorReport;
1921
import org.opensearch.sql.expression.function.BuiltinFunctionName;
2022
import org.opensearch.sql.expression.function.PPLFuncImpTable;
2123

@@ -322,7 +324,10 @@ private static Optional<RexNode> replaceWithNullLiteralInCoalesce(CalcitePlanCon
322324
return Optional.empty();
323325
}
324326

325-
private static RuntimeException getNotFoundException(QualifiedName node) {
326-
return new IllegalArgumentException(String.format("Field [%s] not found.", node.toString()));
327+
private static ErrorReport getNotFoundException(QualifiedName node) {
328+
return ErrorReport.wrap(
329+
new IllegalArgumentException(String.format("Field [%s] not found.", node.toString())))
330+
.code(ErrorCode.FIELD_NOT_FOUND)
331+
.build();
327332
}
328333
}

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteErrorReportStageIT.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public void testFieldNotFoundErrorIncludesStage() throws IOException {
4949
String stageDescription = context.getString("stage_description");
5050
assertTrue(
5151
"Stage description should be user-friendly",
52-
stageDescription.contains("Checking") || stageDescription.contains("Query"));
52+
stageDescription.toLowerCase().contains("checking")
53+
|| stageDescription.toLowerCase().contains("query"));
5354

5455
// Verify error has location chain
5556
assertTrue("Error should have location", error.has("location"));

0 commit comments

Comments
 (0)