1
1
import { FastifyInstance } from "fastify" ;
2
2
import { ReasonPhrases , StatusCodes } from "http-status-codes" ;
3
+ import { ZodError } from "zod" ;
3
4
import { env } from "../../utils/env" ;
4
5
import { logger } from "../../utils/logger" ;
5
6
@@ -20,6 +21,41 @@ export const createCustomError = (
20
21
code,
21
22
} ) ;
22
23
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
+
23
59
export const createCustomDateTimestampError = ( key : string ) : CustomError => {
24
60
return createCustomError (
25
61
`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 => {
31
67
const flipObject = ( data : any ) =>
32
68
Object . fromEntries ( Object . entries ( data ) . map ( ( [ key , value ] ) => [ value , key ] ) ) ;
33
69
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
+
34
85
export 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,
69
93
} ) ;
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
+ ) ;
72
165
} ;
0 commit comments