Skip to content

Commit f3c5bd9

Browse files
authored
Updates: Engine Error Handling (#569)
* updated error handling * Updates: Engine Error Handling * updates error catching * updates * updates * comments
1 parent cffc32a commit f3c5bd9

File tree

3 files changed

+183
-52
lines changed

3 files changed

+183
-52
lines changed

src/server/middleware/error.ts

Lines changed: 129 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FastifyInstance } from "fastify";
22
import { ReasonPhrases, StatusCodes } from "http-status-codes";
3+
import { ZodError } from "zod";
34
import { env } from "../../utils/env";
45
import { 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+
2359
export 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 => {
3167
const 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+
3485
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,
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
};

src/server/schemas/nft/index.ts

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,63 @@ import { BigNumber } from "ethers";
44
// NFTInput format compatible with v5 SDK
55
export const nftInputSchema = Type.Partial(
66
Type.Object({
7-
name: Type.String(),
8-
description: Type.String(),
9-
image: Type.String(),
10-
animation_url: Type.String(),
11-
external_url: Type.String(),
12-
background_color: Type.String(),
13-
properties: Type.Any(),
7+
name: Type.String({
8+
description: "The name of the NFT",
9+
}),
10+
description: Type.String({
11+
description: "The description of the NFT",
12+
}),
13+
image: Type.String({
14+
description: "The image of the NFT",
15+
}),
16+
animation_url: Type.String({
17+
description: "The animation url of the NFT",
18+
}),
19+
external_url: Type.String({
20+
description: "The external url of the NFT",
21+
}),
22+
background_color: Type.String({
23+
description: "The background color of the NFT",
24+
}),
25+
properties: Type.Any({
26+
description: "The properties of the NFT",
27+
}),
1428
}),
1529
);
1630

1731
export const nftMetadataInputSchema = Type.Object({
18-
name: Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Null()])),
19-
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
20-
image: Type.Optional(Type.Union([Type.String(), Type.Null()])),
21-
external_url: Type.Optional(Type.Union([Type.String(), Type.Null()])),
22-
animation_url: Type.Optional(Type.Union([Type.String(), Type.Null()])),
23-
properties: Type.Optional(Type.Any()),
24-
attributes: Type.Optional(Type.Any()),
25-
background_color: Type.Optional(Type.Union([Type.String(), Type.Null()])),
32+
name: Type.Optional({
33+
description: "The name of the NFT",
34+
...Type.Union([Type.String(), Type.Number(), Type.Null()]),
35+
}),
36+
description: Type.Optional({
37+
description: "The description of the NFT",
38+
...Type.Union([Type.String(), Type.Null()]),
39+
}),
40+
image: Type.Optional({
41+
...Type.Union([Type.String(), Type.Null()]),
42+
description: "The image of the NFT",
43+
}),
44+
external_url: Type.Optional({
45+
...Type.Union([Type.String(), Type.Null()]),
46+
description: "The external url of the NFT",
47+
}),
48+
animation_url: Type.Optional({
49+
...Type.Union([Type.String(), Type.Null()]),
50+
description: "The animation url of the NFT",
51+
}),
52+
properties: Type.Optional({
53+
...Type.Any(),
54+
description: "The properties of the NFT",
55+
}),
56+
attributes: Type.Optional({
57+
...Type.Any(),
58+
description: "The attributes of the NFT",
59+
}),
60+
background_color: Type.Optional({
61+
...Type.Union([Type.String(), Type.Null()]),
62+
description: "The background color of the NFT",
63+
}),
2664
});
2765

2866
export const nftMetadataSchema = Type.Object(

src/server/schemas/sharedApiSchemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { StatusCodes } from "http-status-codes";
66

77
export const baseReplyErrorSchema = Type.Object({
88
message: Type.Optional(Type.String()),
9-
reason: Type.Optional(Type.String()),
9+
reason: Type.Optional(Type.Any()),
1010
code: Type.Optional(Type.String()),
1111
stack: Type.Optional(Type.String()),
1212
statusCode: Type.Optional(Type.Number()),

0 commit comments

Comments
 (0)