Skip to content

Commit

Permalink
refactor: switch to @sapphire/result for error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
favna committed Aug 21, 2022
1 parent 3977787 commit 2a1f845
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 143 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"dependencies": {
"@sapphire/fetch": "^2.4.1",
"@sapphire/result": "^2.4.0",
"@types/country-data": "^0.0.2",
"country-data": "^0.0.31",
"fast-xml-parser": "^4.0.9"
Expand Down
28 changes: 13 additions & 15 deletions src/lib/getGames/getGamesAmerica.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { stringify } from 'querystring';
import { US_ALGOLIA_HEADERS, US_GET_GAMES_URL } from '../utils/constants';
import type { AlgoliaResponse, GameUS } from '../utils/interfaces';
Expand Down Expand Up @@ -71,22 +72,19 @@ export const getGamesAmerica = async (): Promise<GameUS[]> => {
headers: US_ALGOLIA_HEADERS
};

try {
const gamesResponse = await fetch<AlgoliaResponse>(US_GET_GAMES_URL, requestOptions, FetchResultTypes.JSON);

let allGames: any[] | PromiseLike<GameUS[]> = [];
for (const results of gamesResponse.results) {
allGames = allGames.concat(results.hits);
}

allGames = arrayRemoveDuplicates(allGames, 'slug');
return allGames;
} catch (err) {
if (/(?:US_games_request_failed)/i.test((err as Error).message)) {
throw new EshopError('Fetching of US Games failed');
}
throw err;
const gamesResponse = await Result.fromAsync(fetch<AlgoliaResponse>(US_GET_GAMES_URL, requestOptions, FetchResultTypes.JSON));

if (gamesResponse.isErr()) {
throw new EshopError('Fetching of US Games failed');
}

let allGames: any[] | PromiseLike<GameUS[]> = [];
for (const results of gamesResponse.unwrap().results) {
allGames = allGames.concat(results.hits);
}

allGames = arrayRemoveDuplicates(allGames, 'slug');
return allGames;
};

interface Request {
Expand Down
28 changes: 13 additions & 15 deletions src/lib/getGames/getGamesBrazil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { stringify } from 'querystring';
import { BR_ALGOLIA_HEADERS, BR_GET_GAMES_URL } from '../utils/constants';
import type { AlgoliaResponse, GameUS } from '../utils/interfaces';
Expand Down Expand Up @@ -71,22 +72,19 @@ export const getGamesBrazil = async (): Promise<GameUS[]> => {
headers: BR_ALGOLIA_HEADERS
};

try {
const gamesResponse = await fetch<AlgoliaResponse>(BR_GET_GAMES_URL, requestOptions, FetchResultTypes.JSON);

let allGames: any[] | PromiseLike<GameUS[]> = [];
for (const results of gamesResponse.results) {
allGames = allGames.concat(results.hits);
}

allGames = arrayRemoveDuplicates(allGames, 'slug');
return allGames;
} catch (err) {
if (/(?:BR_games_request_failed)/i.test((err as Error).message)) {
throw new EshopError('Fetching of BR Games failed');
}
throw err;
const gamesResponse = await Result.fromAsync(fetch<AlgoliaResponse>(BR_GET_GAMES_URL, requestOptions, FetchResultTypes.JSON));

if (gamesResponse.isErr()) {
throw new EshopError('Fetching of BR Games failed');
}

let allGames: any[] | PromiseLike<GameUS[]> = [];
for (const results of gamesResponse.unwrap().results) {
allGames = allGames.concat(results.hits);
}

allGames = arrayRemoveDuplicates(allGames, 'slug');
return allGames;
};

interface Request {
Expand Down
18 changes: 9 additions & 9 deletions src/lib/getGames/getGamesEurope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { stringify } from 'querystring';
import { EU_DEFAULT_LOCALE, EU_GAME_LIST_LIMIT, EU_GET_GAMES_OPTIONS, EU_GET_GAMES_URL } from '../utils/constants';
import type { EURequestOptions, GameEU } from '../utils/interfaces';
Expand All @@ -17,20 +18,19 @@ export const getGamesEurope = async (options: EURequestOptions = { limit: EU_GAM
if (!options.limit) options.limit = EU_GAME_LIST_LIMIT;
if (!options.locale) options.locale = EU_DEFAULT_LOCALE;

try {
const gamesData = await fetch<{ response: { docs: GameEU[] } }>(
const gamesData = await Result.fromAsync(
fetch<{ response: { docs: GameEU[] } }>(
`${EU_GET_GAMES_URL.replace('{locale}', options.locale)}?${stringify({
rows: options.limit,
...EU_GET_GAMES_OPTIONS
})}`,
FetchResultTypes.JSON
);
)
);

return gamesData.response.docs;
} catch (err) {
if (/(?:EU_games_request_failed)/i.test((err as Error).message)) {
throw new EshopError('Fetching of EU Games failed');
}
throw err;
if (gamesData.isErr()) {
throw new EshopError('Fetching of EU Games failed');
}

return gamesData.unwrap().response.docs;
};
24 changes: 14 additions & 10 deletions src/lib/getGames/getGamesJapan.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { XMLParser } from 'fast-xml-parser';
import { JP_GET_GAMES_URL } from '../utils/constants';
import type { GameJP } from '../utils/interfaces';
Expand All @@ -10,18 +11,21 @@ import { EshopError } from '../utils/utils';
* @returns Promise containing all the games
*/
export const getGamesJapan = async (): Promise<GameJP[]> => {
try {
const parser = new XMLParser();
const response = await Result.fromAsync(fetch(JP_GET_GAMES_URL, FetchResultTypes.Text));

const gamesJP = parser.parse(await fetch(JP_GET_GAMES_URL, FetchResultTypes.Text));
if (response.isErr()) {
throw new EshopError('Fetching of JP Games failed');
}

const parser = new XMLParser();

const allGamesJP: GameJP[] = gamesJP.TitleInfoList.TitleInfo;
const gamesJP = Result.from(parser.parse(response.unwrap()));

return allGamesJP;
} catch (err) {
if (/(?:JP_games_request_failed)/i.test((err as Error).message)) {
throw new EshopError('Fetching of JP Games failed');
}
throw err;
if (gamesJP.isErr()) {
throw new EshopError('Parsing of JP Games failed');
}

const allGamesJP: GameJP[] = gamesJP.unwrap().TitleInfoList.TitleInfo;

return allGamesJP;
};
71 changes: 41 additions & 30 deletions src/lib/getGames/getQueriedGamesAmerica.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '../utils/constants';
import type { QueriedGameResult, QueriedGamesAmericaOptions, QueriedGameUS } from '../utils/interfaces';
import { EshopError } from '../utils/utils';
import { Result } from '@sapphire/result';

/**
* Fetches a subset of games from the American e-shops as based on a given query
Expand All @@ -21,43 +22,53 @@ export const getQueriedGamesAmerica = async (
query: string,
{ hitsPerPage = 200, page = 0 }: QueriedGamesAmericaOptions = { hitsPerPage: 200, page: 0 }
): Promise<QueriedGameUS[]> => {
const { hits: newHits } = await fetch<QueriedGameResult>(
QUERIED_US_GET_GAMES_URL_NEW,
{
method: 'POST',
headers: {
...US_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_US_ALGOLIA_KEY_NEW
const newGamesResult = await Result.fromAsync(
fetch<QueriedGameResult>(
QUERIED_US_GET_GAMES_URL_NEW,
{
method: 'POST',
headers: {
...US_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_US_ALGOLIA_KEY_NEW
},
body: JSON.stringify({
hitsPerPage,
page,
query
})
},
body: JSON.stringify({
hitsPerPage,
page,
query
})
},
FetchResultTypes.JSON
FetchResultTypes.JSON
)
);

if (!newHits.length) throw new EshopError(`No game results for the query "${query}"`);
if (newGamesResult.isErr() || newGamesResult.isOkAnd((v) => v.hits.length === 0)) {
throw new EshopError(`No game results for the query "${query}"`);
}

const { hits: oldHits } = await fetch<QueriedGameResult>(
QUERIED_US_GET_GAMES_URL_OLD,
{
method: 'POST',
headers: {
...US_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_US_ALGOLIA_KEY_OLD
const oldGamesResult = await Result.fromAsync(
fetch<QueriedGameResult>(
QUERIED_US_GET_GAMES_URL_OLD,
{
method: 'POST',
headers: {
...US_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_US_ALGOLIA_KEY_OLD
},
body: JSON.stringify({
hitsPerPage,
page,
query
})
},
body: JSON.stringify({
hitsPerPage,
page,
query
})
},
FetchResultTypes.JSON
FetchResultTypes.JSON
)
);

return enrichNewHitsWithOldHitData(newHits, oldHits);
if (oldGamesResult.isErr() || oldGamesResult.isOkAnd((v) => v.hits.length === 0)) {
throw new EshopError(`No game results for the query "${query}"`);
}

return enrichNewHitsWithOldHitData(newGamesResult.unwrap().hits, oldGamesResult.unwrap().hits);
};

function enrichNewHitsWithOldHitData(oldHits: QueriedGameUS[], newHits: QueriedGameUS[]) {
Expand Down
41 changes: 23 additions & 18 deletions src/lib/getGames/getQueriedGamesBrazil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { stringify } from 'querystring';
import { QUERIED_BR_ALGOLIA_KEY, QUERIED_BR_GET_GAMES_URL, BR_ALGOLIA_HEADERS } from '../utils/constants';
import type { QueriedGameResult, QueriedGamesAmericaOptions, QueriedGameUS } from '../utils/interfaces';
Expand All @@ -16,26 +17,30 @@ export const getQueriedGamesBrazil = async (
query: string,
{ hitsPerPage = 200, page = 0 }: QueriedGamesAmericaOptions = { hitsPerPage: 200, page: 0 }
): Promise<QueriedGameUS[]> => {
const { hits } = await fetch<QueriedGameResult>(
QUERIED_BR_GET_GAMES_URL,
{
method: 'POST',
headers: {
...BR_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_BR_ALGOLIA_KEY
},
body: JSON.stringify({
params: stringify({
hitsPerPage,
page,
query
const gamesResult = await Result.fromAsync(
fetch<QueriedGameResult>(
QUERIED_BR_GET_GAMES_URL,
{
method: 'POST',
headers: {
...BR_ALGOLIA_HEADERS,
'X-Algolia-API-Key': QUERIED_BR_ALGOLIA_KEY
},
body: JSON.stringify({
params: stringify({
hitsPerPage,
page,
query
})
})
})
},
FetchResultTypes.JSON
},
FetchResultTypes.JSON
)
);

if (!hits.length) throw new EshopError(`No game results for the query "${query}"`);
if (gamesResult.isErr() || gamesResult.isOkAnd((v) => v.hits.length === 0)) {
throw new EshopError(`No game results for the query "${query}"`);
}

return hits;
return gamesResult.unwrap().hits;
};
39 changes: 19 additions & 20 deletions src/lib/other/getPrices.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetch, FetchResultTypes } from '@sapphire/fetch';
import { Result } from '@sapphire/result';
import { stringify } from 'querystring';
import { PRICE_GET_OPTIONS, PRICE_GET_URL, PRICE_LIST_LIMIT } from '../utils/constants';
import type { PriceResponse, TitleData } from '../utils/interfaces';
Expand All @@ -14,36 +15,34 @@ import { EshopError } from '../utils/utils';
* @returns A promise containing the pricing information.
*/
export const getPrices = async (country: string, gameIds: string[] | string, offset = 0, prices: TitleData[] = []): Promise<PriceResponse> => {
try {
const filteredIds = gameIds.slice(offset, offset + PRICE_LIST_LIMIT);
const response = await fetch<PriceResponse>(
const filteredIds = gameIds.slice(offset, offset + PRICE_LIST_LIMIT);
const response = await Result.fromAsync(
fetch<PriceResponse>(
`${PRICE_GET_URL}?${stringify({
country,
ids: filteredIds,
limit: PRICE_LIST_LIMIT,
...PRICE_GET_OPTIONS
})}`,
FetchResultTypes.JSON
);
)
);

if (response.prices && response.prices.length + offset < gameIds.length) {
const accumulatedPrices = prices.concat(response.prices);
if (response.isErr()) {
throw new EshopError('Fetching of eShop prices failed');
}

const responseUnwrapped = response.unwrap();

return await getPrices(country, gameIds, offset + PRICE_LIST_LIMIT, accumulatedPrices);
} else if (response.prices) {
response.prices = response.prices.concat(prices);
if (responseUnwrapped.prices && responseUnwrapped.prices.length + offset < gameIds.length) {
const accumulatedPrices = prices.concat(responseUnwrapped.prices);

return response;
}
return getPrices(country, gameIds, offset + PRICE_LIST_LIMIT, accumulatedPrices);
} else if (responseUnwrapped.prices) {
responseUnwrapped.prices = responseUnwrapped.prices.concat(prices);

return response;
} catch (err) {
if (/(?:PRICE_Rate_Limit)/i.test((err as Error).message)) {
throw new EshopError('Looks like you ran into a rate limit while getting price data, please do not spam the Nintendo servers.');
}
if (/(?:PRICE_get_request_failed)/i.test((err as Error).message)) {
throw new EshopError('Fetching of eShop prices failed');
}
throw err;
return responseUnwrapped;
}

return responseUnwrapped;
};
Loading

0 comments on commit 2a1f845

Please sign in to comment.