mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-11 03:47:18 +02:00
72 lines
2.6 KiB
Go
72 lines
2.6 KiB
Go
|
|
package apiv1
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/log"
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
)
|
||
|
|
|
||
|
|
const problemContentType = "application/problem+json"
|
||
|
|
|
||
|
|
func writeProblem(w http.ResponseWriter, r *http.Request, err error) {
|
||
|
|
status, code := classifyError(err)
|
||
|
|
detail := err.Error()
|
||
|
|
if status == http.StatusInternalServerError {
|
||
|
|
log.Error(r.Context(), "API v1: unexpected error", "path", r.URL.Path, err)
|
||
|
|
detail = ""
|
||
|
|
}
|
||
|
|
writeProblemStatus(w, r, status, code, detail)
|
||
|
|
}
|
||
|
|
|
||
|
|
func classifyError(err error) (int, ProblemCode) {
|
||
|
|
switch {
|
||
|
|
case errors.Is(err, model.ErrNotFound):
|
||
|
|
return http.StatusNotFound, ProblemCodeNotFound
|
||
|
|
case errors.Is(err, model.ErrNotAuthorized):
|
||
|
|
return http.StatusForbidden, ProblemCodeForbidden
|
||
|
|
case errors.Is(err, model.ErrInvalidAuth), errors.Is(err, model.ErrExpired):
|
||
|
|
return http.StatusUnauthorized, ProblemCodeUnauthorized
|
||
|
|
case errors.Is(err, model.ErrValidation):
|
||
|
|
return http.StatusBadRequest, ProblemCodeValidation
|
||
|
|
case errors.Is(err, model.ErrNotAvailable):
|
||
|
|
return http.StatusServiceUnavailable, ProblemCodeUnavailable
|
||
|
|
}
|
||
|
|
return http.StatusInternalServerError, ProblemCodeInternal
|
||
|
|
}
|
||
|
|
|
||
|
|
func writeProblemStatus(w http.ResponseWriter, r *http.Request, status int, code ProblemCode, detail string, fieldErrors ...ValidationError) {
|
||
|
|
p := Problem{Title: http.StatusText(status), Status: status, Code: code}
|
||
|
|
if detail != "" {
|
||
|
|
p.Detail = &detail
|
||
|
|
}
|
||
|
|
if len(fieldErrors) > 0 {
|
||
|
|
p.Errors = &fieldErrors
|
||
|
|
}
|
||
|
|
w.Header().Set("Content-Type", problemContentType)
|
||
|
|
w.WriteHeader(status)
|
||
|
|
if err := json.NewEncoder(w).Encode(p); err != nil {
|
||
|
|
log.Warn(r.Context(), "API v1: could not write problem response", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func bindingErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||
|
|
var fieldErrors []ValidationError
|
||
|
|
var required *RequiredParamError
|
||
|
|
var invalid *InvalidParamFormatError
|
||
|
|
var tooMany *TooManyValuesForParamError
|
||
|
|
var unmarshal *UnmarshalingParamError
|
||
|
|
switch {
|
||
|
|
case errors.As(err, &required):
|
||
|
|
fieldErrors = append(fieldErrors, ValidationError{Field: required.ParamName, Message: "is required"})
|
||
|
|
case errors.As(err, &invalid):
|
||
|
|
fieldErrors = append(fieldErrors, ValidationError{Field: invalid.ParamName, Message: invalid.Err.Error()})
|
||
|
|
case errors.As(err, &tooMany):
|
||
|
|
fieldErrors = append(fieldErrors, ValidationError{Field: tooMany.ParamName, Message: "expected a single value"})
|
||
|
|
case errors.As(err, &unmarshal):
|
||
|
|
fieldErrors = append(fieldErrors, ValidationError{Field: unmarshal.ParamName, Message: unmarshal.Err.Error()})
|
||
|
|
}
|
||
|
|
writeProblemStatus(w, r, http.StatusBadRequest, ProblemCodeValidation, err.Error(), fieldErrors...)
|
||
|
|
}
|