forked from jriou/coller
refactor: Use functional errors
Use well-defined server errors instead of hardcoded messages that could be slightly different and spread accross the code base. Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
parent
55de3afc71
commit
f721e56371
3 changed files with 137 additions and 80 deletions
|
@ -24,29 +24,30 @@ func (e APIErrorResponse) ToJSON() string {
|
|||
return fmt.Sprintf("{\"message\":\"could not serialize response to JSON\", \"error\":\"%v\"}", err)
|
||||
}
|
||||
|
||||
func apiError(level int, w http.ResponseWriter, logger *slog.Logger, msg string, err error) {
|
||||
func apiError(level int, w http.ResponseWriter, logger *slog.Logger, topLevelErr error, err error) {
|
||||
// Wrap error for logging
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s: %w", msg, err)
|
||||
err = fmt.Errorf("%v: %w", topLevelErr, err)
|
||||
}
|
||||
logger.Error(fmt.Sprintf("%v", err))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(level)
|
||||
fmt.Fprint(w, APIErrorResponse{
|
||||
Message: msg,
|
||||
Message: fmt.Sprintf("%v", topLevelErr),
|
||||
}.ToJSON())
|
||||
}
|
||||
|
||||
func APIError(w http.ResponseWriter, logger *slog.Logger, msg string, err error) {
|
||||
apiError(http.StatusInternalServerError, w, logger, msg, err)
|
||||
func APIError(w http.ResponseWriter, logger *slog.Logger, topLevelErr error, err error) {
|
||||
apiError(http.StatusInternalServerError, w, logger, topLevelErr, err)
|
||||
}
|
||||
|
||||
func APIErrorNotFound(w http.ResponseWriter, logger *slog.Logger, msg string, err error) {
|
||||
apiError(http.StatusNotFound, w, logger, msg, err)
|
||||
func APIErrorNotFound(w http.ResponseWriter, logger *slog.Logger, topLevelErr error, err error) {
|
||||
apiError(http.StatusNotFound, w, logger, topLevelErr, err)
|
||||
}
|
||||
|
||||
func APIErrorBadRequest(w http.ResponseWriter, logger *slog.Logger, msg string, err error) {
|
||||
apiError(http.StatusBadRequest, w, logger, msg, err)
|
||||
func APIErrorBadRequest(w http.ResponseWriter, logger *slog.Logger, topLevelErr error, err error) {
|
||||
apiError(http.StatusBadRequest, w, logger, topLevelErr, err)
|
||||
}
|
||||
|
||||
func HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -61,6 +62,10 @@ type CreateNoteHandler struct {
|
|||
allowNoEncryption bool
|
||||
}
|
||||
|
||||
func (h *CreateNoteHandler) Name() string {
|
||||
return "CreateNoteHandler"
|
||||
}
|
||||
|
||||
type CreateNotePayload struct {
|
||||
Content string `json:"content"`
|
||||
Password string `json:"password"`
|
||||
|
@ -78,7 +83,7 @@ type CreateNoteResponse struct {
|
|||
func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
logger := h.logger.With("handler", "CreateNoteHandler")
|
||||
logger := h.logger.With("handler", h.Name())
|
||||
|
||||
bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize)
|
||||
defer r.Body.Close()
|
||||
|
@ -86,30 +91,30 @@ func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
var body CreateNotePayload
|
||||
err := json.NewDecoder(bodyReader).Decode(&body)
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not decode payload", err)
|
||||
APIError(w, logger, ErrCouldNotDecodePayload, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !h.allowNoEncryption && !body.Encrypted {
|
||||
APIError(w, logger, "encryption is mandatory", nil)
|
||||
APIError(w, logger, ErrEncryptionRequired, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !h.allowClientEncryptionKey && body.EncryptionKey != "" {
|
||||
APIError(w, logger, "client encryption key is not allowed", nil)
|
||||
APIError(w, logger, ErrClientEncryptionKeyNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := internal.Decode(body.Content)
|
||||
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not decode content", err)
|
||||
APIError(w, logger, ErrCouldNotDecodeContent, err)
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.db.Create(content, body.Password, body.EncryptionKey, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language)
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not create note", err)
|
||||
APIError(w, logger, ErrCouldNotCreateNote, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -122,6 +127,10 @@ type GetNoteHandler struct {
|
|||
db *Database
|
||||
}
|
||||
|
||||
func (h *GetNoteHandler) Name() string {
|
||||
return "GetNoteHandler"
|
||||
}
|
||||
|
||||
func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
|
@ -129,14 +138,14 @@ func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
note, err := h.db.Get(id)
|
||||
|
||||
logger := h.logger.With("handler", "CreateNoteHandler", "note_id", id)
|
||||
logger := h.logger.With("handler", h.Name(), "note_id", id)
|
||||
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not find note", err)
|
||||
APIError(w, logger, ErrCouldNotFindNote, err)
|
||||
} else if note == nil {
|
||||
APIErrorNotFound(w, logger, "note does not exist", err)
|
||||
APIErrorNotFound(w, logger, ErrNoteDoesNotExist, err)
|
||||
} else if note.PasswordHash != nil {
|
||||
APIErrorBadRequest(w, logger, "note is password protected", err)
|
||||
APIErrorBadRequest(w, logger, ErrNoteIsPasswordProtected, err)
|
||||
} else {
|
||||
if note.Encrypted {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
@ -157,13 +166,17 @@ type GetProtectedNotePayload struct {
|
|||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (h *GetProtectedNoteHandler) Name() string {
|
||||
return "GetProtectedNoteHandler"
|
||||
}
|
||||
|
||||
func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
logger := h.logger.With("handler", "GetProtectedNoteHandler", "note_id", id)
|
||||
logger := h.logger.With("handler", h.Name(), "note_id", id)
|
||||
|
||||
bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize)
|
||||
defer r.Body.Close()
|
||||
|
@ -171,14 +184,14 @@ func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
|
|||
var body GetProtectedNotePayload
|
||||
err := json.NewDecoder(bodyReader).Decode(&body)
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not decode payload", err)
|
||||
APIError(w, logger, ErrCouldNotDecodePayload, err)
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.db.Get(id)
|
||||
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not find note", err)
|
||||
APIError(w, logger, ErrCouldNotFindNote, err)
|
||||
return
|
||||
} else if note == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
@ -188,15 +201,15 @@ func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
|
|||
if body.EncryptionKey != "" && note.Encrypted {
|
||||
note.Content, err = internal.Decrypt(note.Content, body.EncryptionKey)
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not decrypt note", err)
|
||||
APIError(w, logger, ErrCouldNotDecryptNote, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if body.Password == "" && (note.PasswordHash != nil || len(note.PasswordHash) > 0) {
|
||||
if len(note.PasswordHash) > 0 {
|
||||
err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(body.Password))
|
||||
if err != nil {
|
||||
APIError(w, logger, "could not validate password", err)
|
||||
APIError(w, logger, ErrInvalidPassword, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue