11import { FastifyInstance } from "fastify" ;
22import { ReasonPhrases , StatusCodes } from "http-status-codes" ;
3+ import { ZodError } from "zod" ;
34import { env } from "../../utils/env" ;
45import { logger } from "../../utils/logger" ;
56
@@ -20,6 +21,41 @@ export const createCustomError = (
2021 code,
2122} ) ;
2223
24+ // https://github.com/ethers-io/ethers.js/blob/main/src.ts/utils/errors.ts
25+ const ETHERS_ERROR_CODES = new Set ( [
26+ // Generic Errors
27+ "UNKNOWN_ERROR" ,
28+ "NOT_IMPLEMENTED" ,
29+ "UNSUPPORTED_OPERATION" ,
30+ "NETWORK_ERROR" ,
31+ "SERVER_ERROR" ,
32+ "TIMEOUT" ,
33+ "BAD_DATA" ,
34+ "CANCELLED" ,
35+
36+ // Operational Errors
37+ "BUFFER_OVERRUN" ,
38+ "NUMERIC_FAULT" ,
39+
40+ // Argument Errors
41+ "INVALID_ARGUMENT" ,
42+ "MISSING_ARGUMENT" ,
43+ "UNEXPECTED_ARGUMENT" ,
44+ "VALUE_MISMATCH" ,
45+
46+ // Blockchain Errors
47+ "CALL_EXCEPTION" ,
48+ "INSUFFICIENT_FUNDS" ,
49+ "NONCE_EXPIRED" ,
50+ "REPLACEMENT_UNDERPRICED" ,
51+ "TRANSACTION_REPLACED" ,
52+ "UNCONFIGURED_NAME" ,
53+ "OFFCHAIN_FAULT" ,
54+
55+ // User Interaction
56+ "ACTION_REJECTED" ,
57+ ] ) ;
58+
2359export const createCustomDateTimestampError = ( key : string ) : CustomError => {
2460 return createCustomError (
2561 `Invalid ${ key } Value. Needs to new Date() / new Date().toISOstring() / new Date().getTime() / Unix Epoch` ,
@@ -31,42 +67,99 @@ export const createCustomDateTimestampError = (key: string): CustomError => {
3167const flipObject = ( data : any ) =>
3268 Object . fromEntries ( Object . entries ( data ) . map ( ( [ key , value ] ) => [ value , key ] ) ) ;
3369
70+ const isZodError = ( err : unknown ) : boolean => {
71+ return Boolean (
72+ err && ( err instanceof ZodError || ( err as ZodError ) . name === "ZodError" ) ,
73+ ) ;
74+ } ;
75+
76+ const isEthersError = ( error : any ) : boolean => {
77+ return (
78+ error &&
79+ typeof error === "object" &&
80+ "code" in error &&
81+ ETHERS_ERROR_CODES . has ( error . code )
82+ ) ;
83+ } ;
84+
3485export const withErrorHandler = async ( server : FastifyInstance ) => {
35- server . setErrorHandler ( ( error : Error | CustomError , request , reply ) => {
36- logger ( {
37- service : "server" ,
38- level : "error" ,
39- message : `Encountered server error` ,
40- error,
41- } ) ;
42-
43- if ( "statusCode" in error && "code" in error ) {
44- // Transform unexpected errors into a standard payload
45- const statusCode = error . statusCode ?? StatusCodes . INTERNAL_SERVER_ERROR ;
46- const code =
47- error . code ??
48- flipObject ( StatusCodes ) [ statusCode ] ??
49- StatusCodes . INTERNAL_SERVER_ERROR ;
50-
51- const message = error . message ?? ReasonPhrases . INTERNAL_SERVER_ERROR ;
52- reply . status ( statusCode ) . send ( {
53- error : {
54- code,
55- message,
56- statusCode,
57- stack : env . NODE_ENV !== "production" ? error . stack : undefined ,
58- } ,
59- } ) ;
60- } else {
61- // Handle non-custom errors
62- reply . status ( StatusCodes . INTERNAL_SERVER_ERROR ) . send ( {
63- error : {
64- statusCode : 500 ,
65- code : "INTERNAL_SERVER_ERROR" ,
66- message : error . message || ReasonPhrases . INTERNAL_SERVER_ERROR ,
67- stack : env . NODE_ENV !== "production" ? error . stack : undefined ,
68- } ,
86+ server . setErrorHandler (
87+ ( error : Error | CustomError | ZodError , request , reply ) => {
88+ logger ( {
89+ service : "server" ,
90+ level : "error" ,
91+ message : `Encountered server error` ,
92+ error,
6993 } ) ;
70- }
71- } ) ;
94+
95+ // Ethers Error Codes
96+ if ( isEthersError ( error ) ) {
97+ return reply . status ( StatusCodes . BAD_REQUEST ) . send ( {
98+ error : {
99+ code : "BAD_REQUEST" ,
100+ message : "code" in error ? error . code : error . message ,
101+ reason : error . message ,
102+ statusCode : 400 ,
103+ stack : env . NODE_ENV !== "production" ? error . stack : undefined ,
104+ } ,
105+ } ) ;
106+ }
107+
108+ // Zod Typings Errors
109+ if ( isZodError ( error ) ) {
110+ const _error = error as ZodError ;
111+ let parsedMessage : any [ ] = [ ] ;
112+
113+ try {
114+ parsedMessage = JSON . parse ( _error . message ) ;
115+ } catch ( e ) {
116+ console . error ( "Failed to parse error message:" , e ) ;
117+ }
118+ const errorObject =
119+ Array . isArray ( parsedMessage ) && parsedMessage . length > 0
120+ ? parsedMessage [ 0 ]
121+ : { } ;
122+
123+ return reply . status ( StatusCodes . BAD_REQUEST ) . send ( {
124+ error : {
125+ code : "BAD_REQUEST" ,
126+ message : errorObject . message ?? "Invalid Request" ,
127+ reason : errorObject ?? undefined ,
128+ statusCode : 400 ,
129+ stack : env . NODE_ENV !== "production" ? _error . stack : undefined ,
130+ } ,
131+ } ) ;
132+ }
133+
134+ if ( "statusCode" in error && "code" in error ) {
135+ // Transform unexpected errors into a standard payload
136+ const statusCode =
137+ error . statusCode ?? StatusCodes . INTERNAL_SERVER_ERROR ;
138+ const code =
139+ error . code ??
140+ flipObject ( StatusCodes ) [ statusCode ] ??
141+ StatusCodes . INTERNAL_SERVER_ERROR ;
142+
143+ const message = error . message ?? ReasonPhrases . INTERNAL_SERVER_ERROR ;
144+ reply . status ( statusCode ) . send ( {
145+ error : {
146+ code,
147+ message,
148+ statusCode,
149+ stack : env . NODE_ENV !== "production" ? error . stack : undefined ,
150+ } ,
151+ } ) ;
152+ } else {
153+ // Handle non-custom errors
154+ reply . status ( StatusCodes . INTERNAL_SERVER_ERROR ) . send ( {
155+ error : {
156+ statusCode : 500 ,
157+ code : "INTERNAL_SERVER_ERROR" ,
158+ message : error . message || ReasonPhrases . INTERNAL_SERVER_ERROR ,
159+ stack : env . NODE_ENV !== "production" ? error . stack : undefined ,
160+ } ,
161+ } ) ;
162+ }
163+ } ,
164+ ) ;
72165} ;
0 commit comments