diff --git a/src/api/v2/queries/history.ts b/src/api/v2/queries/history.ts index 0710d87..973a56c 100644 --- a/src/api/v2/queries/history.ts +++ b/src/api/v2/queries/history.ts @@ -1,15 +1,16 @@ import type { Request, Response } from "express"; import { CACHE_TTL_ANCHORED, CACHE_TTL_PENDING, DPID_ENV, getCeramicClient } from "../../../util/config.js"; import { type CeramicClient } from "@desci-labs/desci-codex-lib"; -import parentLogger from "../../../logger.js"; -import { resolveDpid } from "../resolvers/dpid.js"; +import parentLogger, { serializeError } from "../../../logger.js"; +import { DpidResolverError, resolveDpid } from "../resolvers/dpid.js"; import { isDpid } from "../../../util/validation.js"; import { CommitID, StreamID } from "@desci-labs/desci-codex-lib/dist/streams.js"; import { getFromCache, keyBump, setToCache } from "../../../redis.js"; import { cleanupEip155Address } from "../../../util/conversions.js"; +const MODULE_PATH = "api/v2/queries/history" as const; const logger = parentLogger.child({ - module: "api/v2/queries/history", + module: MODULE_PATH, }); export type HistoryQueryRequest = { @@ -22,7 +23,15 @@ export type HistoryQueryParams = { id?: string; }; -export type HistoryQueryResponse = HistoryQueryResult[] | HistoryQueryError; +export type ErrorResponse = { + error: string; + details: unknown; + body: unknown; + params: unknown; + path: typeof MODULE_PATH; +}; + +export type HistoryQueryResponse = HistoryQueryResult[] | ErrorResponse; export type HistoryVersion = { /** Manifest CID at this version */ @@ -44,8 +53,6 @@ export type HistoryQueryResult = { versions: HistoryVersion[]; }; -export type HistoryQueryError = string; - /** * For one or more IDs, fetch metadata and version history. * An ID can be both a streamID and a dPID, but a dPID lookup is a bit slower. @@ -57,10 +64,20 @@ export const historyQueryHandler = async ( const { id } = req.params; const { ids = [] } = req.body; + const baseError = { + params: req.params, + body: req.body, + path: MODULE_PATH, + }; + if (!Array.isArray(ids)) { // Received ids in body, but not as array - logger.error({ body: req.body, params: req.params }, "received malformed IDs"); - return res.status(400).send("body.ids expects string[]"); + logger.error(baseError, "received malformed IDs"); + return res.status(400).send({ + error: "invalid request", + details: "body.ids expects string[]", + ...baseError, + }); } if (id) { @@ -70,8 +87,12 @@ export const historyQueryHandler = async ( if (ids.length === 0) { // Neither ID format was supplied - logger.error({ body: req.body, params: req.params }, "request missing IDs"); - return res.status(400).send("missing /:id or ids array in body"); + logger.error(baseError, "request missing IDs"); + return res.status(400).send({ + error: "invalid request", + details: "missing /:id or ids array in body", + ...baseError, + }); } logger.info({ ids }, "handling history query"); @@ -87,9 +108,23 @@ export const historyQueryHandler = async ( ]); const result = [...codexHistories, ...dpidHistories]; return res.send(result); - } catch (error) { - logger.error({ ids, error }, "failed to compile histories"); - return res.status(500).send("failed to compile histories"); + } catch (e) { + if (e instanceof DpidResolverError) { + const errPayload = { + error: "failed to resolve dpid", + details: serializeError(e), + ...baseError, + }; + logger.error(errPayload, "failed to resolve dpid"); + return res.status(500).send(errPayload); + } + const errPayload = { + error: "failed to compile histories", + details: serializeError(e as Error), + ...baseError, + }; + logger.error(errPayload, "failed to compile histories"); + return res.status(500).send(errPayload); } }; diff --git a/src/api/v2/resolvers/codex.ts b/src/api/v2/resolvers/codex.ts index e35f5c9..41166c1 100644 --- a/src/api/v2/resolvers/codex.ts +++ b/src/api/v2/resolvers/codex.ts @@ -1,5 +1,5 @@ import type { Request, Response } from "express"; -import parentLogger from "../../../logger.js"; +import parentLogger, { serializeError } from "../../../logger.js"; import { pidFromStringID, type PID } from "@desci-labs/desci-codex-lib"; import { getCodexHistory, type HistoryQueryResult } from "../queries/history.js"; @@ -46,7 +46,7 @@ export const resolveCodexHandler = async ( } catch (e) { const errPayload = { error: "Invalid stream or commit ID", - details: "Could not coerce ID into neither stream nor commitID", + details: serializeError(e as Error), params: req.params, path: MODULE_PATH, }; @@ -67,7 +67,7 @@ export const resolveCodexHandler = async ( logger.error({ streamId, versionIx, err }, "failed to resolve stream"); return res.status(404).send({ error: "Could not resolve; does stream/version exist?", - details: err, + details: serializeError(err), params: req.params, path: MODULE_PATH, }); diff --git a/src/api/v2/resolvers/dpid.ts b/src/api/v2/resolvers/dpid.ts index 81efa0c..6d2941d 100644 --- a/src/api/v2/resolvers/dpid.ts +++ b/src/api/v2/resolvers/dpid.ts @@ -1,5 +1,5 @@ import type { Request, Response } from "express"; -import parentLogger from "../../../logger.js"; +import parentLogger, { serializeError } from "../../../logger.js"; import { CACHE_TTL_ANCHORED, CACHE_TTL_PENDING, DPID_ENV, getDpidAliasRegistry } from "../../../util/config.js"; import { ResolverError } from "../../../errors.js"; import { getCodexHistory, type HistoryQueryResult, type HistoryVersion } from "../queries/history.js"; @@ -7,7 +7,7 @@ import { getFromCache, setToCache } from "../../../redis.js"; import type { DpidAliasRegistry } from "@desci-labs/desci-contracts/dist/typechain-types/DpidAliasRegistry.js"; import { BigNumber } from "ethers"; -const MODULE_PATH = "/api/v2/resolvers/codex" as const; +const MODULE_PATH = "/api/v2/resolvers/dpid" as const; const logger = parentLogger.child({ module: MODULE_PATH, }); @@ -53,7 +53,7 @@ export const resolveDpidHandler = async ( if (e instanceof DpidResolverError) { const errPayload = { error: e.message, - details: e.cause, + details: serializeError(e.cause), params: req.params, path: MODULE_PATH, }; @@ -63,7 +63,7 @@ export const resolveDpidHandler = async ( const err = e as Error; const errPayload = { error: err.message, - details: err, + details: serializeError(err), params: req.params, path: MODULE_PATH, }; @@ -148,8 +148,11 @@ export const resolveDpid = async (dpid: number, versionIx?: number): Promise { + v[1] = BigNumber.from(v[1]); + }); } const owner = resolvedEntry[0]; @@ -166,11 +169,11 @@ export const resolveDpid = async (dpid: number, versionIx?: number): Promise { }, [] as LegacyVersion[]); }; -const isLegacyDupe = ( - [aCid, aTimeBn]: LegacyVersion, - [bCid, bTimeBn]: LegacyVersion -): Boolean => { +const isLegacyDupe = ([aCid, aTimeBn]: LegacyVersion, [bCid, bTimeBn]: LegacyVersion): boolean => { const cidIsEqual = aCid === bCid; const timeIsEqual = aTimeBn.toNumber() === bTimeBn.toNumber(); return cidIsEqual && timeIsEqual; diff --git a/src/api/v2/resolvers/generic.ts b/src/api/v2/resolvers/generic.ts index 94eaa11..6057f35 100644 --- a/src/api/v2/resolvers/generic.ts +++ b/src/api/v2/resolvers/generic.ts @@ -2,7 +2,7 @@ import type { Request, Response } from "express"; import axios from "axios"; import type { ResearchObjectV1 } from "@desci-labs/desci-models"; -import parentLogger from "../../../logger.js"; +import parentLogger, { serializeError } from "../../../logger.js"; import analytics, { LogEventType } from "../../../analytics.js"; import { IPFS_GATEWAY, getNodesUrl } from "../../../util/config.js"; import { DpidResolverError, resolveDpid } from "./dpid.js"; @@ -152,7 +152,7 @@ export const resolveGenericHandler = async ( if (e instanceof DpidResolverError) { const errPayload = { error: e.message, - details: e.cause, + details: serializeError(e.cause), ...baseError, }; logger.error(errPayload, "failed to resolve dpid"); @@ -161,7 +161,7 @@ export const resolveGenericHandler = async ( const err = e as Error; const errPayload = { error: err.message, - details: err, + details: serializeError(err), ...baseError, }; logger.error(errPayload, "unexpected error occurred"); @@ -216,7 +216,7 @@ export const resolveGenericHandler = async ( // Doesn't seem it was a validDagUrl const errPayload = { error: "Failed to resolve DAG URL; check path and versioning", - details: e, + details: serializeError(e as Error), ...baseError, }; logger.error(errPayload, "got invalid DAG URL"); diff --git a/src/logger.ts b/src/logger.ts index 06e13d2..eb1dba2 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -39,6 +39,8 @@ function omitBuffer(array: any) { }); } +export const serializeError = (e: Error) => pino.stdSerializers.err(e); + process.on("uncaughtException", (err) => { logger.fatal(err, "uncaught exception"); });