Skip to content

Commit

Permalink
enhance: Better handling of errors (#2168)
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Sep 8, 2022
1 parent 9c25e36 commit 0d75952
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 22 deletions.
6 changes: 2 additions & 4 deletions examples/github-app/src/pages/IssueDetail/CommentForm.tsx
@@ -1,9 +1,7 @@
import { Card, Avatar, Form, Button, Input, Space } from 'antd';
import { Link } from '@anansi/router';
import { Form, Button, Input, Space } from 'antd';
import { useLoading } from '@rest-hooks/hooks';
import { memo, useCallback } from 'react';
import { memo } from 'react';

const { Meta } = Card;
const { TextArea } = Input;

function CommentForm({
Expand Down
Expand Up @@ -9,7 +9,6 @@ import rehypeHighlight from 'rehype-highlight';
import { UserResource } from 'resources/User';
import { css } from '@linaria/core';
import { EllipsisOutlined } from '@ant-design/icons';
import { styled } from '@linaria/react';
import { CommentResource, Comment } from 'resources/Comment';
import FlexRow from 'components/FlexRow';

Expand Down
@@ -1,6 +1,6 @@
import UserResource from 'resources/User';
import { useCache, useController } from 'rest-hooks';
import { Card, Avatar, Input } from 'antd';
import { Card, Avatar } from 'antd';
import { Link } from '@anansi/router';
import { memo, useCallback } from 'react';
import { CommentResource } from 'resources/Comment';
Expand Down
13 changes: 6 additions & 7 deletions examples/github-app/src/pages/IssueDetail/CreateReaction.tsx
@@ -1,5 +1,4 @@
import { useSuspense, useController } from 'rest-hooks';
import React from 'react';
import { useController, useCache } from 'rest-hooks';
import { UserResource } from 'resources/User';
import { v4 as uuid } from 'uuid';
import { Reaction, ReactionResource, contentToIcon } from 'resources/Reaction';
Expand All @@ -18,13 +17,13 @@ export function CreateReaction({
issue: Issue;
}) {
const { fetch } = useController();
const currentUser = useSuspense(UserResource.current);
const userReaction: Reaction | undefined = reactions.find(
(reaction) => reaction.user.login === currentUser.login,
);
const currentUser = useCache(UserResource.current);
const userReaction: Reaction | undefined =
currentUser &&
reactions.find((reaction) => reaction.user.login === currentUser.login);

const handleClick = () => {
if (userReaction) return;
if (userReaction || !currentUser) return;
fetch(
ReactionResource.create,
{
Expand Down
2 changes: 1 addition & 1 deletion examples/github-app/src/resources/Reaction.tsx
Expand Up @@ -70,7 +70,7 @@ export const ReactionResource = {
...rest,
}),
}),
getOptimisticResponse: (snap, params, body) => body,
getOptimisticResponse: (snap, params, body) => body as any,
}),
delete: base.delete.extend({
getOptimisticResponse: (snap, params) => params,
Expand Down
2 changes: 0 additions & 2 deletions examples/github-app/src/resources/Repository.tsx
@@ -1,5 +1,3 @@
import { RestGenerics } from '@rest-hooks/experimental';

import { GithubEntity, createGithubResource, GithubGqlEndpoint } from './Base';

export class Repository extends GithubEntity {
Expand Down
23 changes: 18 additions & 5 deletions packages/graphql/src/GQLEndpoint.ts
Expand Up @@ -28,11 +28,20 @@ export default class GQLEndpoint<
signal: this.signal ?? null,
headers: this.getHeaders({ 'Content-Type': 'application/json' }),
}),
).then(async res => {
const { data, errors } = await res.json();
if (errors) throw new GQLNetworkError(errors);
return data;
});
)
.then(async res => {
const json = await res.json();
if (json.errors) throw new GQLNetworkError(json.errors);
if (!res.ok) throw new GQLNetworkError([json]);
return json.data;
})
.catch(error => {
// ensure CORS, network down, and parse errors are still caught by NetworkErrorBoundary
if (error instanceof TypeError) {
(error as any).status = 400;
}
throw error;
});
}, args);
return this;
}
Expand All @@ -54,6 +63,10 @@ export default class GQLEndpoint<
return headers;
}

errorPolicy(error: any) {
return error.status >= 500 ? ('soft' as const) : undefined;
}

query<
Q extends string | ((variables: any) => string),
S extends Schema | undefined,
Expand Down
42 changes: 41 additions & 1 deletion packages/graphql/src/__tests__/gql.ts
Expand Up @@ -61,7 +61,7 @@ describe('GQLEndpoint', () => {
},
{ 'content-type': 'application/json' },
];
else
else if (body.variables.name === 'Fong2')
return [
400,
{
Expand All @@ -80,6 +80,15 @@ describe('GQLEndpoint', () => {
},
{ 'content-type': 'application/json' },
];
else
return [
401,
{
message: 'This endpoint requires you to be authenticated.',
documentation_url:
'https://docs.github.com/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql',
},
];
});
});
afterAll(() => {
Expand Down Expand Up @@ -161,4 +170,35 @@ describe('GQLEndpoint', () => {
`[NetworkError: Generic failure]`,
);
});

it('should throw if network response is not OK', async () => {
await expect(userDetail({ name: 'Fong3' })).rejects.toMatchInlineSnapshot(
`[NetworkError: This endpoint requires you to be authenticated.]`,
);
});

it('should throw if network is down', async () => {
const oldError = console.error;
console.error = () => {};

nock('https://nosy-baritone.glitch.me')
.defaultReplyHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
})
.post('/')
.replyWithError(new TypeError('Network Down'));

let error: any;
try {
await userDetail({ name: 'Fong4' });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.status).toBe(400);

// eslint-disable-next-line require-atomic-updates
console.error = oldError;
});
});

0 comments on commit 0d75952

Please sign in to comment.