crowdsec/pkg/apiclient/resperr.go
mmetc 3532e872d3
metrics: avoid nil deref with inactive bouncers or malformed response (#3170)
* metrics: avoid nil deref with inactive bouncers

* log message from API even it if cannot be parsed

* fix unit test
2024-08-08 16:46:39 +02:00

61 lines
1.4 KiB
Go

package apiclient
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/crowdsecurity/crowdsec/pkg/models"
)
type ErrorResponse struct {
models.ErrorResponse
}
func (e *ErrorResponse) Error() string {
message := ptr.OrEmpty(e.Message)
errors := ""
if len(e.Errors) > 0 {
errors = fmt.Sprintf(" (%s)", e.Errors)
}
if message == "" && errors == "" {
errors = "(no errors)"
}
return fmt.Sprintf("API error: %s%s", message, errors)
}
// CheckResponse verifies the API response and builds an appropriate Go error if necessary.
func CheckResponse(r *http.Response) error {
if c := r.StatusCode; 200 <= c && c <= 299 || c == 304 {
return nil
}
ret := &ErrorResponse{}
data, err := io.ReadAll(r.Body)
if err != nil || len(data) == 0 {
ret.Message = ptr.Of(fmt.Sprintf("http code %d, no response body", r.StatusCode))
return ret
}
switch r.StatusCode {
case http.StatusUnprocessableEntity:
ret.Message = ptr.Of(fmt.Sprintf("http code %d, invalid request: %s", r.StatusCode, string(data)))
default:
// try to unmarshal and if there are no 'message' or 'errors' fields, display the body as is,
// the API is following a different convention
err := json.Unmarshal(data, ret)
if err != nil || (ret.Message == nil && len(ret.Errors) == 0) {
ret.Message = ptr.Of(fmt.Sprintf("http code %d, response: %s", r.StatusCode, string(data)))
return ret
}
}
return ret
}