From b164ecebda4bf6ddba04b2b1d42940416c3a81ec Mon Sep 17 00:00:00 2001 From: Ophelia Goldstein <159258143+opheliagoldstein@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:31:46 +0300 Subject: [PATCH] refactor: move errors related code into separate file (#4554) * refactor: move errors related code into separate file * jsdoc --------- Co-authored-by: Alexandr --- src/cards/stats.js | 3 +- src/common/error.js | 69 +++++++++++++++++++++++++++++++++++ src/common/index.js | 2 - src/common/retryer.js | 11 ++++-- src/common/utils.js | 59 +----------------------------- src/fetchers/gist.js | 3 +- src/fetchers/repo.js | 4 +- src/fetchers/stats.js | 9 +---- src/fetchers/top-languages.js | 9 +---- src/fetchers/wakatime.js | 6 ++- tests/renderStatsCard.test.js | 3 +- 11 files changed, 95 insertions(+), 83 deletions(-) create mode 100644 src/common/error.js diff --git a/src/cards/stats.js b/src/cards/stats.js index 8920c52b..6e5bdd51 100644 --- a/src/cards/stats.js +++ b/src/cards/stats.js @@ -1,9 +1,10 @@ // @ts-check + import { Card } from "../common/Card.js"; +import { CustomError } from "../common/error.js"; import { I18n } from "../common/I18n.js"; import { icons, rankIcon } from "../common/icons.js"; import { - CustomError, clampValue, flexLayout, getCardColors, diff --git a/src/common/error.js b/src/common/error.js new file mode 100644 index 00000000..c76a198d --- /dev/null +++ b/src/common/error.js @@ -0,0 +1,69 @@ +/** + * @type {string} A general message to ask user to try again later. + */ +const TRY_AGAIN_LATER = "Please try again later"; + +/** + * @type {Object} A map of error types to secondary error messages. + */ +const SECONDARY_ERROR_MESSAGES = { + MAX_RETRY: + "You can deploy own instance or wait until public will be no longer limited", + NO_TOKENS: + "Please add an env variable called PAT_1 with your GitHub API token in vercel", + USER_NOT_FOUND: "Make sure the provided username is not an organization", + GRAPHQL_ERROR: TRY_AGAIN_LATER, + GITHUB_REST_API_ERROR: TRY_AGAIN_LATER, + WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile", +}; + +/** + * Custom error class to handle custom GRS errors. + */ +class CustomError extends Error { + /** + * Custom error constructor. + * + * @param {string} message Error message. + * @param {string} type Error type. + */ + constructor(message, type) { + super(message); + this.type = type; + this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type; + } + + static MAX_RETRY = "MAX_RETRY"; + static NO_TOKENS = "NO_TOKENS"; + static USER_NOT_FOUND = "USER_NOT_FOUND"; + static GRAPHQL_ERROR = "GRAPHQL_ERROR"; + static GITHUB_REST_API_ERROR = "GITHUB_REST_API_ERROR"; + static WAKATIME_ERROR = "WAKATIME_ERROR"; +} + +/** + * Missing query parameter class. + */ +class MissingParamError extends Error { + /** + * Missing query parameter error constructor. + * + * @param {string[]} missedParams An array of missing parameters names. + * @param {string=} secondaryMessage Optional secondary message to display. + */ + constructor(missedParams, secondaryMessage) { + const msg = `Missing params ${missedParams + .map((p) => `"${p}"`) + .join(", ")} make sure you pass the parameters in URL`; + super(msg); + this.missedParams = missedParams; + this.secondaryMessage = secondaryMessage; + } +} + +export { + CustomError, + MissingParamError, + SECONDARY_ERROR_MESSAGES, + TRY_AGAIN_LATER, +}; diff --git a/src/common/index.js b/src/common/index.js index 39bbd6d6..aa5ec082 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -22,8 +22,6 @@ export { getCardColors, wrapTextMultiline, logger, - CustomError, - MissingParamError, measureText, lowercaseTrim, chunkArray, diff --git a/src/common/retryer.js b/src/common/retryer.js index 090865ed..c65d6ede 100644 --- a/src/common/retryer.js +++ b/src/common/retryer.js @@ -1,4 +1,7 @@ -import { CustomError, logger } from "./utils.js"; +// @ts-check + +import { CustomError } from "./error.js"; +import { logger } from "./utils.js"; // Script variables. @@ -10,7 +13,7 @@ const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs; /** * @typedef {import("axios").AxiosResponse} AxiosResponse Axios response. - * @typedef {(variables: object, token: string) => Promise} FetcherFunction Fetcher function. + * @typedef {(variables: object, token: string, retriesForTests?: number) => Promise} FetcherFunction Fetcher function. */ /** @@ -19,7 +22,7 @@ const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs; * @param {FetcherFunction} fetcher The fetcher function. * @param {object} variables Object with arguments to pass to the fetcher function. * @param {number} retries How many times to retry. - * @returns {Promise} The response from the fetcher function. + * @returns {Promise} The response from the fetcher function. */ const retryer = async (fetcher, variables, retries = 0) => { if (!RETRIES) { @@ -37,7 +40,9 @@ const retryer = async (fetcher, variables, retries = 0) => { // try to fetch with the first token since RETRIES is 0 index i'm adding +1 let response = await fetcher( variables, + // @ts-ignore process.env[`PAT_${retries + 1}`], + // used in tests for faking rate limit retries, ); diff --git a/src/common/utils.js b/src/common/utils.js index 8b2fe01d..c9ceb24a 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -1,43 +1,10 @@ // @ts-check + import axios from "axios"; import toEmoji from "emoji-name-map"; import wrap from "word-wrap"; import { themes } from "../../themes/index.js"; - -const TRY_AGAIN_LATER = "Please try again later"; - -const SECONDARY_ERROR_MESSAGES = { - MAX_RETRY: - "You can deploy own instance or wait until public will be no longer limited", - NO_TOKENS: - "Please add an env variable called PAT_1 with your GitHub API token in vercel", - USER_NOT_FOUND: "Make sure the provided username is not an organization", - GRAPHQL_ERROR: TRY_AGAIN_LATER, - GITHUB_REST_API_ERROR: TRY_AGAIN_LATER, - WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile", -}; - -/** - * Custom error class to handle custom GRS errors. - */ -class CustomError extends Error { - /** - * @param {string} message Error message. - * @param {string} type Error type. - */ - constructor(message, type) { - super(message); - this.type = type; - this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type; - } - - static MAX_RETRY = "MAX_RETRY"; - static NO_TOKENS = "NO_TOKENS"; - static USER_NOT_FOUND = "USER_NOT_FOUND"; - static GRAPHQL_ERROR = "GRAPHQL_ERROR"; - static GITHUB_REST_API_ERROR = "GITHUB_REST_API_ERROR"; - static WAKATIME_ERROR = "WAKATIME_ERROR"; -} +import { SECONDARY_ERROR_MESSAGES, TRY_AGAIN_LATER } from "./error.js"; /** * Auto layout utility, allows us to layout things vertically or horizontally with @@ -452,26 +419,6 @@ const noop = () => {}; const logger = process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console; -/** - * Missing query parameter class. - */ -class MissingParamError extends Error { - /** - * Missing query parameter error constructor. - * - * @param {string[]} missedParams An array of missing parameters names. - * @param {string=} secondaryMessage Optional secondary message to display. - */ - constructor(missedParams, secondaryMessage) { - const msg = `Missing params ${missedParams - .map((p) => `"${p}"`) - .join(", ")} make sure you pass the parameters in URL`; - super(msg); - this.missedParams = missedParams; - this.secondaryMessage = secondaryMessage; - } -} - /** * Retrieve text length. * @@ -620,8 +567,6 @@ export { getCardColors, wrapTextMultiline, logger, - CustomError, - MissingParamError, measureText, lowercaseTrim, chunkArray, diff --git a/src/fetchers/gist.js b/src/fetchers/gist.js index cd4006b2..ce96c2b1 100644 --- a/src/fetchers/gist.js +++ b/src/fetchers/gist.js @@ -1,7 +1,8 @@ // @ts-check -import { request, MissingParamError } from "../common/utils.js"; +import { request } from "../common/utils.js"; import { retryer } from "../common/retryer.js"; +import { MissingParamError } from "../common/error.js"; /** * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. diff --git a/src/fetchers/repo.js b/src/fetchers/repo.js index 6438f889..3cad9c00 100644 --- a/src/fetchers/repo.js +++ b/src/fetchers/repo.js @@ -1,6 +1,8 @@ // @ts-check + +import { MissingParamError } from "../common/error.js"; import { retryer } from "../common/retryer.js"; -import { MissingParamError, request } from "../common/utils.js"; +import { request } from "../common/utils.js"; /** * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. diff --git a/src/fetchers/stats.js b/src/fetchers/stats.js index d4ead294..f22dfaf6 100644 --- a/src/fetchers/stats.js +++ b/src/fetchers/stats.js @@ -5,14 +5,9 @@ import * as dotenv from "dotenv"; import githubUsernameRegex from "github-username-regex"; import { calculateRank } from "../calculateRank.js"; import { retryer } from "../common/retryer.js"; -import { - CustomError, - logger, - MissingParamError, - request, - wrapTextMultiline, -} from "../common/utils.js"; +import { logger, request, wrapTextMultiline } from "../common/utils.js"; import { excludeRepositories } from "../common/envs.js"; +import { CustomError, MissingParamError } from "../common/error.js"; dotenv.config(); diff --git a/src/fetchers/top-languages.js b/src/fetchers/top-languages.js index 2e0c1e21..9542678e 100644 --- a/src/fetchers/top-languages.js +++ b/src/fetchers/top-languages.js @@ -1,14 +1,9 @@ // @ts-check import { retryer } from "../common/retryer.js"; -import { - CustomError, - logger, - MissingParamError, - request, - wrapTextMultiline, -} from "../common/utils.js"; +import { logger, request, wrapTextMultiline } from "../common/utils.js"; import { excludeRepositories } from "../common/envs.js"; +import { CustomError, MissingParamError } from "../common/error.js"; /** * @typedef {import("axios").AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. diff --git a/src/fetchers/wakatime.js b/src/fetchers/wakatime.js index f69d6ae4..a081dbd2 100644 --- a/src/fetchers/wakatime.js +++ b/src/fetchers/wakatime.js @@ -1,11 +1,13 @@ +// @ts-check + import axios from "axios"; -import { CustomError, MissingParamError } from "../common/utils.js"; +import { CustomError, MissingParamError } from "../common/error.js"; /** * WakaTime data fetcher. * * @param {{username: string, api_domain: string }} props Fetcher props. - * @returns {Promise} WakaTime data response. + * @returns {Promise} WakaTime data response. */ const fetchWakatimeStats = async ({ username, api_domain }) => { if (!username) { diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 40010944..de123ffe 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -7,8 +7,7 @@ import { import "@testing-library/jest-dom"; import { cssToObject } from "@uppercod/css-to-object"; import { renderStatsCard } from "../src/cards/stats.js"; -import { CustomError } from "../src/common/utils.js"; - +import { CustomError } from "../src/common/error.js"; import { themes } from "../themes/index.js"; const stats = {