From 41022ae7ecbdec066a8a44ede201c7411e5913f1 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 7 Mar 2024 12:31:22 +0800 Subject: [PATCH] add error handler --- .github/{ => workflows}/ci.yml | 0 .gitignore | 1 + package.json | 4 +- src/client.ts | 81 ++++++++++++++-------------------- src/errors.ts | 55 +++++++++++++++++++++++ src/index.ts | 21 +++++---- 6 files changed, 103 insertions(+), 59 deletions(-) rename .github/{ => workflows}/ci.yml (100%) create mode 100644 src/errors.ts diff --git a/.github/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/ci.yml rename to .github/workflows/ci.yml diff --git a/.gitignore b/.gitignore index 3c539bf..606f142 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ yarn.lock dist .vscode/ *.log +.DS_Store diff --git a/package.json b/package.json index e9d817f..504a3ac 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,11 @@ }, "homepage": "https://github.com/omnivore-app/omnivore-api#readme", "dependencies": { - "gql.tada": "^1.2.1", + "gql.tada": "^1.3.1", "urql": "^4.0.6" }, "devDependencies": { - "@0no-co/graphqlsp": "^1.4.1", + "@0no-co/graphqlsp": "^1.4.2", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "eslint": "^8.57.0", diff --git a/src/client.ts b/src/client.ts index 381c507..19c0842 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ import { Client, fetchExchange } from 'urql' +import { buildOmnivoreError } from './errors' import { graphql } from './graphql' export interface ClientOptions { @@ -317,61 +318,50 @@ export class Omnivore { const { data, error } = await this._client .query(SearchQuery, params) .toPromise() - if (error) { - throw new Error(`Search error: ${error.message}`) + const search = data?.search + if (error || !search || search.__typename === 'SearchError') { + const err = buildOmnivoreError(search, error) + console.error('Search error', err) + throw err } - if (!data) { - throw new Error('Search error: No data returned') - } - - if (data.search.__typename === 'SearchError') { - throw new Error(`Search error: ${data.search.errorCodes.join(', ')}`) - } - - return data.search + return search } async updatesSince(since: string): Promise { const { data, error } = await this._client .query(UpdatesSinceQuery, { since }) .toPromise() - if (error) { - throw new Error(`UpdatesSince error: ${error.message}`) - } - - if (!data) { - throw new Error('UpdatesSince error: No data returned') - } - - if (data.updatesSince.__typename === 'UpdatesSinceError') { - throw new Error( - `UpdatesSince error: ${data.updatesSince.errorCodes.join(', ')}`, - ) + const updatesSince = data?.updatesSince + if ( + error || + !updatesSince || + updatesSince.__typename === 'UpdatesSinceError' + ) { + const err = buildOmnivoreError(updatesSince, error) + console.error('UpdatesSince error', err) + throw err } - return data.updatesSince + return updatesSince } async delete(id: string): Promise { const { data, error } = await this._client .mutation(DeleteMutation, { input: { articleID: id, bookmark: false } }) .toPromise() - if (error) { - throw new Error(`Delete error: ${error.message}`) - } - - if (!data) { - throw new Error('Delete error: No data returned') - } - - if (data.setBookmarkArticle.__typename === 'SetBookmarkArticleError') { - throw new Error( - `Delete error: ${data.setBookmarkArticle.errorCodes.join(', ')}`, - ) + const deleteArticle = data?.setBookmarkArticle + if ( + error || + !deleteArticle || + deleteArticle.__typename === 'SetBookmarkArticleError' + ) { + const err = buildOmnivoreError(deleteArticle, error) + console.error('Delete error', err) + throw err } - return { id: data.setBookmarkArticle.bookmarkedArticle.id } + return { id: deleteArticle.bookmarkedArticle.id } } async saveByURL(params: SaveByURLParameters): Promise { @@ -384,18 +374,13 @@ export class Omnivore { }, }) .toPromise() - if (error) { - throw new Error(`SaveByURL error: ${error.message}`) - } - - if (!data) { - throw new Error('SaveByURL error: No data returned') - } - - if (data.saveUrl.__typename === 'SaveError') { - throw new Error(`SaveByURL error: ${data.saveUrl.errorCodes.join(', ')}`) + const saveUrl = data?.saveUrl + if (error || !saveUrl || saveUrl.__typename === 'SaveError') { + const err = buildOmnivoreError(saveUrl, error) + console.error('SaveByURL error', err) + throw err } - return { id: data.saveUrl.clientRequestId } + return { id: saveUrl.clientRequestId } } } diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..95da7bf --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,55 @@ +import { CombinedError } from 'urql' + +export enum OmnivoreErrorCode { + GraphQLError = 'GRAPHQL_ERROR', + NetworkError = 'NETWORK_ERROR', + UnknownError = 'UNKNOWN_ERROR', +} + +abstract class OmnivoreErrorBase extends Error { + abstract code: Code +} + +class GraphQLError extends OmnivoreErrorBase { + code = OmnivoreErrorCode.GraphQLError + constructor(public messages?: string[]) { + super(messages?.join(', ')) + } +} + +class NetworkError extends OmnivoreErrorBase { + code = OmnivoreErrorCode.NetworkError + constructor(public message: string) { + super(message) + } +} + +class UnknownError extends OmnivoreErrorBase { + code = OmnivoreErrorCode.UnknownError + constructor(public message: string) { + super(message) + } +} + +export type OmnivoreError = GraphQLError | NetworkError | UnknownError + +export const buildOmnivoreError = ( + data?: { __typename: string; errorCodes?: string[] }, + error?: CombinedError, +): OmnivoreError => { + if (error) { + if (error.graphQLErrors.length > 0) { + return new GraphQLError(error.graphQLErrors.map((e) => e.message)) + } + + if (error.networkError) { + return new NetworkError(error.networkError.message) + } + } + + if (!data) { + return new UnknownError('No data returned') + } + + return new GraphQLError(data.errorCodes) +} diff --git a/src/index.ts b/src/index.ts index 66f215a..053ea5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,18 @@ export { - Omnivore, - SearchParameters, - SearchResponse, - UpdatesSinceResponse, - SaveByURLParameters, - Node, - PageInfo, - PageType, - SaveByURLResponse, + ClientOptions, DeleteResponse, Highlight, HighlightType, Label, + Node, + Omnivore, + PageInfo, + PageType, + SaveByURLParameters, + SaveByURLResponse, + SearchParameters, + SearchResponse, + UpdatesSinceResponse, } from './client' + +export { OmnivoreError, OmnivoreErrorCode } from './errors'