Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate error objects #12398

Merged
merged 101 commits into from
Mar 12, 2025
Merged

Separate error objects #12398

merged 101 commits into from
Mar 12, 2025

Conversation

jerelmiller
Copy link
Member

@jerelmiller jerelmiller commented Mar 4, 2025

Closes #12200

Completely replaces ApolloError with separate error types. The error type depends on the type of error encapsulated by ApolloError:

Network errors

Network errors are no longer wrapped by the client and are passed through as-is. This should help prevent issues such as the Kaspersky issue where it looked like Apollo Client was to blame since errors were shown as ApolloError.

GraphQL Errors

These are now encapsulated in a CombinedGraphQLErrors type. You can access the original error objects with the errors property.

Protocol errors

Replaced with a CombinedProtocolErrors type. You can access the original error objects with the errors property.

Client errors

These weren't really used by the client, so these have no replacement. All non-GraphQL and non-protocol errors are passed through unchanged, so these replace that.

Copy link

changeset-bot bot commented Mar 4, 2025

🦋 Changeset detected

Latest commit: ec9351c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svc-apollo-docs
Copy link

svc-apollo-docs commented Mar 4, 2025

⚠️ Docs preview not attached to branch

The preview was not built because the PR's base branch release-4.0 is not in the list of sources.

An Apollo team member can comment one of the following commands to dictate which branch to attach the preview to:

  • !docs set-base-branch version-2.6
  • !docs set-base-branch main

Build ID: 9965c20fda73417dbdaf840e

Copy link

pkg-pr-new bot commented Mar 4, 2025

npm i https://pkg.pr.new/@apollo/client@12398

commit: ec9351c

@jerelmiller jerelmiller changed the title [WIP] Separate error objects Separate error objects Mar 5, 2025
@jerelmiller jerelmiller requested a review from phryneas March 5, 2025 01:20
@jerelmiller jerelmiller added this to the Release 4.0 milestone Mar 5, 2025
@jerelmiller jerelmiller marked this pull request as ready for review March 5, 2025 01:21
*/
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export type GraphQLErrors = ReadonlyArray<GraphQLError>;

export type NetworkError = Error | ServerParseError | ServerError | null;
Copy link
Member Author

Choose a reason for hiding this comment

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

I'd be curious your thoughts on whether we should keep this type or not. Looks like its used for the onError link for the networkError property.

Copy link
Member

Choose a reason for hiding this comment

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

I'd go for an ErrorLike since the Error interface and Error class of TS overlap, which feels wonky.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will leave this for now and revisit in a future PR when we touch onError to take an ErrorLike argument rather than the separate properties.


const { hasOwnProperty } = Object.prototype;

export type ServerParseError = Error & {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is now an error subclass so instanceof works 🎉

@jerelmiller jerelmiller force-pushed the jerel/consolidate-error-properties branch from 56937ce to a8800ff Compare March 6, 2025 03:39
@jerelmiller jerelmiller force-pushed the jerel/separate-error-objects branch from 33068df to 3ea9357 Compare March 6, 2025 03:45
@jerelmiller jerelmiller force-pushed the jerel/consolidate-error-properties branch 2 times, most recently from a550a44 to aec740b Compare March 8, 2025 00:28
@jerelmiller jerelmiller force-pushed the jerel/separate-error-objects branch from 3ea9357 to 372a87b Compare March 8, 2025 01:45
Comment on lines 71 to 73
new CombinedProtocolErrors(
result.extensions[PROTOCOL_ERRORS_SYMBOL]
)
Copy link
Member

Choose a reason for hiding this comment

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

Let's move this to the code where result.extensions[PROTOCOL_ERRORS_SYMBOL] is initially set, so that onError and code in/after QueryManager see the same error instance and not two different ones.

Comment on lines +517 to +520
// TODO: Determine if we still want the behavior of always rejecting
// "network" errors (any error that isn't a CombinedGraphQLErrors).
Copy link
Member

Choose a reason for hiding this comment

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

I would be so happy if we'd change this.

Comment on lines 9347 to 9355
error: new CombinedGraphQLErrors([
new GraphQLError(
"homeWorld for character with ID 1000 could not be fetched.",
{ path: ["hero", "heroFriends", 0, "homeWorld"] }
),
]),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
error: new CombinedGraphQLErrors([
new GraphQLError(
"homeWorld for character with ID 1000 could not be fetched.",
{ path: ["hero", "heroFriends", 0, "homeWorld"] }
),
]),
error: new CombinedGraphQLErrors([
{
message: "homeWorld for character with ID 1000 could not be fetched.",
path: ["hero", "heroFriends", 0, "homeWorld"]
},
]),

Similar for other places here - these are not GraphQLError instances and we should probably not test for that.

Copy link
Member

@phryneas phryneas left a comment

Choose a reason for hiding this comment

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

A few suggestions, let's discuss this later :)

Comment on lines +1364 to +1368
// TODO: Need to rewrite this to actually simulate a protocol error, not
// just a plain error. The underlying setup does not use the PROTOCOL_ERRORS_SYMBOL.
Copy link
Member

Choose a reason for hiding this comment

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

Let's do this as part of this PR.

*/
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export type GraphQLErrors = ReadonlyArray<GraphQLError>;

export type NetworkError = Error | ServerParseError | ServerError | null;
Copy link
Member

Choose a reason for hiding this comment

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

I'd go for an ErrorLike since the Error interface and Error class of TS overlap, which feels wonky.

@@ -0,0 +1,8 @@
export class UnknownError extends Error {
constructor(errorType: unknown) {
super("An error of unknown type occurred", { cause: errorType });
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
super("An error of unknown type occurred", { cause: errorType });
super("An error of unexpected shape occurred", { cause: errorType });

Copy link
Member Author

@jerelmiller jerelmiller Mar 11, 2025

Choose a reason for hiding this comment

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

I renamed this to UnconventionalError which I think better captures this situation and updated the error message to your suggestion :) ff284d1

response,
getResult(),
`Response not successful: Received status code ${response.status}`
throw new ServerError(
Copy link
Member

Choose a reason for hiding this comment

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

I kinda want to use invariant here too, so our error message extraction could kick in. Maybe we can have an overload of invariant that allows to change the error class in some way?

Copy link
Member Author

Choose a reason for hiding this comment

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

My only rebuttal against that is that I see invariant as more of a validation error against user input (options, values, etc.) where this is a non-successful response.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we'll just have an additional check(condition, [constructor, ...additionalConstructorArgs], [errorMessage, ...errorMessageReplacements]) function?
It's not a problem to hook that up, it's just a shame we currently don't use the message extraction mechanism for those messages.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's hit this up in a separate PR so we can look at this throughout the client and find other areas where we might be able to optimize.

@jerelmiller jerelmiller force-pushed the jerel/consolidate-error-properties branch from d77e202 to ad8137f Compare March 10, 2025 20:35
@jerelmiller jerelmiller force-pushed the jerel/separate-error-objects branch from 002c5d0 to c3b0070 Compare March 12, 2025 18:03
@jerelmiller jerelmiller force-pushed the jerel/separate-error-objects branch from c3b0070 to 6264e51 Compare March 12, 2025 18:07
@jerelmiller jerelmiller merged commit 8cf5077 into release-4.0 Mar 12, 2025
47 checks passed
@jerelmiller jerelmiller deleted the jerel/separate-error-objects branch March 12, 2025 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants