From 8f2deb68730d11820caea63bbdca1197c7742cd2 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 4 Jan 2024 13:02:09 +0200 Subject: [PATCH] minor improvements on x/errors package, see previous commit for more --- x/errors/errors.go | 69 ++++++++++++++++++++++++++++++++++---------- x/errors/handlers.go | 25 +++++++++++----- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/x/errors/errors.go b/x/errors/errors.go index 6a6b2ef1..35aa895d 100644 --- a/x/errors/errors.go +++ b/x/errors/errors.go @@ -139,6 +139,15 @@ func HandleError(ctx *context.Context, err error) bool { return false } + for errorCodeName, errorFuncs := range errorFuncCodeMap { + for _, errorFunc := range errorFuncs { + if errToSend := errorFunc(err); errToSend != nil { + errorCodeName.Err(ctx, errToSend) + return true + } + } + } + // Unwrap and collect the errors slice so the error result doesn't contain the ErrorCodeName type // and fire the error status code and title based on this error code name itself. var asErrCode ErrorCodeName @@ -161,13 +170,18 @@ func HandleError(ctx *context.Context, err error) bool { return true } - for errorCodeName, errorFuncs := range errorFuncCodeMap { - for _, errorFunc := range errorFuncs { - if errToSend := errorFunc(err); errToSend != nil { - errorCodeName.Err(ctx, errToSend) - return true - } - } + if handleJSONError(ctx, err) { + return true + } + + if vErrs, ok := AsValidationErrors(err); ok { + InvalidArgument.Data(ctx, "validation failure", vErrs) + return true + } + + if apiErr, ok := client.GetError(err); ok { + handleAPIError(ctx, apiErr) + return true } Internal.LogErr(ctx, err) @@ -335,20 +349,45 @@ func HandleAPIError(ctx *context.Context, err error) { // Error expected and came from the external server, // save its body so we can forward it to the end-client. if apiErr, ok := client.GetError(err); ok { - statusCode := apiErr.Response.StatusCode - if statusCode >= 400 && statusCode < 500 { - InvalidArgument.DataWithDetails(ctx, "remote server error", "invalid client request", apiErr.Body) - } else { - Internal.Data(ctx, "remote server error", apiErr.Body) - } - - // Unavailable.DataWithDetails(ctx, "remote server error", "unavailable", apiErr.Body) + handleAPIError(ctx, apiErr) return } Internal.LogErr(ctx, err) } +func handleAPIError(ctx *context.Context, apiErr client.APIError) { + // Error expected and came from the external server, + // save its body so we can forward it to the end-client. + statusCode := apiErr.Response.StatusCode + if statusCode >= 400 && statusCode < 500 { + InvalidArgument.DataWithDetails(ctx, "remote server error", "invalid client request", apiErr.Body) + } else { + Internal.Data(ctx, "remote server error", apiErr.Body) + } + + // Unavailable.DataWithDetails(ctx, "remote server error", "unavailable", apiErr.Body) +} + +func handleJSONError(ctx *context.Context, err error) bool { + var syntaxErr *json.SyntaxError + if errors.As(err, &syntaxErr) { + InvalidArgument.Details(ctx, "unable to parse body", "syntax error at byte offset %d", syntaxErr.Offset) + return true + } + + var unmarshalErr *json.UnmarshalTypeError + if errors.As(err, &unmarshalErr) { + InvalidArgument.Details(ctx, "unable to parse body", "unmarshal error for field %q", unmarshalErr.Field) + return true + } + + return false + // else { + // InvalidArgument.Details(ctx, "unable to parse body", err.Error()) + // } +} + var ( // ErrUnexpected is the HTTP error which sent to the client // when server fails to send an error, it's a fallback error. diff --git a/x/errors/handlers.go b/x/errors/handlers.go index 2a45dcb6..d8dafb20 100644 --- a/x/errors/handlers.go +++ b/x/errors/handlers.go @@ -2,6 +2,8 @@ package errors import ( stdContext "context" + "errors" + "io" "net/http" "github.com/kataras/iris/v12/context" @@ -117,10 +119,11 @@ func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fn var req T switch len(fnInput) { case 0: - err := ctx.ReadJSON(&req) - if err != nil { + var ok bool + req, ok = ReadPayload[T](ctx) + if !ok { var resp R - return resp, !HandleError(ctx, err) + return resp, false } case 1: req = fnInput[0] @@ -200,11 +203,19 @@ func ReadPayload[T any](ctx *context.Context) (T, bool) { var payload T err := ctx.ReadJSON(&payload) if err != nil { - if vErrs, ok := AsValidationErrors(err); ok { - InvalidArgument.Data(ctx, "validation failure", vErrs) - } else { - InvalidArgument.Details(ctx, "unable to parse body", err.Error()) + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + InvalidArgument.Details(ctx, "unable to parse body", "empty body") + return payload, false } + + if !handleJSONError(ctx, err) { + if vErrs, ok := AsValidationErrors(err); ok { + InvalidArgument.Data(ctx, "validation failure", vErrs) + } else { + InvalidArgument.Details(ctx, "unable to parse body", err.Error()) + } + } + return payload, false }