diff --git a/apps/node/package.json b/apps/node/package.json index 6c45fc1..7529b6f 100644 --- a/apps/node/package.json +++ b/apps/node/package.json @@ -33,6 +33,7 @@ "@repo/indexer-api": "workspace:*", "@repo/persistence-example": "workspace:*", "@repo/template": "workspace:*", + "@repo/error-handling": "workspace:*", "@uma/logger": "^1.3.0", "dotenv": "^16.4.5", "source-map-support": "^0.5.21", diff --git a/apps/node/src/app.ts b/apps/node/src/app.ts index 0e7a55f..fdcf456 100644 --- a/apps/node/src/app.ts +++ b/apps/node/src/app.ts @@ -1,12 +1,12 @@ import "source-map-support/register"; import dotenv from "dotenv"; -import assert from "assert"; import * as Template from "@repo/template"; import * as Indexer from "@repo/indexer"; import * as PersistenceExample from "@repo/persistence-example"; import * as IndexerApi from "@repo/indexer-api"; +import { assert } from "@repo/error-handling"; import { Logger } from "@uma/logger"; dotenv.config(); diff --git a/packages/error-handling/src/errors/IndexerHTTPError.ts b/packages/error-handling/src/errors/IndexerHTTPError.ts index 187d2eb..ee24d09 100644 --- a/packages/error-handling/src/errors/IndexerHTTPError.ts +++ b/packages/error-handling/src/errors/IndexerHTTPError.ts @@ -10,22 +10,11 @@ export { StatusCodes }; */ export abstract class IndexerHTTPError extends IndexerError { constructor( - private readonly httpStatusCode: StatusCodes, + public readonly httpStatusCode: StatusCodes, errorName: string, errorMessage: string, errorData?: Record, ) { super(errorName, errorMessage, errorData); } - - /** - * A function used by `JSON.stringify` to specify which data will be serialized - * @returns A formatted JSON - */ - public toJSON(): Record { - return { - statusCode: this.httpStatusCode, - ...super.toJSON(), - }; - } } diff --git a/packages/indexer-api/package.json b/packages/indexer-api/package.json index 51cfdbf..5b2ea49 100644 --- a/packages/indexer-api/package.json +++ b/packages/indexer-api/package.json @@ -22,6 +22,7 @@ "author": "", "license": "ISC", "dependencies": { + "@repo/error-handling": "workspace:*", "@repo/indexer": "workspace:*", "@repo/webhooks": "workspace:*", "@repo/indexer-database": "workspace:*", diff --git a/packages/indexer-api/src/dtos/deposits.dto.ts b/packages/indexer-api/src/dtos/deposits.dto.ts index 39007c1..d64cf38 100644 --- a/packages/indexer-api/src/dtos/deposits.dto.ts +++ b/packages/indexer-api/src/dtos/deposits.dto.ts @@ -1,9 +1,13 @@ import * as s from "superstruct"; import { entities } from "@repo/indexer-database"; -const stringToInt = s.coerce(s.number(), s.string(), (value) => - parseInt(value), -); +const stringToInt = s.coerce(s.number(), s.string(), (value) => { + // Ensure the value is a valid integer string + if (!/^-?\d+$/.test(value)) { + return value; + } + return parseInt(value, 10); +}); export const DepositsParams = s.object({ depositor: s.optional(s.string()), diff --git a/packages/indexer-api/src/error-handler.ts b/packages/indexer-api/src/error-handler.ts new file mode 100644 index 0000000..92964fc --- /dev/null +++ b/packages/indexer-api/src/error-handler.ts @@ -0,0 +1,45 @@ +import { + isIndexerError, + isIndexerHTTPError, + StatusCodes, +} from "@repo/error-handling"; +import { Request, Response, NextFunction } from "express"; +import { isHttpError } from "./express-app"; +import { StructError } from "superstruct"; + +const DEFAULT_STATUS = StatusCodes.BAD_REQUEST; + +const errorHandler = ( + err: unknown, + req: Request, + res: Response, + _: NextFunction, +): void => { + // At a base level we need to confirm that this isn't a valid + // passthrough - if so ignore + if (isIndexerError(err)) { + // If we have a custom sub-type to specify the error code, use it + // otherwise default to a status 400 + const httpStatus = isIndexerHTTPError(err) + ? err.httpStatusCode + : DEFAULT_STATUS; + res.status(httpStatus).json(err.toJSON()); + } else if (isHttpError(err)) { + res.status(err.status ?? DEFAULT_STATUS).json({ + message: err.message, + error: "NavigationError", + }); + } else if (err instanceof StructError) { + res.status(StatusCodes.BAD_REQUEST).json({ + error: "ValidationError", + message: err.message, + }); + } else if (err instanceof Error) { + res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + error: "UnknownError", + message: err.message, + }); + } +}; + +export default errorHandler; diff --git a/packages/indexer-api/src/express-app.ts b/packages/indexer-api/src/express-app.ts index 21b47b0..b07a511 100644 --- a/packages/indexer-api/src/express-app.ts +++ b/packages/indexer-api/src/express-app.ts @@ -2,6 +2,7 @@ import cors from "cors"; import bodyParser from "body-parser"; import type { Request, Response, NextFunction, Express, Router } from "express"; import express from "express"; +import errorHandler from "./error-handler"; export class HttpError extends Error { status?: number; @@ -31,33 +32,14 @@ export function ExpressApp(routers: RouterConfigs): Express { }); app.use(function (_: Request, __: Response, next: NextFunction) { - const error = new HttpError("Not Found"); + const error = new HttpError("Route does not exist."); error["status"] = 404; next(error); }); - app.use(function ( - err: HttpError | Error, - req: Request, - res: Response, - // this needs to be included even if unused, since 4 param call triggers error handler - _: NextFunction, - ) { - const request = { - method: req.method, - path: req.path, - body: req.body, - }; - let status = 500; - if (isHttpError(err)) { - status = err.status ?? status; - } - res.status(status).json({ - message: err.message, - request, - stack: err.stack, - }); - }); + // Register an error handler as the last part of the + // express pipeline + app.use(errorHandler); return app; } diff --git a/packages/indexer-api/src/main.ts b/packages/indexer-api/src/main.ts index bee044b..475e495 100644 --- a/packages/indexer-api/src/main.ts +++ b/packages/indexer-api/src/main.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import { assert } from "@repo/error-handling"; import { ExpressApp } from "./express-app"; import { createDataSource, DatabaseConfig } from "@repo/indexer-database"; import * as routers from "./routers"; diff --git a/packages/indexer-api/src/services/balances.ts b/packages/indexer-api/src/services/balances.ts index e5dfa73..86ee8cf 100644 --- a/packages/indexer-api/src/services/balances.ts +++ b/packages/indexer-api/src/services/balances.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import { assert } from "@repo/error-handling"; import Redis from "ioredis"; import * as Indexer from "@repo/indexer"; import { diff --git a/packages/indexer-api/src/services/exceptions.ts b/packages/indexer-api/src/services/exceptions.ts index 621c257..1982996 100644 --- a/packages/indexer-api/src/services/exceptions.ts +++ b/packages/indexer-api/src/services/exceptions.ts @@ -1,18 +1,17 @@ -import { HttpError } from "../express-app"; -import { HttpStatus } from "../model/httpStatus"; +import { IndexerHTTPError, StatusCodes } from "@repo/error-handling"; -export class DepositNotFoundException extends HttpError { +export class DepositNotFoundException extends IndexerHTTPError { constructor() { - super("Deposit not found"); - this.name = "DepositNotFoundException"; - this.status = HttpStatus.NOT_FOUND; + super( + StatusCodes.NOT_FOUND, + DepositNotFoundException.name, + "Deposit not found given the provided constraints", + ); } } -export class IndexParamOutOfRangeException extends HttpError { +export class IndexParamOutOfRangeException extends IndexerHTTPError { constructor(message: string) { - super(message); - this.name = "IndexParamOutOfRangeException"; - this.status = HttpStatus.BAD_REQUEST; + super(StatusCodes.BAD_REQUEST, IndexParamOutOfRangeException.name, message); } } diff --git a/packages/indexer/package.json b/packages/indexer/package.json index 40d67e5..5af07f0 100644 --- a/packages/indexer/package.json +++ b/packages/indexer/package.json @@ -35,6 +35,7 @@ "redis": "^4.7.0", "superstruct": "^2.0.3-1", "@repo/webhooks": "workspace:*", + "@repo/error-handling": "workspace:*", "winston": "^3.13.1" }, "exports": { diff --git a/packages/indexer/src/parseEnv.ts b/packages/indexer/src/parseEnv.ts index 46327de..57a8b9b 100644 --- a/packages/indexer/src/parseEnv.ts +++ b/packages/indexer/src/parseEnv.ts @@ -1,7 +1,7 @@ -import assert from "assert"; import * as s from "superstruct"; import { DatabaseConfig } from "@repo/indexer-database"; import { getNoTtlBlockDistance } from "./web3/constants"; +import { assert } from "@repo/error-handling"; export type Config = { redisConfig: RedisConfig; diff --git a/packages/indexer/src/redis/rangeQueryStore.ts b/packages/indexer/src/redis/rangeQueryStore.ts index d93c8db..982a6f7 100644 --- a/packages/indexer/src/redis/rangeQueryStore.ts +++ b/packages/indexer/src/redis/rangeQueryStore.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import { assert } from "@repo/error-handling"; import Redis from "ioredis"; /** diff --git a/packages/indexer/src/services/BundleBuilderService.ts b/packages/indexer/src/services/BundleBuilderService.ts index 384904d..aedf186 100644 --- a/packages/indexer/src/services/BundleBuilderService.ts +++ b/packages/indexer/src/services/BundleBuilderService.ts @@ -1,6 +1,6 @@ import { caching, clients, typechain, utils } from "@across-protocol/sdk"; import { entities } from "@repo/indexer-database"; -import assert from "assert"; +import { assert } from "@repo/error-handling"; import Redis from "ioredis"; import winston from "winston"; import { BundleRepository } from "../database/BundleRepository"; diff --git a/packages/webhooks/package.json b/packages/webhooks/package.json index 7ecb36e..0dd53bc 100644 --- a/packages/webhooks/package.json +++ b/packages/webhooks/package.json @@ -21,6 +21,7 @@ "author": "", "license": "ISC", "dependencies": { + "@repo/error-handling": "workspace:*", "@repo/indexer-database": "workspace:*", "bullmq": "^5.12.12", "express": "^4.19.2", diff --git a/packages/webhooks/src/eventProcessorManager.ts b/packages/webhooks/src/eventProcessorManager.ts index 12aa53a..afa1787 100644 --- a/packages/webhooks/src/eventProcessorManager.ts +++ b/packages/webhooks/src/eventProcessorManager.ts @@ -1,7 +1,7 @@ import { Logger } from "winston"; -import assert from "assert"; import { DataSource, entities } from "@repo/indexer-database"; +import { assert } from "@repo/error-handling"; import { WebhookClientRepository } from "./database/webhookClientRepository"; import { JSONValue, IEventProcessor } from "./types"; diff --git a/packages/webhooks/src/eventProcessors/depositStatus.ts b/packages/webhooks/src/eventProcessors/depositStatus.ts index 889a119..a945974 100644 --- a/packages/webhooks/src/eventProcessors/depositStatus.ts +++ b/packages/webhooks/src/eventProcessors/depositStatus.ts @@ -1,8 +1,9 @@ -import assert from "assert"; import * as ss from "superstruct"; import { Logger } from "winston"; +import { assert } from "@repo/error-handling"; import { DataSource, entities } from "@repo/indexer-database"; + import { WebhookRequestRepository } from "../database/webhookRequestRepository"; import { customId } from "../utils"; diff --git a/packages/webhooks/src/factory.ts b/packages/webhooks/src/factory.ts index 1d5023e..6f02657 100644 --- a/packages/webhooks/src/factory.ts +++ b/packages/webhooks/src/factory.ts @@ -1,8 +1,9 @@ -import assert from "assert"; import { Logger } from "winston"; import { Redis } from "ioredis"; import { DataSource } from "@repo/indexer-database"; +import { assert } from "@repo/error-handling"; + import { EventProcessorManager } from "./eventProcessorManager"; import { WebhookNotifier } from "./notifier"; import { DepositStatusProcessor } from "./eventProcessors"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3172ff3..b61712d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: apps/node: dependencies: + '@repo/error-handling': + specifier: workspace:* + version: link:../../packages/error-handling '@repo/indexer': specifier: workspace:* version: link:../../packages/indexer @@ -151,6 +154,9 @@ importers: '@across-protocol/sdk': specifier: ^3.2.2 version: 3.2.2(@babel/core@7.25.2)(@eth-optimism/contracts@0.6.0(bufferutil@4.0.8)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@ethersproject/abi@5.7.0)(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(buffer-layout@1.2.2)(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typechain@4.0.3(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + '@repo/error-handling': + specifier: workspace:* + version: link:../error-handling '@repo/webhooks': specifier: workspace:* version: link:../webhooks @@ -233,6 +239,9 @@ importers: packages/indexer-api: dependencies: + '@repo/error-handling': + specifier: workspace:* + version: link:../error-handling '@repo/indexer': specifier: workspace:* version: link:../indexer @@ -506,6 +515,9 @@ importers: packages/webhooks: dependencies: + '@repo/error-handling': + specifier: workspace:* + version: link:../error-handling '@repo/indexer-database': specifier: workspace:* version: link:../indexer-database @@ -8425,6 +8437,25 @@ snapshots: '@across-protocol/constants@3.1.20': {} + '@across-protocol/contracts@0.1.4(@babel/core@7.25.2)(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(typechain@4.0.3(typescript@5.5.4))(utf-8-validate@5.0.10)': + dependencies: + '@eth-optimism/contracts': 0.5.40(bufferutil@4.0.8)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@openzeppelin/contracts': 4.1.0 + '@uma/core': 2.59.1(@babel/core@7.25.2)(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(typechain@4.0.3(typescript@5.5.4))(utf-8-validate@5.0.10) + transitivePeerDependencies: + - '@babel/core' + - '@codechecks/client' + - '@ethersproject/hardware-wallets' + - bufferutil + - debug + - encoding + - ethers + - hardhat + - supports-color + - ts-generator + - typechain + - utf-8-validate + '@across-protocol/contracts@0.1.4(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@eth-optimism/contracts': 0.5.40(bufferutil@4.0.8)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) @@ -10894,7 +10925,7 @@ snapshots: '@uma/common@2.37.1(@babel/core@7.25.2)(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(typechain@4.0.3(typescript@5.5.4))(utf-8-validate@5.0.10)': dependencies: - '@across-protocol/contracts': 0.1.4(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@across-protocol/contracts': 0.1.4(@babel/core@7.25.2)(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(typechain@4.0.3(typescript@5.5.4))(utf-8-validate@5.0.10) '@ethersproject/address': 5.7.0 '@ethersproject/bignumber': 5.7.0 '@ethersproject/bytes': 5.7.0 @@ -11003,9 +11034,9 @@ snapshots: '@ethersproject/constants': 5.7.0 '@google-cloud/kms': 3.8.0(encoding@0.1.13) '@google-cloud/storage': 6.12.0(encoding@0.1.13) - '@nomicfoundation/hardhat-verify': 1.1.1(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomiclabs/hardhat-web3': 2.0.0(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(web3@1.10.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': 1.1.1(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-web3': 2.0.0(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(web3@1.10.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) '@truffle/contract': 4.6.17(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) '@truffle/hdwallet-provider': 1.5.1-alpha.1(@babel/core@7.25.2)(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) '@types/ethereum-protocol': 1.0.5 @@ -11019,7 +11050,7 @@ snapshots: dotenv: 9.0.2 eth-crypto: 2.6.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) hardhat-deploy: 0.9.1(@ethersproject/hardware-wallets@5.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - hardhat-gas-reporter: 1.0.10(bufferutil@4.0.8)(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + hardhat-gas-reporter: 1.0.10(bufferutil@4.0.8)(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@16.18.104)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) hardhat-typechain: 0.3.5(hardhat@2.22.12(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.7.3)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(ts-generator@0.1.1)(typechain@4.0.3(typescript@5.5.4)) lodash.uniqby: 4.7.0 minimist: 1.2.8