1import { GraphQLError } from '@0no-co/graphql.web';
2import type { ErrorLike } from '../types';
3
4const generateErrorMessage = (
5 networkErr?: Error,
6 graphQlErrs?: GraphQLError[]
7) => {
8 let error = '';
9 if (networkErr) return `[Network] ${networkErr.message}`;
10 if (graphQlErrs) {
11 for (let i = 0, l = graphQlErrs.length; i < l; i++) {
12 if (error) error += '\n';
13 error += `[GraphQL] ${graphQlErrs[i].message}`;
14 }
15 }
16 return error;
17};
18
19const rehydrateGraphQlError = (error: any): GraphQLError => {
20 if (
21 error &&
22 typeof error.message === 'string' &&
23 (error.extensions || error.name === 'GraphQLError')
24 ) {
25 return error;
26 } else if (typeof error === 'object' && typeof error.message === 'string') {
27 return new GraphQLError(
28 error.message,
29 error.nodes,
30 error.source,
31 error.positions,
32 error.path,
33 error,
34 error.extensions || {}
35 );
36 } else {
37 return new GraphQLError(error as any);
38 }
39};
40
41/** An abstracted `Error` that provides either a `networkError` or `graphQLErrors`.
42 *
43 * @remarks
44 * During a GraphQL request, either the request can fail entirely, causing a network error,
45 * or the GraphQL execution or fields can fail, which will cause an {@link ExecutionResult}
46 * to contain an array of GraphQL errors.
47 *
48 * The `CombinedError` abstracts and normalizes both failure cases. When {@link OperationResult.error}
49 * is set to this error, the `CombinedError` abstracts all errors, making it easier to handle only
50 * a subset of error cases.
51 *
52 * @see {@link https://urql.dev/goto/docs/basics/errors} for more information on handling
53 * GraphQL errors and the `CombinedError`.
54 */
55export class CombinedError extends Error {
56 public name: string;
57 public message: string;
58
59 /** A list of GraphQL errors rehydrated from a {@link ExecutionResult}.
60 *
61 * @remarks
62 * If an {@link ExecutionResult} received from the API contains a list of errors,
63 * the `CombinedError` will rehydrate them, normalize them to
64 * {@link GraphQLError | GraphQLErrors} and list them here.
65 * An empty list indicates that no GraphQL error has been sent by the API.
66 */
67 public graphQLErrors: GraphQLError[];
68
69 /** Set to an error, if a GraphQL request has failed outright.
70 *
71 * @remarks
72 * A GraphQL over HTTP request may fail and not reach the API. Any error that
73 * prevents a GraphQl request outright, will be considered a “network error” and
74 * set here.
75 */
76 public networkError?: Error;
77
78 /** Set to the {@link Response} object a fetch exchange received.
79 *
80 * @remarks
81 * If a built-in fetch {@link Exchange} is used in `urql`, this may
82 * be set to the {@link Response} object of the Fetch API response.
83 * However, since `urql` doesn’t assume that all users will use HTTP
84 * as the only or exclusive transport for GraphQL this property is
85 * neither typed nor guaranteed and may be re-used for other purposes
86 * by non-fetch exchanges.
87 *
88 * Hint: It can be useful to use `response.status` here, however, if
89 * you plan on relying on this being a {@link Response} in your app,
90 * which it is by default, then make sure you add some extra checks
91 * before blindly assuming so!
92 */
93 public response?: any;
94
95 constructor(input: {
96 networkError?: Error;
97 graphQLErrors?: Array<string | ErrorLike>;
98 response?: any;
99 }) {
100 const normalizedGraphQLErrors = (input.graphQLErrors || []).map(
101 rehydrateGraphQlError
102 );
103 const message = generateErrorMessage(
104 input.networkError,
105 normalizedGraphQLErrors
106 );
107
108 super(message);
109
110 this.name = 'CombinedError';
111 this.message = message;
112 this.graphQLErrors = normalizedGraphQLErrors;
113 this.networkError = input.networkError;
114 this.response = input.response;
115 }
116
117 toString(): string {
118 return this.message;
119 }
120}