mirror of
https://github.com/kataras/iris.git
synced 2026-01-09 13:05:56 +00:00
rest json errors: handle unspported NaN values as the standard package's error doesn't give us more information
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
@@ -404,22 +405,49 @@ func handleAPIError(ctx *context.Context, apiErr client.APIError) {
|
||||
}
|
||||
|
||||
func handleJSONError(ctx *context.Context, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
messageText := "unable to parse request body"
|
||||
wireError := InvalidArgument
|
||||
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
wireError.Details(ctx, messageText, "empty body")
|
||||
return true
|
||||
}
|
||||
|
||||
var syntaxErr *json.SyntaxError
|
||||
if errors.As(err, &syntaxErr) {
|
||||
InvalidArgument.Details(ctx, "unable to parse body", "syntax error at byte offset %d", syntaxErr.Offset)
|
||||
wireError.Details(ctx, messageText, "json: 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)
|
||||
wireError.Details(ctx, messageText, unmarshalErr.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
var unsupportedValueErr *json.UnsupportedValueError
|
||||
if errors.As(err, &unsupportedValueErr) {
|
||||
wireError.Details(ctx, messageText, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
var unsupportedTypeErr *json.UnsupportedTypeError
|
||||
if errors.As(err, &unsupportedTypeErr) {
|
||||
wireError.Details(ctx, messageText, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
var invalidUnmarshalErr *json.InvalidUnmarshalError
|
||||
if errors.As(err, &invalidUnmarshalErr) {
|
||||
wireError.Details(ctx, messageText, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
// else {
|
||||
// InvalidArgument.Details(ctx, "unable to parse body", err.Error())
|
||||
// }
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,9 +2,13 @@ package errors
|
||||
|
||||
import (
|
||||
stdContext "context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
recovery "github.com/kataras/iris/v12/middleware/recover"
|
||||
@@ -33,7 +37,7 @@ func RecoveryHandler(ctx *context.Context) {
|
||||
// Handle handles a generic response and error from a service call and sends a JSON response to the client.
|
||||
// It returns a boolean value indicating whether the handle was successful or not.
|
||||
// If the error is not nil, it calls HandleError to send an appropriate error response to the client.
|
||||
func Handle(ctx *context.Context, resp interface{}, err error) bool {
|
||||
func Handle(ctx *context.Context, resp any, err error) bool {
|
||||
if HandleError(ctx, err) {
|
||||
return false
|
||||
}
|
||||
@@ -41,7 +45,17 @@ func Handle(ctx *context.Context, resp interface{}, err error) bool {
|
||||
ctx.StatusCode(http.StatusOK)
|
||||
|
||||
if resp != nil {
|
||||
if ctx.JSON(resp) != nil {
|
||||
if jsonErr := ctx.JSON(resp); jsonErr != nil { // this returns the original error as it's, even if it's handled by the HandleJSONError function.
|
||||
if unsupportedValueErr, ok := jsonErr.(*json.UnsupportedValueError); ok {
|
||||
if unsupportedValueErr.Str == "NaN" {
|
||||
if nanErr := checkNaN(resp); nanErr != nil {
|
||||
newErr := fmt.Errorf("unable to parse response body: json: unspported value: %w", nanErr)
|
||||
LogError(ctx, newErr)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -49,6 +63,49 @@ func Handle(ctx *context.Context, resp interface{}, err error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// checkNaN checks if any exported field in the struct is NaN and returns an error if it is.
|
||||
func checkNaN(v interface{}) error {
|
||||
val := reflect.ValueOf(v)
|
||||
return checkNaNRecursive(val, val.Type().Name())
|
||||
}
|
||||
|
||||
func checkNaNRecursive(val reflect.Value, path string) error {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
return nil
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
fieldType := val.Type().Field(i)
|
||||
fieldPath := fmt.Sprintf("%s.%s", path, fieldType.Name)
|
||||
|
||||
if err := checkNaNRecursive(field, fieldPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
elem := val.Index(i)
|
||||
elemPath := fmt.Sprintf("%s[%d]", path, i)
|
||||
|
||||
if err := checkNaNRecursive(elem, elemPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
if math.IsNaN(val.Float()) {
|
||||
return fmt.Errorf("NaN value found in field: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDPayload is a simple struct which describes a json id value.
|
||||
type IDPayload[T string | int] struct {
|
||||
ID T `json:"id"`
|
||||
|
||||
Reference in New Issue
Block a user