package api import ( "encoding/json" "errors" "fmt" "io" "net/http" "browser.local/platform/dto" "browser.local/platform/repo" "browser.local/platform/service" "browser.local/platform/validator" ) const ( errorCodeForbidden = "forbidden" errorCodeBadRequest = "bad_request" errorCodeDuplicate = "duplicate_resource" errorCodeInternal = "internal_error" errorCodeMethodNotAllowed = "method_not_allowed" errorCodeNotFound = "not_found" errorCodeUnauthorized = "unauthorized" errorCodeValidation = "validation_failed" ) func decodeJSON[T any](r *http.Request) (T, error) { var value T decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil { return value, fmt.Errorf("decode json: %w", err) } var extra struct{} if err := decoder.Decode(&extra); err != io.EOF { return value, errors.New("decode json: multiple JSON values are not allowed") } return value, nil } func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) } func writeAPIError(w http.ResponseWriter, status int, code string, message string, details []string) { writeJSON(w, status, dto.ErrorResponse{ Code: code, Message: message, Details: details, }) } func writeMethodNotAllowed(w http.ResponseWriter, allow string) { w.Header().Set("Allow", allow) writeAPIError(w, http.StatusMethodNotAllowed, errorCodeMethodNotAllowed, "method not allowed", nil) } func writeServiceError(w http.ResponseWriter, err error) { var validationErr validator.ValidationError switch { case errors.As(err, &validationErr): writeAPIError(w, http.StatusBadRequest, errorCodeValidation, "validation failed", validationErr.Violations) case errors.Is(err, service.ErrUnauthorized): writeAPIError(w, http.StatusUnauthorized, errorCodeUnauthorized, "authentication required", nil) case errors.Is(err, service.ErrForbidden): writeAPIError(w, http.StatusForbidden, errorCodeForbidden, "account is not allowed to access this resource", nil) case errors.Is(err, repo.ErrDuplicate): writeAPIError(w, http.StatusConflict, errorCodeDuplicate, "resource already exists", nil) case errors.Is(err, repo.ErrNotFound): writeAPIError(w, http.StatusNotFound, errorCodeNotFound, "resource not found", nil) default: writeAPIError(w, http.StatusInternalServerError, errorCodeInternal, "internal server error", nil) } } func writeDecodeError(w http.ResponseWriter, err error) { writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid JSON request body", []string{err.Error()}) }