From f50f03fe6f357b98ebef6a97833503e121931149 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 26 Jan 2017 23:15:05 +0100 Subject: [PATCH] Allow data store to set the status code for errors Closes https://github.com/tus/tusd/issues/77 --- metrics.go | 20 +++++------- unrouted_handler.go | 75 +++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/metrics.go b/metrics.go index 92df351..177d965 100644 --- a/metrics.go +++ b/metrics.go @@ -31,11 +31,14 @@ func (m Metrics) incRequestsTotal(method string) { // incErrorsTotal increases the counter for this error atomically by one. func (m Metrics) incErrorsTotal(err error) { msg := err.Error() - if _, ok := ErrStatusCodes[err]; !ok { - msg = "system error" - } - atomic.AddUint64(m.ErrorsTotal[msg], 1) + if addr, ok := m.ErrorsTotal[msg]; ok { + atomic.AddUint64(addr, 1) + } else { + addr := new(uint64) + *addr = 1 + m.ErrorsTotal[msg] = addr + } } // incBytesReceived increases the number of received bytes atomically be the @@ -78,13 +81,6 @@ func newMetrics() Metrics { } func newErrorsTotalMap() map[string]*uint64 { - m := make(map[string]*uint64, len(ErrStatusCodes)+1) - - for err := range ErrStatusCodes { - m[err.Error()] = new(uint64) - } - - m["system error"] = new(uint64) - + m := make(map[string]*uint64, 20) return m } diff --git a/unrouted_handler.go b/unrouted_handler.go index fde83c4..74bc6e9 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -20,39 +20,46 @@ var ( reForwardedProto = regexp.MustCompile(`proto=(https?)`) ) -var ( - ErrUnsupportedVersion = errors.New("unsupported version") - ErrMaxSizeExceeded = errors.New("maximum size exceeded") - ErrInvalidContentType = errors.New("missing or invalid Content-Type header") - ErrInvalidUploadLength = errors.New("missing or invalid Upload-Length header") - ErrInvalidOffset = errors.New("missing or invalid Upload-Offset header") - ErrNotFound = errors.New("upload not found") - ErrFileLocked = errors.New("file currently locked") - ErrMismatchOffset = errors.New("mismatched offset") - ErrSizeExceeded = errors.New("resource's size exceeded") - ErrNotImplemented = errors.New("feature not implemented") - ErrUploadNotFinished = errors.New("one of the partial uploads is not finished") - ErrInvalidConcat = errors.New("invalid Upload-Concat header") - ErrModifyFinal = errors.New("modifying a final upload is not allowed") -) - -// HTTP status codes sent in the response when the specific error is returned. -var ErrStatusCodes = map[error]int{ - ErrUnsupportedVersion: http.StatusPreconditionFailed, - ErrMaxSizeExceeded: http.StatusRequestEntityTooLarge, - ErrInvalidContentType: http.StatusBadRequest, - ErrInvalidUploadLength: http.StatusBadRequest, - ErrInvalidOffset: http.StatusBadRequest, - ErrNotFound: http.StatusNotFound, - ErrFileLocked: 423, // Locked (WebDAV) (RFC 4918) - ErrMismatchOffset: http.StatusConflict, - ErrSizeExceeded: http.StatusRequestEntityTooLarge, - ErrNotImplemented: http.StatusNotImplemented, - ErrUploadNotFinished: http.StatusBadRequest, - ErrInvalidConcat: http.StatusBadRequest, - ErrModifyFinal: http.StatusForbidden, +// HTTPError represents an error with an additional status code attached +// which may be used when this error is sent in a HTTP response. +// See the net/http package for standardized status codes. +type HTTPError interface { + error + StatusCode() int } +type httpError struct { + error + statusCode int +} + +func (err httpError) StatusCode() int { + return err.statusCode +} + +// NewHTTPError adds the given status code to the provided error and returns +// the new error instance. The status code may be used in corresponding HTTP +// responses. See the net/http package for standardized status codes. +func NewHTTPError(err error, statusCode int) HTTPError { + return httpError{err, statusCode} +} + +var ( + ErrUnsupportedVersion = NewHTTPError(errors.New("unsupported version"), http.StatusPreconditionFailed) + ErrMaxSizeExceeded = NewHTTPError(errors.New("maximum size exceeded"), http.StatusRequestEntityTooLarge) + ErrInvalidContentType = NewHTTPError(errors.New("missing or invalid Content-Type header"), http.StatusBadRequest) + ErrInvalidUploadLength = NewHTTPError(errors.New("missing or invalid Upload-Length header"), http.StatusBadRequest) + ErrInvalidOffset = NewHTTPError(errors.New("missing or invalid Upload-Offset header"), http.StatusBadRequest) + ErrNotFound = NewHTTPError(errors.New("upload not found"), http.StatusNotFound) + ErrFileLocked = NewHTTPError(errors.New("file currently locked"), 423) // Locked (WebDAV) (RFC 4918) + ErrMismatchOffset = NewHTTPError(errors.New("mismatched offset"), http.StatusConflict) + ErrSizeExceeded = NewHTTPError(errors.New("resource's size exceeded"), http.StatusRequestEntityTooLarge) + ErrNotImplemented = NewHTTPError(errors.New("feature not implemented"), http.StatusNotImplemented) + ErrUploadNotFinished = NewHTTPError(errors.New("one of the partial uploads is not finished"), http.StatusBadRequest) + ErrInvalidConcat = NewHTTPError(errors.New("invalid Upload-Concat header"), http.StatusBadRequest) + ErrModifyFinal = NewHTTPError(errors.New("modifying a final upload is not allowed"), http.StatusForbidden) +) + // UnroutedHandler exposes methods to handle requests as part of the tus protocol, // such as PostFile, HeadFile, PatchFile and DelFile. In addition the GetFile method // is provided which is, however, not part of the specification. @@ -593,9 +600,9 @@ func (handler *UnroutedHandler) sendError(w http.ResponseWriter, r *http.Request err = ErrNotFound } - status, ok := ErrStatusCodes[err] - if !ok { - status = 500 + status := 500 + if statusErr, ok := err.(HTTPError); ok { + status = statusErr.StatusCode() } reason := err.Error() + "\n"