Better error handling
Perceived Problem
- Error handling is confusing
- There is an error policy configuration that allows errors to be returned or thrown
- That configuration has the concept of
ignorewhich is confusing - When reading the code, it is not clear what will happen
- When interacting with the types, it is not clear what will happen b/c TS implementation lacks type mapping that reflects the current config https://github.com/jasonkuhrt/graphql-request/issues/174#issuecomment-1177314916
- HTTP errors are always thrown, only GraphQL errors part of the above system
- input validation will also lead to throws. For example
abcas a url leads to a thrown fetch error ofTypeError: Only absolute URLs are supported.
Ideas / Proposed Solution(s)
-
Remove error policy configuration
-
Make
OrThrowfunction variants -
Make current functions default to not throwing (BREAKING CHANGE)
equest() equestOrThrow() ew GraphQLCLient().request() ew GraphQLCLient().requestOrThrow() ew GraphQLCLient().rawRequest() ew GraphQLCLient().rawRequestOrThrow() -
When throw is not used, make the return type of
requestbe a union ofHTTPError|TypedAggregateError<GraphQLError>|Data -
For
rawRequestmake it beGraphQLRequestResponse< HTTPError|TypedAggregateError<GraphQLError>|Data> -
Add an error mode configuration that allows GraphQL errors to coexist with data.
request({ errorMode:'coexist'|'exclusive'}). When'coexist'is used, then the return types change such thatdataanderrorsare together in an envelope:{ data: Data, error: TypedAggregateError<GraphQLError> }. -
Note:
TypedAggregateErroris to handle multiple GraphQL errors in a type safe way.AggregateErrorhasany[]for theerrorsfield. -
make
exclusivemode be default
In summary:
Exclusive Mode (default)
request() | GraphQLRequestHTTPError
| TypedAggregateError<GraphQLError>
| Data
rawRequest() GraphQLRequestResponse<
| GraphQLRequestHTTPError
| TypedAggregateError<GraphQLError>
| Data
>
requestOrThrow() Data
rawRequestOrThrow() GraphQLRequestResponse<Data>
Coexist Mode
request() | GraphQLRequestHTTPError
| {error: TypedAggregateError<GraphQLError>; data: Data }
rawRequest() GraphQLRequestResponse<
| GraphQLRequestHTTPError
| {error: TypedAggregateError<GraphQLError>; data: Data }
>
requestOrThrow() Data
rawRequestOrThrow() GraphQLRequestResponse<Data>
Is this subject to a vote? I am strongly in favor of rawRequest() always throwing, as it was before, and as it is now. Our code base deepens on it and a breaking change not only will force refactoring. I think throwing an exception should be default behavior, as it it not only a widely accepted error handling method, it is the one that is offered by the language itself, and the one that is used consistently in all relatively recent additions to the standard library.
I can see that in some cases, it's maybe more convenient to receive an error via return value, but it shouldn't be the default behavior.
Proposal for exclusive mode: https://github.com/supermacro/neverthrow
I think it is fine not to throw by default, because GraphQL is designed around the idea, that errors are possible and only partial data is returned. The orThrow naming is also easy to understand.
@AlexIII IMO Throw is a language mistake. That TS doesn’t type it (understandably) exacerbates its faults. I’m aware it will impact existing users, but it’s strictly an improvement to not throw by default. And the refactor is straight forward, a few second find replace.
@P4sca1 Cool looking library, but if we were to bless a library data structure returned by this library I would probably look to use the fp-ts ecosystem which now also include Effect.
@jasonkuhrt While I agree that generally, in programming, exceptions have proven to be inferior to the functional style, discussing shortcomings of the language is a bit out of the scope of one single library. No language is perfect, but the least we can do is to maintain consistency. I don't believe that refusing to use exception even in the most popular of libraries will ever EVER affect the language specification and the standard library (which everyone is forced to use weather one like it or not).
I do realize you coming that hard on the question, there's probably no way to persuade. Still, I find it necessary to voice this argument.
Not throwing by default is an approach Prisma takes in some of its APIs, including an *orThrow suffix set of methods. Point being it's not a fringe approach and you con find it around the ecosystem, probably more and more rather than less and less.
I think making graphql-request output an fp-ts Either type would be a step too far in opinionation for example, but I don't think making throw be more explicit is.
I appreciate the thoughts and for-the-record capture of it here though thanks!
I'd love to see some improvement in the error handling!
Do you intend to address this quirk too? https://github.com/jasonkuhrt/graphql-request/issues/201
@beeman Generally yes. If there is a mistake this proposal repeats from there, or an aspect not addressed by this proposal, let me know!
I think we can add a settings system that would allow the libraries default behaviour to be configured per what @AlexIII is asking for. There would still be a default-default but I'm totally open to being able to change the default globally.
I am closing this as the WIP ts-client has a version of this going even further to account for schema errors. What people today know about graphql-request will become the "raw" client. The current API will not be changed but instead turned into a deprecated unchanging state starting in the next release with focus going thereafter toward the TS client.