package main import ( "encoding/json" "errors" "fmt" "io" "net/http" "strings" ) const ( maxHttpJsonBodySize = 256 * 1024 ) const ( EmptyRequestBody = "Request body must not be empty" ) type MalformedRequest struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } func (mr *MalformedRequest) Error() string { return mr.Msg } func HttpDecodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error { // Use http.MaxBytesReader to enforce a maximum read of 1MB from the // response body. A request body larger than that will now result in // Decode() returning a "http: request body too large" error. r.Body = http.MaxBytesReader(w, r.Body, maxHttpJsonBodySize) dec := json.NewDecoder(r.Body) //dec.DisallowUnknownFields() err := dec.Decode(&dst) if err != nil { var syntaxError *json.SyntaxError var unmarshalTypeError *json.UnmarshalTypeError switch { case errors.As(err, &syntaxError): msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset) return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} case errors.Is(err, io.ErrUnexpectedEOF): msg := fmt.Sprintf("Request body contains badly-formed JSON") return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} case errors.As(err, &unmarshalTypeError): msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset) return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} case strings.HasPrefix(err.Error(), "json: unknown field "): fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") msg := fmt.Sprintf("Request body contains unknown field %s", fieldName) return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} case errors.Is(err, io.EOF): msg := "Request body must not be empty" return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} case err.Error() == "http: request body too large": msg := "Request body must not be larger than 1MB" return &MalformedRequest{Code: http.StatusRequestEntityTooLarge, Msg: msg} default: return err } } err = dec.Decode(&struct{}{}) if err != io.EOF { msg := "Request body must only contain a single JSON object" return &MalformedRequest{Code: http.StatusBadRequest, Msg: msg} } return nil }