diff --git a/server.go b/server.go index d8de896..7b4b669 100644 --- a/server.go +++ b/server.go @@ -45,6 +45,45 @@ func (s *server) defaultErrorHandler(err error, c echo.Context) { } } +func (s *server) response(c echo.Context, status int, data interface{}) error { + return c.JSON(status, data) +} + +func (s *server) responseOK(c echo.Context, data interface{}) error { + return s.response(c, http.StatusOK, data) +} + +func (s *server) responseCreated(c echo.Context, data interface{}) error { + return s.response(c, http.StatusCreated, data) +} + +func (s *server) responseNoContent(c echo.Context) error { + return c.NoContent(http.StatusNoContent) +} + +func (s *server) error(c echo.Context, status int, err error) error { + type genericError struct { + Error string `json:"error"` + } + return c.JSON(status, genericError{Error: err.Error()}) +} + +func (s *server) errorBadRequest(c echo.Context, err error) error { + return s.error(c, http.StatusBadRequest, err) +} + +func (s *server) errorNotFound(c echo.Context, err error) error { + s.logger.Warn("client error", zap.Error(err)) + return s.error(c, http.StatusNotFound, err) +} + +func (s *server) errorInternal(c echo.Context, err error) error { + s.logger.Error("server error", zap.Error(err)) + // Overwrite error so not to leak internal errors to clients + err = fmt.Errorf("internal server error") + return s.error(c, http.StatusInternalServerError, err) +} + func (s *server) handleInitMultisig() echo.HandlerFunc { type member struct{} @@ -74,9 +113,7 @@ func (s *server) handleInitMultisig() echo.HandlerFunc { in := &input{} if err := c.Bind(in); err != nil { - return c.JSON(http.StatusBadRequest, map[string]interface{}{ - "error": err.Error(), - }) + return s.errorBadRequest(c, err) } // TODO: validate input! @@ -84,17 +121,13 @@ func (s *server) handleInitMultisig() echo.HandlerFunc { // Check if input contains manager's username managerNickname := s.config.ManagementCredentials.Username() if _, ok := in.Members[managerNickname]; ok { - return c.JSON(http.StatusBadRequest, map[string]interface{}{ - "error": fmt.Sprintf("username %s already taken", managerNickname), - }) + return s.errorBadRequest(c, fmt.Errorf("username `%s` already taken", managerNickname)) } in.Members[managerNickname] = member{} topic, err := uuid.GenerateUUID() if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": "failed to generate UUID", - }) + return s.errorInternal(c, err) } memberSecrets := map[string]string{} @@ -102,9 +135,7 @@ func (s *server) handleInitMultisig() echo.HandlerFunc { // TODO: put password length in config. token, err := password.Generate(18, 6, 0, false, false) if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": "failed to generate a token", - }) + return s.errorInternal(c, err) } member := members.Member{ @@ -121,9 +152,7 @@ func (s *server) handleInitMultisig() echo.HandlerFunc { } encoded, err := encodeSecret(secret) if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": "failed to encode secret data", - }) + return s.errorInternal(c, err) } memberSecrets[nickname] = encoded } @@ -136,7 +165,7 @@ func (s *server) handleInitMultisig() echo.HandlerFunc { Secrets: memberSecrets, } - return c.JSON(http.StatusCreated, out) + return s.responseCreated(c, out) } } @@ -152,19 +181,21 @@ func (s *server) handleListMessages() echo.HandlerFunc { topic := c.Param("topic") offset, err := offsetToNumber(c.QueryParam("offset")) if err != nil { - return c.JSON(http.StatusBadRequest, map[string]interface{}{ - "error": "offset is not a number", - }) + return s.errorBadRequest(c, fmt.Errorf("offset is not a number")) } messages, err := s.messageLog.List(topic, int(offset)) if err != nil { - return c.JSON(http.StatusNotFound, map[string]interface{}{ - "error": err.Error(), - }) + switch err.(type) { + case *messagelog.ErrInvalidMessage: + return s.errorBadRequest(c, err) + case *messagelog.ErrTopicNotFound: + return s.errorNotFound(c, err) + } + return s.errorInternal(c, err) } - return c.JSON(http.StatusOK, messages) + return s.responseOK(c, messages) } } @@ -173,27 +204,27 @@ func (s *server) handlePushMessage() echo.HandlerFunc { message := messagelog.Message{} if err := c.Bind(&message); err != nil { - return c.JSON(http.StatusBadRequest, map[string]interface{}{ - "error": "invalid message format", - }) + return s.errorBadRequest(c, fmt.Errorf("invalid message format")) } member, ok := c.Get("member").(members.Member) if !ok { - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": "internal server error", - }) + return s.errorInternal(c, fmt.Errorf("missing member information from the middleware")) } message.Sender = member.Nickname topic := c.Param("topic") if err := s.messageLog.Push(topic, message); err != nil { - return c.JSON(http.StatusNotFound, map[string]interface{}{ - "error": err.Error(), - }) + switch err.(type) { + case *messagelog.ErrInvalidMessage: + return s.errorBadRequest(c, err) + case *messagelog.ErrTopicNotFound: + return s.errorNotFound(c, err) + } + return s.errorInternal(c, err) } - return c.String(http.StatusNoContent, "") + return s.responseNoContent(c) } }