Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(core): support non-spec errors (#3395)

Changed files
+177 -1
.changeset
packages
core
src
+6
.changeset/olive-schools-prove.md
···
+
---
+
'@urql/core': patch
+
---
+
+
Support non spec-compliant error bodies, i.e. the Shopify API does return `errors` but as an object. Adding
+
a check whether we are really dealing with an Array of errors enables this.
+146
packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap
···
}
`;
+
exports[`on error with non spec-compliant body > handles network errors 1`] = `
+
{
+
"data": undefined,
+
"error": [CombinedError: [Network] Forbidden],
+
"extensions": undefined,
+
"hasNext": false,
+
"operation": {
+
"context": {
+
"fetchOptions": {
+
"method": "POST",
+
},
+
"requestPolicy": "cache-first",
+
"url": "http://localhost:3000/graphql",
+
},
+
"key": 2,
+
"kind": "query",
+
"query": {
+
"__key": -2395444236,
+
"definitions": [
+
{
+
"directives": [],
+
"kind": "OperationDefinition",
+
"name": {
+
"kind": "Name",
+
"value": "getUser",
+
},
+
"operation": "query",
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"alias": undefined,
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "name",
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "name",
+
},
+
},
+
},
+
],
+
"directives": [],
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "user",
+
},
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"alias": undefined,
+
"arguments": [],
+
"directives": [],
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id",
+
},
+
"selectionSet": undefined,
+
},
+
{
+
"alias": undefined,
+
"arguments": [],
+
"directives": [],
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "firstName",
+
},
+
"selectionSet": undefined,
+
},
+
{
+
"alias": undefined,
+
"arguments": [],
+
"directives": [],
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "lastName",
+
},
+
"selectionSet": undefined,
+
},
+
],
+
},
+
},
+
],
+
},
+
"variableDefinitions": [
+
{
+
"defaultValue": undefined,
+
"directives": [],
+
"kind": "VariableDefinition",
+
"type": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "String",
+
},
+
},
+
"variable": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "name",
+
},
+
},
+
},
+
],
+
},
+
],
+
"kind": "Document",
+
"loc": {
+
"end": 92,
+
"source": {
+
"body": "query getUser($name: String) {
+
user(name: $name) {
+
id
+
firstName
+
lastName
+
}
+
}",
+
"locationOffset": {
+
"column": 1,
+
"line": 1,
+
},
+
"name": "gql",
+
},
+
"start": 0,
+
},
+
},
+
"variables": {
+
"name": "Clara",
+
},
+
},
+
"stale": false,
+
}
+
`;
+
exports[`on success > returns response data 1`] = `
{
"data": {
+21
packages/core/src/internal/fetchSource.test.ts
···
});
});
+
describe('on error with non spec-compliant body', () => {
+
beforeEach(() => {
+
fetch.mockResolvedValue({
+
status: 400,
+
statusText: 'Forbidden',
+
headers: { get: () => 'application/json' },
+
text: vi.fn().mockResolvedValue('{"errors":{"detail":"Bad Request"}}'),
+
});
+
});
+
+
it('handles network errors', async () => {
+
const data = await pipe(
+
makeFetchSource(queryOperation, 'https://test.com/graphql', {}),
+
toPromise
+
);
+
+
expect(data).toMatchSnapshot();
+
expect(data).toHaveProperty('error.networkError.message', 'Forbidden');
+
});
+
});
+
describe('on teardown', () => {
const fail = () => {
expect(true).toEqual(false);
+4 -1
packages/core/src/utils/result.ts
···
result: ExecutionResult,
response?: any
): OperationResult => {
-
if (!('data' in result) && !('errors' in result)) {
+
if (
+
!('data' in result) &&
+
(!('errors' in result) || !Array.isArray(result.errors))
+
) {
throw new Error('No Content');
}