package internalhttp import ( "encoding/json" "errors" "fmt" "io" "net/http" "strings" "galaxy/authsession/internal/service/shared" "github.com/gin-gonic/gin" ) const internalErrorCodeContextKey = "internal_error_code" type malformedJSONRequestError struct { message string } func (e *malformedJSONRequestError) Error() string { if e == nil { return "" } return e.message } func decodeJSONRequest(request *http.Request, target any) error { if request == nil || request.Body == nil { return &malformedJSONRequestError{message: "request body must not be empty"} } return decodeJSONReader(request.Body, target) } func decodeJSONReader(reader io.Reader, target any) error { decoder := json.NewDecoder(reader) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { return describeJSONDecodeError(err) } if err := decoder.Decode(&struct{}{}); err != nil { if errors.Is(err, io.EOF) { return nil } return &malformedJSONRequestError{message: "request body must contain a single JSON object"} } return &malformedJSONRequestError{message: "request body must contain a single JSON object"} } func describeJSONDecodeError(err error) error { var syntaxErr *json.SyntaxError var typeErr *json.UnmarshalTypeError switch { case errors.Is(err, io.EOF): return &malformedJSONRequestError{message: "request body must not be empty"} case errors.As(err, &syntaxErr): return &malformedJSONRequestError{message: "request body contains malformed JSON"} case errors.Is(err, io.ErrUnexpectedEOF): return &malformedJSONRequestError{message: "request body contains malformed JSON"} case errors.As(err, &typeErr): if strings.TrimSpace(typeErr.Field) != "" { return &malformedJSONRequestError{ message: fmt.Sprintf("request body contains an invalid value for %q", typeErr.Field), } } return &malformedJSONRequestError{message: "request body contains an invalid JSON value"} case strings.HasPrefix(err.Error(), "json: unknown field "): return &malformedJSONRequestError{ message: fmt.Sprintf("request body contains unknown field %s", strings.TrimPrefix(err.Error(), "json: unknown field ")), } default: return &malformedJSONRequestError{message: "request body contains invalid JSON"} } } func abortWithProjection(c *gin.Context, projection shared.InternalErrorProjection) { c.Set(internalErrorCodeContextKey, projection.Code) c.AbortWithStatusJSON(projection.StatusCode, errorResponse{ Error: errorBody{ Code: projection.Code, Message: projection.Message, }, }) }