package util import ( "context" "encoding/json" "errors" "fmt" "net/http" "strings" "time" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/server/status" ) type ErrorResponse struct { Message string `json:"message"` Code int `json:"code"` } // WriteJSONObject simply writes object to the HTTP response in JSON format func WriteJSONObject(ctx context.Context, w http.ResponseWriter, obj interface{}) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) err := json.NewEncoder(w).Encode(obj) if err != nil { WriteError(ctx, err, w) return } } // Duration is used strictly for JSON requests/responses due to duration marshalling issues type Duration struct { time.Duration } // MarshalJSON marshals the duration func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.String()) } // UnmarshalJSON unmarshals the duration func (d *Duration) UnmarshalJSON(b []byte) error { var v interface{} if err := json.Unmarshal(b, &v); err != nil { return err } switch value := v.(type) { case float64: d.Duration = time.Duration(value) return nil case string: var err error d.Duration, err = time.ParseDuration(value) if err != nil { return err } return nil default: return errors.New("invalid duration") } } // WriteErrorResponse prepares and writes an error response i nJSON func WriteErrorResponse(errMsg string, httpStatus int, w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(httpStatus) err := json.NewEncoder(w).Encode(&ErrorResponse{ Message: errMsg, Code: httpStatus, }) if err != nil { http.Error(w, "failed handling request", http.StatusInternalServerError) } } // WriteError converts an error to an JSON error response. // If it is known internal error of type server.Error then it sets the messages from the error, a generic message otherwise func WriteError(ctx context.Context, err error, w http.ResponseWriter) { log.WithContext(ctx).Errorf("got a handler error: %s", err.Error()) errStatus, ok := status.FromError(err) httpStatus := http.StatusInternalServerError msg := "internal server error" if ok { switch errStatus.Type() { case status.UserAlreadyExists: httpStatus = http.StatusConflict case status.AlreadyExists: httpStatus = http.StatusConflict case status.PreconditionFailed: httpStatus = http.StatusPreconditionFailed case status.PermissionDenied: httpStatus = http.StatusForbidden case status.NotFound: httpStatus = http.StatusNotFound case status.Internal: httpStatus = http.StatusInternalServerError case status.InvalidArgument: httpStatus = http.StatusUnprocessableEntity case status.Unauthorized: httpStatus = http.StatusUnauthorized case status.BadRequest: httpStatus = http.StatusBadRequest default: } msg = strings.ToLower(err.Error()) } else { unhandledMSG := fmt.Sprintf("got unhandled error code, error: %s", err.Error()) log.WithContext(ctx).Error(unhandledMSG) } WriteErrorResponse(msg, httpStatus, w) }