Skip to content

turn off async-graphql's blanket error conversion - #2881

Merged
shivamka1 merged 8 commits into
fix/policy-fault-answersfrom
feat/explicit-gql-errors
Nov 5, 2026
Merged

shivamka1 merged 8 commits into
fix/policy-fault-answersfrom
feat/explicit-gql-errors

Conversation

@shivamka1

@shivamka1 shivamka1 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #2880; retarget to db_v4 once it merges. v4-storage: when its Raphtory pin moves past this PR, the same commit must add apollo_tracing and custom-error-conversion to its workspace async-graphql entry, because v4-storage builds raphtory-graphql as its own member.

async-graphql turns any error that can print itself into a client error at every ?. Whatever an internal error says reaches the caller, without an extension code, and nothing makes a resolver decide. This enables custom-error-conversion and routes every resolver error through one type, GQLError, whose conversion is the only place a client error is built.

Error Client sees
PermissionError its message and code (ACCESS_DENIED, GRAPH_NOT_FOUND, AUTH_ERROR)
AuthPolicyError the generic policy error; detail logged
AuthError its message; access denials carry ACCESS_DENIED
SPARQL and SHACL errors (rdf) their message; limits carry RESULTS_TOO_LARGE or TIMEOUT
I/O failure Internal server error; detail logged
a grant whose filter cannot be applied a generic refusal; detail logged
GraphError and the errors it wraps, PathValidationError, InsertionError, DeletionError, ParquetPathError, BatchFailures, a malformed listing filter their message, as before

Resolvers and the permission gates return GQLError, so ? converts without a map_err. Each error that reaches a resolver through GraphError has its own From impl. A blanket impl would send an io::Error through GraphError and show its text. GqlGraphError is gone, and raphtory re-exports StorageError beside read_constant_graph_properties, which returns it. The schema is unchanged.

Decisions for review:

  1. GraphError passes through unchanged. Some variants wrap storage or I/O errors and may name server paths. Should those be logged and answered generically too?
  2. Kept deliberately: the scalar parsers (timeindex.rs, property.rs) and history formatting still build errors from strings, because those describe the client's own input or data.
  3. A malformed listing filter is GQLError::InvalidFilter, while the GqlFilter conversions still use GraphError::InvalidGqlFilter (move GraphError::InvalidGqlFilter out of raphtory core #2886).

@github-actions github-actions Bot 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.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rust Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 44af998 Previous: a05fce6 Ratio
lotr_graph_subgraph_10pc/num_nodes 10 ns/iter (± 0) 4 ns/iter (± 0) 2.50
lotr_graph_subgraph_10pc/num_nodes_cached 9 ns/iter (± 0) 4 ns/iter (± 0) 2.25

This comment was automatically generated by workflow using github-action-benchmark.

@fabianmurariu fabianmurariu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@ljeub-pometry ljeub-pometry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

minor tidy

Comment thread raphtory-graphql/src/model/mod.rs Resolved
Comment thread raphtory-graphql/src/model/mod.rs Resolved
Comment thread raphtory-graphql/src/model/mod.rs Resolved
Comment thread raphtory-graphql/src/lib.rs Resolved
Comment thread raphtory-graphql/Cargo.toml Resolved
@shivamka1
shivamka1 force-pushed the fix/policy-fault-answers branch from cc84a32 to 222d0e4 Compare November 5, 2026 13:12
shivamka1 and others added 7 commits November 5, 2026 13:49
async-graphql turned any error that can print itself into a client error
at every `?`, so whatever an internal error said reached the caller, with
no extension code, and nothing made a resolver decide. With
custom-error-conversion on, each error type that reaches a resolver says
what the client sees, next to its definition:

- PermissionError keeps its code, which a `?` used to drop.
- AuthPolicyError answers the generic policy error and logs the detail.
- AuthError's access denials carry ACCESS_DENIED.
- GQLError passes the graph layer's message through, except an I/O
  failure, which is logged and answered as an internal server error.
- The other local errors pass their message through, as before.

GraphError lives in raphtory, so it reaches a resolver through GQLError,
which now accepts anything that converts into a GraphError.
The write gates spelled out AuthError's ACCESS_DENIED by hand, the
metadata summary mapped AuthPolicyError by hand, and two resolvers turned
their errors into bare strings, which also dropped any code. Each is now
a plain `?` through the error type's own conversion.
GQLError took any error convertible into GraphError, so an io::Error
behind `?` became GQLError::GraphError and reached the client with its
own text, which can name server paths. The blanket impl is gone: io
errors convert to GQLError::Io, which is logged and answered as
"Internal server error", and GraphError plus the other graph-layer
errors that relied on the blanket each convert explicitly.
apollo_tracing and custom-error-conversion move from raphtory-graphql's
own dependency entry to the workspace one, so every crate in the
workspace builds async-graphql the same way.
Resolvers and the permission gates they call returned async_graphql's
error, so a GraphError could only reach them through a map_err, and each
local error type carried its own conversion to the client error. They now
return GQLError, which gains variants for permission, auth, deletion and
batch failures, so `?` converts and GQLError's conversion is the only one
left; the codes a denial carries are set there.

GqlGraphError, five of whose six variants were unused, folds into
GQLError, and the two denials that were built by hand are typed: a
read-only context is PermissionError::ReadOnlyContext and a grant whose
filter cannot be applied is GQLError::AccessFilterMisconfigured. A
malformed namespace-listing filter is GQLError::InvalidFilter. raphtory
re-exports StorageError beside read_constant_graph_properties, which
returns it, so it converts like the other graph-layer errors.
The RDF resolvers returned GraphError or async_graphql's error and
reached the client through async-graphql's blanket Display conversion,
which custom-error-conversion turns off. They now return GQLError.
SparqlError becomes one of its variants, so the codes a SPARQL or SHACL
limit carries are set in GQLError's conversion with the others, and an
RdfError converts through it whichever resolver raises it. The SHACL
shapes-format refusal is typed as SparqlError::ShapesFormat.
@shivamka1
shivamka1 force-pushed the feat/explicit-gql-errors branch from e42f501 to 0ac353d Compare November 5, 2026 14:51
@shivamka1
shivamka1 merged commit 68a830e into fix/policy-fault-answers Nov 5, 2026
20 checks passed
@shivamka1
shivamka1 deleted the feat/explicit-gql-errors branch November 5, 2026 14:53
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