package api import ( "encoding/json" "errors" "fmt" "io" "net/http" ) var ( // ErrEmptyBody is returned when the request body is empty or nil. ErrEmptyBody = errors.New("request body is empty") // ErrInvalidJSON is returned when the request body contains invalid JSON. ErrInvalidJSON = errors.New("invalid JSON") ) // DecodeJSON decodes JSON from the request body into v. // Returns descriptive errors for common failure cases: // - nil or empty body → ErrEmptyBody // - malformed JSON → ErrInvalidJSON // // Usage: // // if err := api.DecodeJSON(r, &req); err != nil { // api.WriteBadRequest(w, r, "invalid request body") // return // } func DecodeJSON(r *http.Request, v any) error { if r.Body == nil { return ErrEmptyBody } decoder := json.NewDecoder(r.Body) if err := decoder.Decode(v); err != nil { if errors.Is(err, io.EOF) { return ErrEmptyBody } return fmt.Errorf("%w: %w", ErrInvalidJSON, err) } return nil } // DecodeJSONStrict decodes JSON from the request body into v. // Rejects JSON containing fields not present in the target struct. func DecodeJSONStrict(r *http.Request, v any) error { if r.Body == nil { return ErrEmptyBody } decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(v); err != nil { if errors.Is(err, io.EOF) { return ErrEmptyBody } return fmt.Errorf("%w: %w", ErrInvalidJSON, err) } return nil }