Skip to content

Commit eb95d58

Browse files
committed
feat: introduce api error codes
1 parent 8f5a63f commit eb95d58

File tree

7 files changed

+90
-37
lines changed

7 files changed

+90
-37
lines changed

backend/pkg/api/handlers/handler_service.go

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ func (h *HandlerService) getDashboardId(ctx context.Context, dashboardIdParam in
159159
return nil, err
160160
}
161161
if utils.GWeiToWei(big.NewInt(int64(validatorEb))).GreaterThan(perks.EffectiveBalancePerDashboard) {
162-
return nil, newBadRequestErr("effective balance of validators in list is too high, maximum is %d", perks.EffectiveBalancePerDashboard.Div(decimal.NewFromInt(1e9)).IntPart())
162+
return nil, apiError{
163+
err: newBadRequestErr("effective balance of validators in list is too high, maximum is %d", perks.EffectiveBalancePerDashboard.Div(decimal.NewFromInt(1e9)).IntPart()),
164+
code: types.ErrorEffectiveBalanceTooHigh,
165+
}
163166
}
164167
return &types.VDBId{Validators: validators}, nil
165168
}
@@ -259,11 +262,21 @@ func writeResponse(w http.ResponseWriter, r *http.Request, statusCode int, respo
259262
}
260263
}
261264

262-
func returnError(w http.ResponseWriter, r *http.Request, code int, err error) {
265+
type apiError struct {
266+
err error
267+
code types.ErrorCode
268+
}
269+
270+
func (e apiError) Error() string {
271+
return e.err.Error()
272+
}
273+
274+
func returnError(w http.ResponseWriter, r *http.Request, statusCode int, errorCode types.ErrorCode, err error) {
263275
response := types.ApiErrorResponse{
264276
Error: err.Error(),
277+
Code: errorCode,
265278
}
266-
writeResponse(w, r, code, response)
279+
writeResponse(w, r, statusCode, response)
267280
}
268281

269282
func returnOk(w http.ResponseWriter, r *http.Request, data interface{}) {
@@ -281,31 +294,23 @@ func returnNoContent(w http.ResponseWriter, r *http.Request) {
281294
// Errors
282295

283296
func returnBadRequest(w http.ResponseWriter, r *http.Request, err error) {
284-
returnError(w, r, http.StatusBadRequest, err)
285-
}
286-
287-
func returnUnauthorized(w http.ResponseWriter, r *http.Request, err error) {
288-
returnError(w, r, http.StatusUnauthorized, err)
297+
returnError(w, r, http.StatusBadRequest, types.ErrorBadRequest, err)
289298
}
290299

291300
func returnNotFound(w http.ResponseWriter, r *http.Request, err error) {
292-
returnError(w, r, http.StatusNotFound, err)
301+
returnError(w, r, http.StatusNotFound, types.ErrorNotFound, err)
293302
}
294303

295304
func returnConflict(w http.ResponseWriter, r *http.Request, err error) {
296-
returnError(w, r, http.StatusConflict, err)
305+
returnError(w, r, http.StatusConflict, types.ErrorConflict, err)
297306
}
298307

299308
func returnForbidden(w http.ResponseWriter, r *http.Request, err error) {
300-
returnError(w, r, http.StatusForbidden, err)
309+
returnError(w, r, http.StatusForbidden, types.ErrorForbidden, err)
301310
}
302311

303312
func returnTooManyRequests(w http.ResponseWriter, r *http.Request, err error) {
304-
returnError(w, r, http.StatusTooManyRequests, err)
305-
}
306-
307-
func returnGone(w http.ResponseWriter, r *http.Request, err error) {
308-
returnError(w, r, http.StatusGone, err)
313+
returnError(w, r, http.StatusTooManyRequests, types.ErrorTooManyRequests, err)
309314
}
310315

311316
const maxBodySize = 10 * 1024
@@ -327,33 +332,46 @@ func logApiError(r *http.Request, err error, callerSkip int, additionalInfos ...
327332
}
328333

329334
func handleErr(w http.ResponseWriter, r *http.Request, err error) {
335+
if errors.Is(err, context.Canceled) && r.Context().Err() == context.Canceled {
336+
// if the request context was canceled, we don't log the error, as it is expected behavior
337+
return
338+
}
339+
if err == nil {
340+
logApiError(r, errors.New("nil error passed to handleErr"), 1)
341+
return
342+
}
343+
// check if err is an apiError
344+
apiErr, ok := err.(*apiError)
345+
if ok {
346+
// if it is, we use the error code from the apiError
347+
returnError(w, r, http.StatusInternalServerError, apiErr.code, apiErr.err)
348+
return
349+
}
350+
// otherwise we check for common errors and return the appropriate status code and error code
351+
statusCode, errorCode := getErrorStatusCode(err)
352+
returnError(w, r, statusCode, errorCode, err)
353+
}
354+
355+
func getErrorStatusCode(err error) (int, types.ErrorCode) {
330356
switch {
331357
case errors.Is(err, errBadRequest):
332-
returnBadRequest(w, r, err)
358+
return http.StatusBadRequest, types.ErrorBadRequest
359+
case errors.Is(err, errInternalServer):
360+
return http.StatusNotFound, types.ErrorInternalServerError
333361
case errors.Is(err, dataaccess.ErrNotFound):
334-
returnNotFound(w, r, err)
362+
return http.StatusUnauthorized, types.ErrorNotFound
335363
case errors.Is(err, errUnauthorized):
336-
returnUnauthorized(w, r, err)
364+
return http.StatusForbidden, types.ErrorUnauthorized
337365
case errors.Is(err, errForbidden):
338-
returnForbidden(w, r, err)
366+
return http.StatusConflict, types.ErrorForbidden
339367
case errors.Is(err, errConflict):
340-
returnConflict(w, r, err)
368+
return http.StatusServiceUnavailable, types.ErrorConflict
341369
case errors.Is(err, services.ErrWaiting):
342-
returnError(w, r, http.StatusServiceUnavailable, err)
370+
return http.StatusTooManyRequests, types.ErrorTooManyRequests
343371
case errors.Is(err, errTooManyRequests):
344-
returnTooManyRequests(w, r, err)
345-
case errors.Is(err, errGone):
346-
returnGone(w, r, err)
347-
case errors.Is(err, context.Canceled):
348-
if r.Context().Err() != context.Canceled { // only return error if the request context was canceled
349-
logApiError(r, err, 1)
350-
returnError(w, r, http.StatusInternalServerError, err)
351-
}
352-
default:
353-
logApiError(r, err, 1)
354-
// TODO: don't return the error message to the user in production
355-
returnError(w, r, http.StatusInternalServerError, err)
372+
return http.StatusGone, types.ErrorGone
356373
}
374+
return http.StatusInternalServerError, types.ErrorInternalServerError
357375
}
358376

359377
// --------------------------------------

backend/pkg/api/handlers/machine_metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (h *HandlerService) LegacyPostUserMachineMetrics(w http.ResponseWriter, r *
7474
}
7575

7676
if h.cfg.Frontend.DisableStatsInserts {
77-
returnError(w, r, http.StatusServiceUnavailable, fmt.Errorf("machine metrics pushing is temporarily disabled"))
77+
returnError(w, r, http.StatusServiceUnavailable, types.ErrorServiceUnavailable, fmt.Errorf("machine metrics pushing is temporarily disabled"))
7878
return
7979
}
8080

backend/pkg/api/handlers/public.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWrit
411411
return
412412
}
413413
if groupId == types.DefaultGroupId {
414-
returnBadRequest(w, r, errors.New("cannot delete default group"))
414+
handleErr(w, r, errors.New("cannot delete default group"))
415415
return
416416
}
417417
ctx := r.Context()

backend/pkg/api/types/common.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ type ApiResponse struct {
1717
}
1818

1919
type ApiErrorResponse struct {
20-
Error string `json:"error"`
20+
Error string `json:"error"`
21+
Code ErrorCode `json:"code"`
2122
}
2223

2324
type ApiDataResponse[T any] struct {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package types
2+
3+
type ErrorCode string
4+
5+
const (
6+
ErrorEffectiveBalanceTooHigh ErrorCode = "MAX_EB_EXCEEDED"
7+
ErrorBadRequest ErrorCode = "BAD_REQUEST"
8+
ErrorInternalServerError ErrorCode = "INTERNAL_SERVER_ERROR"
9+
ErrorNotFound ErrorCode = "NOT_FOUND"
10+
ErrorUnauthorized ErrorCode = "UNAUTHORIZED"
11+
ErrorForbidden ErrorCode = "FORBIDDEN"
12+
ErrorConflict ErrorCode = "CONFLICT"
13+
ErrorTooManyRequests ErrorCode = "TOO_MANY_REQUESTS"
14+
ErrorGone ErrorCode = "GONE"
15+
ErrorServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
16+
)

frontend/types/api/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface ApiResponse {
1515
}
1616
export interface ApiErrorResponse {
1717
error: string;
18+
code: ErrorCode;
1819
}
1920
export interface ApiDataResponse<T extends any> {
2021
data: T;

frontend/types/api/error_codes.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Code generated by tygo. DO NOT EDIT.
2+
/* eslint-disable */
3+
4+
//////////
5+
// source: error_codes.go
6+
7+
export type ErrorCode = string;
8+
export const ErrorEffectiveBalanceTooHigh: ErrorCode = "MAX_EB_EXCEEDED";
9+
export const ErrorBadRequest: ErrorCode = "BAD_REQUEST";
10+
export const ErrorInternalServerError: ErrorCode = "INTERNAL_SERVER_ERROR";
11+
export const ErrorNotFound: ErrorCode = "NOT_FOUND";
12+
export const ErrorUnauthorized: ErrorCode = "UNAUTHORIZED";
13+
export const ErrorForbidden: ErrorCode = "FORBIDDEN";
14+
export const ErrorConflict: ErrorCode = "CONFLICT";
15+
export const ErrorTooManyRequests: ErrorCode = "TOO_MANY_REQUESTS";
16+
export const ErrorGone: ErrorCode = "GONE";
17+
export const ErrorServiceUnavailable: ErrorCode = "SERVICE_UNAVAILABLE";

0 commit comments

Comments
 (0)