| 102 | }; |
| 103 | |
| 104 | export const errorHandler = ({ |
| 105 | code, |
| 106 | error, |
| 107 | set, |
| 108 | }: IErrorHandlerArgs): IApiErrorResponse => { |
| 109 | const isClientError = isClientErrorCode(code) || isClientApiError(error); |
| 110 | const finalStatus = resolveFinalStatus(code, error); |
| 111 | |
| 112 | const basePayload = { |
| 113 | errorCode: code, |
| 114 | statusCode: finalStatus, |
| 115 | message: |
| 116 | error instanceof ApiError ? error.message : safeMessageFor(code, error), |
| 117 | stack: |
| 118 | env.isDevelopment && error instanceof Error ? error.stack : undefined, |
| 119 | }; |
| 120 | |
| 121 | if (isClientError) { |
| 122 | logger.warn(`Client error ${code}`, { |
| 123 | ...basePayload, |
| 124 | event: "request.client_error", |
| 125 | }); |
| 126 | } else { |
| 127 | logger.error(`Error ${code}`, { |
| 128 | ...basePayload, |
| 129 | event: "request.error", |
| 130 | }); |
| 131 | } |
| 132 | |
| 133 | if (error instanceof ApiError) { |
| 134 | set.status = finalStatus; |
| 135 | |
| 136 | return error.toResponse(); |
| 137 | } |
| 138 | |
| 139 | set.status = finalStatus; |
| 140 | |
| 141 | let apiError: ApiError; |
| 142 | |
| 143 | switch (code) { |
| 144 | case ElysiaErrorCodes.NOT_FOUND: |
| 145 | apiError = ApiErrors.notFound(); |
| 146 | break; |
| 147 | case ElysiaErrorCodes.VALIDATION: |
| 148 | case ElysiaErrorCodes.PARSE: |
| 149 | apiError = ApiErrors.validation( |
| 150 | safeMessageFor(code, error), |
| 151 | extractFieldErrors(error) |
| 152 | ); |
| 153 | break; |
| 154 | case ElysiaErrorCodes.INVALID_COOKIE_SIGNATURE: |
| 155 | /* |
| 156 | * Elysia's cookie middleware throws this BEFORE auth-route derive |
| 157 | * code runs when a signed cookie's signature is missing or wrong |
| 158 | * — i.e. when an attacker sends a tampered or guessed auth cookie. |
| 159 | * It's untrusted user input, not an internal error. |
| 160 | */ |
| 161 | apiError = ApiErrors.unauthorized(); |