Skip to content

feat(api): return structured error code and hint for Cypher query failures - #3241

Open
n24q02m wants to merge 1 commit into
apache:masterfrom
n24q02m:cypher-error-metadata-u5
Open

n24q02m wants to merge 1 commit into
apache:masterfrom
n24q02m:cypher-error-metadata-u5

Conversation

@n24q02m

@n24q02m n24q02m commented Sep 26, 2026

Copy link
Copy Markdown

Purpose of the PR

Machine-readable error metadata for the Cypher API: stable error codes plus per-error hint strings, so clients can branch on failure modes instead of parsing message text.

Complements #238 (bound-query support, currently in hugegraph/hugegraph): that change adds validated parameter bindings; this change makes Cypher failures carry a structured errors array. The two are independent — this branch applies cleanly on master (ed8344d19, single commit, Signed-off-by present).

Main Changes

  • NEW CypherErrorMapper (+67): maps Cypher op-processor failure classes to stable error codes and per-class hint strings.
  • CypherModel (+27): errors[].code / message / hint payload fields.
  • CypherClient (+3 −2): surfaces mapped errors to the client.
  • CypherApiTest (+18): structured-error assertions — syntax failure returns "errors" with SyntaxError and a "Declare the variable" hint; undefined schema element returns ExecutionError with a "Create the schema element" hint.

Verifying these changes

  • Trivial rework / code cleanup without any test coverage. (No Need)
  • Already covered by existing tests.
  • Need tests and can be verified as follows:
    • Java 17; mvn -DskipTests test-compile on this exact branch: clean.
    • Same mapper code on the author's fork branch: CypherApiTest 10/10 incl. error-code assertions, plus live REST verification 2026-09-26 — syntax error → errors:[{"code":"HugeGraph.Cypher.ExecutionError","message":"Invalid input …","hint":""}], valid query → 200 with errors:[].

Does this PR potentially affect the following parts?

  • Dependencies
  • Modify configurations
  • The public API (error response shape for /cypher)
  • Other affects
  • Nope

Documentation Status

Notes

…lures

Signed-off-by: Nguyen Quang Minh <135627235+n24q02m@users.noreply.github.com>
@codecov

codecov Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.53846% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.45%. Comparing base (2f827d6) to head (ed8344d).

Files with missing lines Patch % Lines
...apache/hugegraph/api/cypher/CypherErrorMapper.java 35.71% 4 Missing and 5 partials ⚠️
...a/org/apache/hugegraph/api/cypher/CypherModel.java 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3241      +/-   ##
============================================
+ Coverage     41.40%   41.45%   +0.05%     
- Complexity     7337     7349      +12     
============================================
  Files           802      803       +1     
  Lines         69792    69817      +25     
  Branches       9312     9319       +7     
============================================
+ Hits          28897    28944      +47     
+ Misses        37604    37577      -27     
- Partials       3291     3296       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Review effort: Lite
Findings: 1 Medium severity · 3 Low severity

Open (4)
What changed in this PR

Adds structured Cypher error metadata with stable codes and hints, exposing it through the API and covering representative failures.

Changes:

  • Adds errors[].code, message, and hint to responses.
  • Maps common Cypher failures to structured classifications.
  • Adds API tests for syntax and schema errors.
File Description
hugegraph-server/​hugegraph-test/​src/​main/​java/​org/​apache/​hugegraph/​api/​CypherApiTest.java Updated as part of this pull request.
hugegraph-server/​hugegraph-api/​src/​main/​java/​org/​apache/​hugegraph/​api/​cypher/​CypherModel.java Updated as part of this pull request.
hugegraph-server/​hugegraph-api/​src/​main/​java/​org/​apache/​hugegraph/​api/​cypher/​CypherErrorMapper.java Updated as part of this pull request.
hugegraph-server/​hugegraph-api/​src/​main/​java/​org/​apache/​hugegraph/​api/​cypher/​CypherClient.java Updated as part of this pull request.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +43 to +46
if (lower.contains("not defined") && lower.contains("$")) {
return new CypherModel.CypherError(MISSING_PARAMETER, message,
"Provide the value via the 'parameters' query argument, " +
"e.g. ?parameters=%7B%22city%22%3A%20%22Beijing%22%7D");
Comment on lines +44 to +46
return new CypherModel.CypherError(MISSING_PARAMETER, message,
"Provide the value via the 'parameters' query argument, " +
"e.g. ?parameters=%7B%22city%22%3A%20%22Beijing%22%7D");
Comment on lines +40 to +41
@Schema(description = "Structured errors, empty on success")
public List<CypherError> errors = Collections.emptyList();
Comment on lines +80 to +82
assertContains("\"errors\"", content);
assertContains("SyntaxError", content);
assertContains("Declare the variable", content);

@bitflicker64 bitflicker64 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: yes. Summary: The new top-level errors field is written on every /cypher response, including successful ones, and hugegraph-client cannot parse a response that carries it, so HugeClient.cypher() and Hubble's Cypher queries fail after this change. The mapper also labels real parse errors as ExecutionError, so the new SyntaxError code does not identify syntax errors. Evidence: I ran RestResult.readObject(Response.class) from hugegraph-client 1.8.0 on the response shape before and after this PR; the old shape parses, the new one throws UnrecognizedPropertyException: Unrecognized field "errors". I ran opencypher-gremlin translation 1.0.4 with HugeGraph's default translator definition to capture the messages the mapper sees. CI at ed8344d is green; the in-repo tests read the body as a raw string and do not exercise the Java client.

public Result result = new Result();

@Schema(description = "Structured errors, empty on success")
public List<CypherError> errors = Collections.emptyList();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: This field breaks every existing Java client call to /cypher, successful queries included. errors defaults to Collections.emptyList() and Jersey's JacksonFeature serializes it, so every response now ends with "errors":[]. hugegraph-toolchain's CypherAPI.post parses the body with RestResult.readObject(org.apache.hugegraph.structure.gremlin.Response.class). That class declares only requestId, status and result and has no ignoreUnknown, and RestResult uses a plain ObjectMapper, where FAIL_ON_UNKNOWN_PROPERTIES is on by default.

Evidence: with hugegraph-client-1.8.0.jar and hugegraph-common-1.7.0.jar, new RestResult(200, body, null).readObject(Response.class) parses the current response shape. With ,"errors":[] appended it throws SerializeException caused by UnrecognizedPropertyException: Unrecognized field "errors" (class org.apache.hugegraph.structure.gremlin.Response), not marked as ignorable. CypherManager.execute() and Hubble's QueryService.executeCypher() both go through that path. CI does not catch this because CypherApiTest checks the body as a string.

Requested change: keep the top-level shape unchanged. The existing status.attributes map (Map<String, Object>, already accepted by the client as Map<String, ?>) can carry the code and hint on failures, for example attributes = {"code": ..., "hint": ...}. If a top-level errors field is still wanted, land @JsonIgnoreProperties(ignoreUnknown = true) on the client's Response in hugegraph-toolchain and release it first, and do not emit the field on success in the meantime.

"Create the schema element via the schema API before " +
"running the query");
}
if (lower.contains("not defined") || lower.contains("undefined")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: SyntaxError is assigned only when the message contains "not defined" or "undefined", so real parse errors fall through to ExecutionError with an empty hint on line 65. The PR's goal is to let clients branch on the failure mode, and these codes are described as stable, so a client that checks for HugeGraph.Cypher.SyntaxError will miss most syntax errors.

Evidence: I ran CypherAst.parse(q, {}) from translation 1.0.4 with gremlin+cfog_server_extensions+inline_parameters:

  • MATCH (n:person RETURN n gives SyntaxException: Invalid input 'R': expected ..., mapped to ExecutionError.
  • MATCH (n:person) RETRUN n gives Invalid input 'R': expected 'u/U', mapped to ExecutionError.
  • MATCH (n) RETURN n; MATCH (m) RETURN m gives Expected exactly one statement per query but got: 2, mapped to ExecutionError.
  • RETURN date() gives SyntaxException: Unknown function 'date', mapped to ExecutionError.
  • MATCH (n:person) RETURN not_defined_var gives "Variable not_defined_var not defined", the only one mapped to SyntaxError.

The PR description's own live check shows the same result: a syntax error returned HugeGraph.Cypher.ExecutionError with "hint":"". The branch also catches any other HugeGraph message with "undefined", such as Undefined index label: 'x', and tells the user to declare a variable.

Requested change: match the parser messages explicitly (invalid input, expected exactly one statement, unknown function, variable ... not defined) for SyntaxError, narrow the variable hint to the Variable ... not defined message, and add a test that sends a real parse error such as MATCH (n:person RETURN n and asserts "code":"HugeGraph.Cypher.SyntaxError".

"^([a-zA-Z0-9_]+\\.)+[A-Za-z0-9_]+(Exception|Error):\\s*", "");
String lower = message.toLowerCase();

if (lower.contains("not defined") && lower.contains("$")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: This branch cannot fire with the server's default configuration, so MissingParameter is a published code that is never returned. CypherClient.createRequest() sends no bindings, and CypherOpProcessor uses inline_parameters. With an empty parameter map, translation 1.0.4 turns MATCH (n:person) WHERE n.name = $name RETURN n into a traversal that compares against ' cypher.null' and raises no error, so the query returns no rows. Variable-not-defined messages never contain $.

Requested change: drop MISSING_PARAMETER and this branch until the API accepts parameters, together with the ?parameters= hint already raised on line 46.

@n24q02m

n24q02m commented Oct 6, 2026

Copy link
Copy Markdown
Author

Pushed ca0ef91:

Success responses keep the old shape now — errors is annotated @JsonInclude(NON_EMPTY) so the key is only present on failures. testSuccessResponseKeepsLegacyShape parses the live success body with a strict FAIL_ON_UNKNOWN_PROPERTIES mapper, which is the same failure mode as RestResult.readObject in hugegraph-client — that test failed against the previous commit's shape.

The SyntaxError routing was defeated by our own prefix-stripping: the class prefix was stripped before classification, so no viable alternative at input ... fell through to ExecutionError. Classification now runs on the exception class name and the full message first (translator SyntaxException/ParseException + ANTLR markers), prefix stripped afterwards for the returned message — covered for wrapped rethrows too.

Agreed on the test point; the strict-parse test enforces the exact wire contract (no unknown top-level fields on success) without depending on client internals. build-server (memory/hbase/rocksdb) is green on the fix commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants