diff --git a/pkg/models/error.go b/pkg/models/error.go
index 5ab79e85c..61834d23d 100644
--- a/pkg/models/error.go
+++ b/pkg/models/error.go
@@ -17,6 +17,7 @@
package models
import (
+ "encoding/json"
"fmt"
"net/http"
"strings"
@@ -110,6 +111,26 @@ func (err ValidationHTTPError) Error() string {
return theErr.Error()
}
+// MarshalJSON implements json.Marshaler to ensure InvalidFields is included in the JSON response.
+// This is needed because Echo's DefaultHTTPErrorHandler converts error types to {"message": err.Error()},
+// losing structured data. By implementing json.Marshaler, Echo will serialize the full struct.
+func (err ValidationHTTPError) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ InvalidFields []string `json:"invalid_fields"`
+ }{
+ Code: err.Code,
+ Message: err.Message,
+ InvalidFields: err.InvalidFields,
+ })
+}
+
+// GetHTTPCode returns the HTTP status code for this error.
+func (err ValidationHTTPError) GetHTTPCode() int {
+ return err.HTTPCode
+}
+
func InvalidFieldError(fields []string) error {
return InvalidFieldErrorWithMessage(fields, "Invalid Data")
}
@@ -153,6 +174,42 @@ func (err ErrInvalidTimezone) HTTPError() web.HTTPError {
}
}
+// ErrInvalidModel represents an error where the request body could not be parsed
+type ErrInvalidModel struct {
+ Message string
+ Err error // Original error for unwrapping
+}
+
+// IsErrInvalidModel checks if an error is ErrInvalidModel.
+func IsErrInvalidModel(err error) bool {
+ _, ok := err.(ErrInvalidModel)
+ return ok
+}
+
+func (err ErrInvalidModel) Error() string {
+ if err.Message != "" {
+ return fmt.Sprintf("Invalid model provided: %s", err.Message)
+ }
+ return "Invalid model provided."
+}
+
+// Unwrap returns the wrapped error for use with errors.Is/errors.As
+func (err ErrInvalidModel) Unwrap() error {
+ return err.Err
+}
+
+// ErrCodeInvalidModel holds the unique world-error code of this error
+const ErrCodeInvalidModel = 2004
+
+// HTTPError holds the http error description
+func (err ErrInvalidModel) HTTPError() web.HTTPError {
+ return web.HTTPError{
+ HTTPCode: http.StatusBadRequest,
+ Code: ErrCodeInvalidModel,
+ Message: err.Error(),
+ }
+}
+
// ===========
// Project errors
// ===========
diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go
index 6f57c7204..96f287d36 100644
--- a/pkg/modules/auth/openid/openid.go
+++ b/pkg/modules/auth/openid/openid.go
@@ -34,7 +34,6 @@ import (
"code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/coreos/go-oidc/v3/oidc"
petname "github.com/dustinkirkland/golang-petname"
@@ -140,12 +139,12 @@ func HandleCallback(c echo.Context) error {
"details": detailedErr.Details,
})
}
- return handler.HandleHTTPError(err)
+ return err
}
cl, err := getClaims(provider, oauthToken, idToken)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -156,21 +155,21 @@ func HandleCallback(c echo.Context) error {
if err != nil {
_ = s.Rollback()
log.Errorf("Error creating new user for provider %s: %v", provider.Name, err)
- return handler.HandleHTTPError(err)
+ return err
}
teamData := getTeamDataFromToken(cl.VikunjaGroups, provider)
err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, "OIDC")
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
log.Errorf("Error creating new team for provider %s: %v", provider.Name, err)
- return handler.HandleHTTPError(err)
+ return err
}
// Create token
diff --git a/pkg/modules/background/handler/background.go b/pkg/modules/background/handler/background.go
index fbb1ab3c2..dd8a2fa7b 100644
--- a/pkg/modules/background/handler/background.go
+++ b/pkg/modules/background/handler/background.go
@@ -43,7 +43,6 @@ import (
"code.vikunja.io/api/pkg/modules/background/unsplash"
"code.vikunja.io/api/pkg/modules/background/upload"
"code.vikunja.io/api/pkg/web"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/bbrks/go-blurhash"
"github.com/gabriel-vasile/mimetype"
@@ -138,7 +137,7 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
p := bp.Provider()
@@ -153,13 +152,13 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
err = p.Set(s, image, project, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = project.ReadOne(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, project)
@@ -185,7 +184,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Get + upload the image
@@ -205,7 +204,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
mime, err := mimetype.DetectReader(srcf)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if !strings.HasPrefix(mime.String(), "image") {
_ = s.Rollback()
@@ -233,18 +232,18 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Unsupported image format. Allowed: " + strings.Join(allowedImageMimes, ",")})
}
- return handler.HandleHTTPError(err)
+ return err
}
err = project.ReadOne(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, project)
@@ -314,7 +313,7 @@ func checkProjectBackgroundRights(s *xorm.Session, c echo.Context) (project *mod
can, _, err := project.CanRead(s, auth)
if err != nil {
_ = s.Rollback()
- return nil, auth, handler.HandleHTTPError(err)
+ return nil, auth, err
}
if !can {
_ = s.Rollback()
@@ -359,12 +358,12 @@ func GetProjectBackground(c echo.Context) error {
}
if err := bgFile.LoadFileByID(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
stat, err := bgFile.File.Stat()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Unsplash requires pingbacks as per their api usage guidelines.
@@ -374,7 +373,7 @@ func GetProjectBackground(c echo.Context) error {
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files
diff --git a/pkg/modules/background/unsplash/proxy.go b/pkg/modules/background/unsplash/proxy.go
index 22a34133a..c6d01d0bd 100644
--- a/pkg/modules/background/unsplash/proxy.go
+++ b/pkg/modules/background/unsplash/proxy.go
@@ -21,7 +21,6 @@ import (
"net/http"
"strings"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -56,7 +55,7 @@ func unsplashImage(url string, c echo.Context) error {
func ProxyUnsplashImage(c echo.Context) error {
photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
pingbackByPhotoID(photo.ID)
return unsplashImage(photo.Urls.Raw, c)
@@ -76,7 +75,7 @@ func ProxyUnsplashImage(c echo.Context) error {
func ProxyUnsplashThumb(c echo.Context) error {
photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
pingbackByPhotoID(photo.ID)
return unsplashImage("https://images.unsplash.com/"+getImageID(photo.Urls.Raw)+"?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ", c)
diff --git a/pkg/modules/migration/handler/common.go b/pkg/modules/migration/handler/common.go
index aea3b264b..2fefe7ece 100644
--- a/pkg/modules/migration/handler/common.go
+++ b/pkg/modules/migration/handler/common.go
@@ -21,19 +21,18 @@ import (
"code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
func status(ms migration.MigratorName, c echo.Context) error {
user, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
status, err := migration.GetMigrationStatus(ms, user)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, status)
diff --git a/pkg/modules/migration/handler/handler.go b/pkg/modules/migration/handler/handler.go
index 830f8f0f4..c71be491c 100644
--- a/pkg/modules/migration/handler/handler.go
+++ b/pkg/modules/migration/handler/handler.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -65,12 +64,12 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
// Get the user from context
user, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
stats, err := migration.GetMigrationStatus(ms, user)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
if !stats.StartedAt.IsZero() && stats.FinishedAt.IsZero() {
@@ -92,7 +91,7 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
User: user,
})
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "Migration was started successfully."})
diff --git a/pkg/modules/migration/handler/handler_file.go b/pkg/modules/migration/handler/handler_file.go
index 6a12613b0..97138494f 100644
--- a/pkg/modules/migration/handler/handler_file.go
+++ b/pkg/modules/migration/handler/handler_file.go
@@ -22,7 +22,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -44,7 +43,7 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
// Get the user from context
user, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
file, err := c.FormFile("import")
@@ -59,18 +58,18 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
m, err := migration.StartMigration(ms, user)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
// Do the migration
err = ms.Migrate(user, src, file.Size)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
err = migration.FinishMigration(m)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
diff --git a/pkg/routes/api/v1/avatar.go b/pkg/routes/api/v1/avatar.go
index 3a40cdd50..0f666ff0b 100644
--- a/pkg/routes/api/v1/avatar.go
+++ b/pkg/routes/api/v1/avatar.go
@@ -25,7 +25,6 @@ import (
"code.vikunja.io/api/pkg/modules/avatar/empty"
"code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"io"
"net/http"
@@ -58,7 +57,7 @@ func GetAvatar(c echo.Context) error {
u, err := user.GetUserWithEmail(s, &user.User{Username: username})
if err != nil && !user.IsErrUserDoesNotExist(err) {
log.Errorf("Error getting user for avatar: %v", err)
- return handler.HandleHTTPError(err)
+ return err
}
found := err == nil || !user.IsErrUserDoesNotExist(err)
@@ -75,7 +74,7 @@ func GetAvatar(c echo.Context) error {
sizeInt, err = strconv.ParseInt(size, 10, 64)
if err != nil {
log.Errorf("Error parsing size: %v", err)
- return handler.HandleHTTPError(err)
+ return models.ErrInvalidModel{Message: "Invalid size parameter"}
}
}
if sizeInt > config.ServiceMaxAvatarSize.GetInt64() {
@@ -86,7 +85,7 @@ func GetAvatar(c echo.Context) error {
a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt)
if err != nil {
log.Errorf("Error getting avatar for user %d: %v", u.ID, err)
- return handler.HandleHTTPError(err)
+ return err
}
return c.Blob(http.StatusOK, mimeType, a)
@@ -112,12 +111,12 @@ func UploadAvatar(c echo.Context) (err error) {
uc, err := user.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
u, err := user.GetUserByID(s, uc.ID)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Get + upload the image
@@ -137,7 +136,7 @@ func UploadAvatar(c echo.Context) (err error) {
mime, err := mimetype.DetectReader(src)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if !strings.HasPrefix(mime.String(), "image") {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
@@ -148,12 +147,12 @@ func UploadAvatar(c echo.Context) (err error) {
err = upload.StoreAvatarFile(s, u, src)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
avatar.FlushAllCaches(u)
diff --git a/pkg/routes/api/v1/link_sharing_auth.go b/pkg/routes/api/v1/link_sharing_auth.go
index e233d9bdd..e9c4b0992 100644
--- a/pkg/routes/api/v1/link_sharing_auth.go
+++ b/pkg/routes/api/v1/link_sharing_auth.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -56,7 +55,7 @@ func AuthenticateLinkShare(c echo.Context) error {
sh := &LinkShareAuth{}
err := c.Bind(sh)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -64,19 +63,19 @@ func AuthenticateLinkShare(c echo.Context) error {
share, err := models.GetLinkShareByHash(s, sh.Hash)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
if share.SharingType == models.SharingTypeWithPassword {
err := models.VerifyLinkSharePassword(share, sh.Password)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
}
t, err := auth.NewLinkShareJWTAuthtoken(share)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
share.Password = ""
diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go
index b1bdd286a..44802c0c5 100644
--- a/pkg/routes/api/v1/login.go
+++ b/pkg/routes/api/v1/login.go
@@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/modules/auth/ldap"
"code.vikunja.io/api/pkg/modules/keyvalue"
user2 "code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
@@ -58,7 +57,7 @@ func Login(c echo.Context) (err error) {
user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString())
if err != nil && !user2.IsErrWrongUsernameOrPassword(err) {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
}
@@ -67,25 +66,25 @@ func Login(c echo.Context) (err error) {
user, err = user2.CheckUserCredentials(s, &u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
}
if user.Status == user2.StatusDisabled {
_ = s.Rollback()
- return handler.HandleHTTPError(&user2.ErrAccountDisabled{UserID: user.ID})
+ return &user2.ErrAccountDisabled{UserID: user.ID}
}
totpEnabled, err := user2.TOTPEnabledForUser(s, user)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if totpEnabled {
if u.TOTPPasscode == "" {
_ = s.Rollback()
- return handler.HandleHTTPError(user2.ErrInvalidTOTPPasscode{})
+ return user2.ErrInvalidTOTPPasscode{}
}
_, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{
@@ -97,7 +96,7 @@ func Login(c echo.Context) (err error) {
user2.HandleFailedTOTPAuth(s, user)
}
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
}
@@ -110,7 +109,7 @@ func Login(c echo.Context) (err error) {
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Create token
@@ -141,12 +140,12 @@ func RenewToken(c echo.Context) (err error) {
err := share.ReadOne(s, share)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
t, err := auth.NewLinkShareJWTAuthtoken(share)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, auth.Token{Token: t})
}
@@ -154,18 +153,18 @@ func RenewToken(c echo.Context) (err error) {
u, err := user2.GetUserFromClaims(claims)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
var long bool
diff --git a/pkg/routes/api/v1/task_attachment.go b/pkg/routes/api/v1/task_attachment.go
index 7ffd08d1c..79d0fb797 100644
--- a/pkg/routes/api/v1/task_attachment.go
+++ b/pkg/routes/api/v1/task_attachment.go
@@ -27,11 +27,33 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
auth2 "code.vikunja.io/api/pkg/modules/auth"
- "code.vikunja.io/api/pkg/web/handler"
+ "code.vikunja.io/api/pkg/web"
"github.com/labstack/echo/v4"
)
+// attachmentUploadError represents a structured error for attachment upload failures
+type attachmentUploadError struct {
+ Code int `json:"code,omitempty"`
+ Message string `json:"message"`
+}
+
+// toAttachmentUploadError converts an error to a structured attachmentUploadError
+func toAttachmentUploadError(err error) attachmentUploadError {
+ // Try to get structured error info from HTTPErrorProcessor
+ if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
+ errDetails := httpErr.HTTPError()
+ return attachmentUploadError{
+ Code: errDetails.Code,
+ Message: errDetails.Message,
+ }
+ }
+ // Fall back to just the error message
+ return attachmentUploadError{
+ Message: err.Error(),
+ }
+}
+
// UploadTaskAttachment handles everything needed for the upload of a task attachment
// @Summary Upload a task attachment
// @Description Upload a task attachment. You can pass multiple files with the files form param.
@@ -56,7 +78,7 @@ func UploadTaskAttachment(c echo.Context) error {
// Permissions check
auth, err := auth2.GetAuthFromClaims(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -65,7 +87,7 @@ func UploadTaskAttachment(c echo.Context) error {
can, err := taskAttachment.CanCreate(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if !can {
return echo.ErrForbidden
@@ -78,11 +100,11 @@ func UploadTaskAttachment(c echo.Context) error {
if errors.Is(err, http.ErrNotMultipart) {
return echo.NewHTTPError(http.StatusBadRequest, "No multipart form provided")
}
- return handler.HandleHTTPError(err)
+ return err
}
type result struct {
- Errors []*echo.HTTPError `json:"errors"`
+ Errors []attachmentUploadError `json:"errors"`
Success []*models.TaskAttachment `json:"success"`
}
r := &result{}
@@ -95,14 +117,14 @@ func UploadTaskAttachment(c echo.Context) error {
f, err := file.Open()
if err != nil {
- r.Errors = append(r.Errors, handler.HandleHTTPError(err))
+ r.Errors = append(r.Errors, toAttachmentUploadError(err))
continue
}
defer f.Close()
err = ta.NewAttachment(s, f, file.Filename, uint64(file.Size), auth)
if err != nil {
- r.Errors = append(r.Errors, handler.HandleHTTPError(err))
+ r.Errors = append(r.Errors, toAttachmentUploadError(err))
continue
}
r.Success = append(r.Success, ta)
@@ -110,7 +132,7 @@ func UploadTaskAttachment(c echo.Context) error {
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, r)
@@ -140,7 +162,7 @@ func GetTaskAttachment(c echo.Context) error {
// Permissions check
auth, err := auth2.GetAuthFromClaims(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -149,7 +171,7 @@ func GetTaskAttachment(c echo.Context) error {
can, _, err := taskAttachment.CanRead(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if !can {
return echo.ErrForbidden
@@ -159,7 +181,7 @@ func GetTaskAttachment(c echo.Context) error {
err = taskAttachment.ReadOne(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// If the preview query parameter is set, get the preview (cached or generate)
@@ -175,12 +197,12 @@ func GetTaskAttachment(c echo.Context) error {
err = taskAttachment.File.LoadFileByID()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if config.FilesType.GetString() == "s3" {
// s3 files cannot use http.ServeContent as it requires a Seekable file
@@ -193,7 +215,7 @@ func GetTaskAttachment(c echo.Context) error {
// Stream the file content directly to the response
_, err = io.Copy(c.Response().Writer, taskAttachment.File.File)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
} else {
http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File)
diff --git a/pkg/routes/api/v1/user_caldav_token.go b/pkg/routes/api/v1/user_caldav_token.go
index b96176e8a..5ebccee63 100644
--- a/pkg/routes/api/v1/user_caldav_token.go
+++ b/pkg/routes/api/v1/user_caldav_token.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -43,12 +42,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
u, err := user.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
token, err := user.GenerateNewCaldavToken(u)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusCreated, token)
@@ -69,12 +68,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
func GetCaldavTokens(c echo.Context) error {
u, err := user.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
tokens, err := user.GetCaldavTokens(u)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, tokens)
@@ -95,17 +94,17 @@ func GetCaldavTokens(c echo.Context) error {
func DeleteCaldavToken(c echo.Context) error {
u, err := user.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
err = user.DeleteCaldavTokenByID(u, id)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, &models.Message{Message: "The token was deleted successfully."})
diff --git a/pkg/routes/api/v1/user_confirm_email.go b/pkg/routes/api/v1/user_confirm_email.go
index b2cccc81d..259898886 100644
--- a/pkg/routes/api/v1/user_confirm_email.go
+++ b/pkg/routes/api/v1/user_confirm_email.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -51,12 +50,12 @@ func UserConfirmEmail(c echo.Context) error {
err := user.ConfirmEmail(s, &emailConfirm)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "The email was confirmed successfully."})
diff --git a/pkg/routes/api/v1/user_deletion.go b/pkg/routes/api/v1/user_deletion.go
index 4e7337af4..4101ad8a4 100644
--- a/pkg/routes/api/v1/user_deletion.go
+++ b/pkg/routes/api/v1/user_deletion.go
@@ -22,7 +22,6 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -53,13 +52,13 @@ func UserRequestDeletion(c echo.Context) error {
err := s.Begin()
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
u, err := user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if u.IsLocalUser() {
@@ -76,20 +75,20 @@ func UserRequestDeletion(c echo.Context) error {
err = user.CheckUserPassword(u, deletionRequest.Password)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err).SetInternal(err)
+ return err
}
}
err = user.RequestDeletion(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested deletion."})
@@ -122,25 +121,25 @@ func UserConfirmDeletion(c echo.Context) error {
err = s.Begin()
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
u, err := user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = user.ConfirmDeletion(s, u, deleteConfirmation.Token)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."})
@@ -164,13 +163,13 @@ func UserCancelDeletion(c echo.Context) error {
err := s.Begin()
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
u, err := user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if u.IsLocalUser() {
@@ -187,20 +186,20 @@ func UserCancelDeletion(c echo.Context) error {
err = user.CheckUserPassword(u, deletionRequest.Password)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
}
err = user.CancelDeletion(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."})
diff --git a/pkg/routes/api/v1/user_export.go b/pkg/routes/api/v1/user_export.go
index 807c8a66e..cd8858f44 100644
--- a/pkg/routes/api/v1/user_export.go
+++ b/pkg/routes/api/v1/user_export.go
@@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"xorm.io/xorm"
)
@@ -37,13 +36,13 @@ func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err erro
err = s.Begin()
if err != nil {
- return nil, nil, handler.HandleHTTPError(err)
+ return nil, nil, err
}
u, err = user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
- return nil, nil, handler.HandleHTTPError(err)
+ return nil, nil, err
}
// Users authenticated with a third-party are unable to provide their password.
@@ -64,7 +63,7 @@ func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err erro
err = user.CheckUserPassword(u, pass.Password)
if err != nil {
_ = s.Rollback()
- return nil, nil, handler.HandleHTTPError(err)
+ return nil, nil, err
}
return
@@ -92,13 +91,13 @@ func RequestUserDataExport(c echo.Context) error {
})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
@@ -125,7 +124,7 @@ func DownloadUserDataExport(c echo.Context) error {
err = s.Commit()
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Check if user has an export file
@@ -141,14 +140,14 @@ func DownloadUserDataExport(c echo.Context) error {
if files.IsErrFileDoesNotExist(err) {
return exportNotFoundError
}
- return handler.HandleHTTPError(err)
+ return err
}
err = exportFile.LoadFileByID()
if err != nil {
if os.IsNotExist(err) {
return exportNotFoundError
}
- return handler.HandleHTTPError(err)
+ return err
}
http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File)
@@ -175,7 +174,7 @@ func GetUserExportStatus(c echo.Context) error {
u, err := user.GetCurrentUserFromDB(s, c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
if u.ExportFileID == 0 {
@@ -184,7 +183,7 @@ func GetUserExportStatus(c echo.Context) error {
exportFile := &files.File{ID: u.ExportFileID}
if err := exportFile.LoadFileMetaByID(); err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
status := UserExportStatus{
diff --git a/pkg/routes/api/v1/user_list.go b/pkg/routes/api/v1/user_list.go
index 27870a56f..df401fece 100644
--- a/pkg/routes/api/v1/user_list.go
+++ b/pkg/routes/api/v1/user_list.go
@@ -25,7 +25,6 @@ import (
"code.vikunja.io/api/pkg/models"
auth2 "code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -50,13 +49,13 @@ func UserList(c echo.Context) error {
currentUser, err := user.GetCurrentUser(c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
users, err := user.ListUsers(s, search, currentUser, nil)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Obfuscate the mailadresses
@@ -93,7 +92,7 @@ func ListUsersForProject(c echo.Context) error {
project := models.Project{ID: projectID}
auth, err := auth2.GetAuthFromClaims(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -102,7 +101,7 @@ func ListUsersForProject(c echo.Context) error {
canRead, _, err := project.CanRead(s, auth)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if !canRead {
return echo.ErrForbidden
@@ -111,19 +110,19 @@ func ListUsersForProject(c echo.Context) error {
currentUser, err := user.GetCurrentUser(c)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
search := c.QueryParam("s")
users, err := models.ListUsersFromProject(s, &project, currentUser, search)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, users)
diff --git a/pkg/routes/api/v1/user_password_reset.go b/pkg/routes/api/v1/user_password_reset.go
index 6c13c941e..9c6f2dfdf 100644
--- a/pkg/routes/api/v1/user_password_reset.go
+++ b/pkg/routes/api/v1/user_password_reset.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -51,12 +50,12 @@ func UserResetPassword(c echo.Context) error {
err := user.ResetPassword(s, &pwReset)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."})
@@ -90,12 +89,12 @@ func UserRequestResetPasswordToken(c echo.Context) error {
err := user.RequestUserPasswordResetTokenByEmail(s, &pwTokenReset)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "Token was sent."})
diff --git a/pkg/routes/api/v1/user_register.go b/pkg/routes/api/v1/user_register.go
index e79841180..71b06f5ad 100644
--- a/pkg/routes/api/v1/user_register.go
+++ b/pkg/routes/api/v1/user_register.go
@@ -24,7 +24,6 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -61,7 +60,7 @@ func RegisterUser(c echo.Context) error {
return c.JSON(e.HTTPCode, e)
}
- return handler.HandleHTTPError(err)
+ return err
}
if userIn == nil {
return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."})
@@ -79,19 +78,19 @@ func RegisterUser(c echo.Context) error {
})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Create their initial project
err = models.CreateNewProjectForUser(s, newUser)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, newUser)
diff --git a/pkg/routes/api/v1/user_settings.go b/pkg/routes/api/v1/user_settings.go
index 933df0c9e..59f970fba 100644
--- a/pkg/routes/api/v1/user_settings.go
+++ b/pkg/routes/api/v1/user_settings.go
@@ -28,7 +28,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/avatar"
user2 "code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
)
// UserAvatarProvider holds the user avatar provider type
@@ -81,7 +80,7 @@ func GetUserAvatarProvider(c echo.Context) error {
u, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -90,12 +89,12 @@ func GetUserAvatarProvider(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
uap := &UserAvatarProvider{AvatarProvider: user.AvatarProvider}
@@ -124,7 +123,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
u, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -133,7 +132,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
oldProvider := user.AvatarProvider
@@ -143,7 +142,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
_, err = user2.UpdateUser(s, user, false)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if user.AvatarProvider == "initials" {
@@ -152,7 +151,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if oldProvider != user.AvatarProvider {
@@ -179,9 +178,9 @@ func UpdateGeneralUserSettings(c echo.Context) error {
if err != nil {
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
err = c.Validate(us)
@@ -191,7 +190,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
u, err := user2.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -200,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
invalidateAvatar := user.AvatarProvider == "initials" && user.Name != us.Name
@@ -220,12 +219,12 @@ func UpdateGeneralUserSettings(c echo.Context) error {
_, err = user2.UpdateUser(s, user, true)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if invalidateAvatar {
diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go
index 5619110ae..465d9e7e2 100644
--- a/pkg/routes/api/v1/user_show.go
+++ b/pkg/routes/api/v1/user_show.go
@@ -29,7 +29,6 @@ import (
"code.vikunja.io/api/pkg/db"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -63,7 +62,7 @@ func UserShow(c echo.Context) error {
u, err := models.GetUserOrLinkShareUser(s, a)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
us := &UserWithSettings{
@@ -88,7 +87,7 @@ func UserShow(c echo.Context) error {
us.AuthProvider, err = getAuthProviderName(u)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, us)
diff --git a/pkg/routes/api/v1/user_totp.go b/pkg/routes/api/v1/user_totp.go
index 0df199d01..003981394 100644
--- a/pkg/routes/api/v1/user_totp.go
+++ b/pkg/routes/api/v1/user_totp.go
@@ -27,7 +27,6 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"xorm.io/xorm"
@@ -66,19 +65,19 @@ func getLocalUserFromContext(c echo.Context) (*user.User, *xorm.Session, error)
func UserTOTPEnroll(c echo.Context) error {
u, s, err := getLocalUserFromContext(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
defer s.Close()
t, err := user.EnrollTOTP(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, t)
@@ -101,7 +100,7 @@ func UserTOTPEnroll(c echo.Context) error {
func UserTOTPEnable(c echo.Context) error {
u, s, err := getLocalUserFromContext(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
defer s.Close()
@@ -112,20 +111,20 @@ func UserTOTPEnable(c echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
err = user.EnableTOTP(s, passcode)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."})
@@ -150,35 +149,35 @@ func UserTOTPDisable(c echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
u, s, err := getLocalUserFromContext(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
defer s.Close()
err = user.CheckUserPassword(u, login.Password)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = user.DisableTOTP(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
- return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."})
+ return c.JSON(http.StatusOK, models.Message{Message: "TOTP was disabled successfully."})
}
// UserTOTPQrCode is the handler to show a qr code to enroll the user into totp
@@ -194,26 +193,26 @@ func UserTOTPDisable(c echo.Context) error {
func UserTOTPQrCode(c echo.Context) error {
u, s, err := getLocalUserFromContext(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
defer s.Close()
qrcode, err := user.GetTOTPQrCodeForUser(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
buff := &bytes.Buffer{}
err = jpeg.Encode(buff, qrcode, nil)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes())
@@ -232,19 +231,19 @@ func UserTOTPQrCode(c echo.Context) error {
func UserTOTP(c echo.Context) error {
u, s, err := getLocalUserFromContext(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
defer s.Close()
t, err := user.GetTOTPForUser(s, u)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, t)
diff --git a/pkg/routes/api/v1/user_update_email.go b/pkg/routes/api/v1/user_update_email.go
index d70e1dd46..1841ad7fd 100644
--- a/pkg/routes/api/v1/user_update_email.go
+++ b/pkg/routes/api/v1/user_update_email.go
@@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -50,14 +49,14 @@ func UpdateUserEmail(c echo.Context) (err error) {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
emailUpdate.User, err = user.GetCurrentUser(c)
if err != nil {
- return handler.HandleHTTPError(err)
+ return err
}
s := db.NewSession()
@@ -69,18 +68,18 @@ func UpdateUserEmail(c echo.Context) (err error) {
})
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
err = user.UpdateEmail(s, emailUpdate)
if err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "We sent you email with a link to confirm your email address."})
diff --git a/pkg/routes/api/v1/user_update_password.go b/pkg/routes/api/v1/user_update_password.go
index 9d44d19d6..5e5b6fd83 100644
--- a/pkg/routes/api/v1/user_update_password.go
+++ b/pkg/routes/api/v1/user_update_password.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
)
@@ -60,7 +59,7 @@ func UserChangePassword(c echo.Context) error {
}
if newPW.OldPassword == "" {
- return handler.HandleHTTPError(user.ErrEmptyOldPassword{})
+ return user.ErrEmptyOldPassword{}
}
s := db.NewSession()
@@ -69,18 +68,18 @@ func UserChangePassword(c echo.Context) error {
// Check the current password
if _, err = user.CheckUserCredentials(s, &user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
// Update the password
if err = user.UpdateUserPassword(s, doer, newPW.NewPassword); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return handler.HandleHTTPError(err)
+ return err
}
return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."})
diff --git a/pkg/routes/error_handler.go b/pkg/routes/error_handler.go
new file mode 100644
index 000000000..0d4121772
--- /dev/null
+++ b/pkg/routes/error_handler.go
@@ -0,0 +1,135 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package routes
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/files"
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/web"
+
+ "github.com/getsentry/sentry-go"
+ sentryecho "github.com/getsentry/sentry-go/echo"
+ "github.com/labstack/echo/v4"
+)
+
+// httpCodeGetter is an interface for errors that can provide their HTTP status code.
+type httpCodeGetter interface {
+ GetHTTPCode() int
+}
+
+// errorMessage is used to wrap string error messages in a consistent JSON structure.
+type errorMessage struct {
+ Message interface{} `json:"message"`
+}
+
+// CreateHTTPErrorHandler creates a centralized HTTP error handler that:
+// 1. Converts all error types to proper HTTP responses
+// 2. Preserves full error details (like ValidationHTTPError.InvalidFields)
+// 3. Handles Sentry reporting for 5xx errors
+// 4. Logs all errors appropriately
+func CreateHTTPErrorHandler(e *echo.Echo, enableSentry bool) echo.HTTPErrorHandler {
+ return func(err error, c echo.Context) {
+ if c.Response().Committed {
+ return
+ }
+
+ var (
+ code = http.StatusInternalServerError
+ message interface{} = http.StatusText(http.StatusInternalServerError)
+ )
+
+ // Keep track of the original error for logging/sentry
+ originalErr := err
+
+ // 1. Check if it's already an echo.HTTPError (from middleware, auth, etc.)
+ var he *echo.HTTPError
+ if errors.As(err, &he) {
+ code = he.Code
+ message = he.Message
+ // Check if internal error has more details we should use
+ if he.Internal != nil {
+ originalErr = he.Internal
+ err = he.Internal
+ }
+ }
+
+ // 2. Special case: 413 body limit → convert to ErrFileIsTooLarge
+ // This must be checked before other error type checks
+ if code == http.StatusRequestEntityTooLarge {
+ fileErr := files.ErrFileIsTooLarge{}
+ errDetails := fileErr.HTTPError()
+ code = errDetails.HTTPCode
+ message = errDetails
+ } else if _, isMarshaler := err.(json.Marshaler); isMarshaler {
+ // 3. Check for json.Marshaler (preserves full struct like ValidationHTTPError)
+ // This allows errors with extra fields (like InvalidFields) to be serialized correctly
+ if codeGetter, hasCode := err.(httpCodeGetter); hasCode {
+ code = codeGetter.GetHTTPCode()
+ }
+ message = err // Echo will serialize via MarshalJSON
+ } else if hp, ok := err.(web.HTTPErrorProcessor); ok {
+ // 4. Standard HTTPErrorProcessor (domain errors like ErrProjectDoesNotExist)
+ errDetails := hp.HTTPError()
+ code = errDetails.HTTPCode
+ message = errDetails
+ }
+ // 5. For any other error type, we keep the defaults (500 with generic message)
+ // or the echo.HTTPError values if it was that type
+
+ // Log the error
+ log.Error(originalErr.Error())
+
+ // Sentry reporting for 5xx errors
+ if enableSentry && code >= 500 {
+ reportToSentry(originalErr, c)
+ }
+
+ // Send response
+ if c.Request().Method == http.MethodHead {
+ err = c.NoContent(code)
+ } else {
+ // Wrap string messages in a struct to ensure consistent JSON format
+ // e.g., "Forbidden" becomes {"message": "Forbidden"}
+ if _, isString := message.(string); isString {
+ message = errorMessage{Message: message}
+ }
+ err = c.JSON(code, message)
+ }
+ if err != nil {
+ e.Logger.Error(err)
+ }
+ }
+}
+
+// reportToSentry sends an error to Sentry with request context
+func reportToSentry(err error, c echo.Context) {
+ hub := sentryecho.GetHubFromContext(c)
+ if hub != nil {
+ hub.WithScope(func(scope *sentry.Scope) {
+ scope.SetExtra("url", c.Request().URL)
+ hub.CaptureException(err)
+ })
+ } else {
+ sentry.CaptureException(err)
+ log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
+ }
+ log.Debugf("Error '%s' sent to sentry", err.Error())
+}
diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go
index f621e1d0f..467d0a0f3 100644
--- a/pkg/routes/routes.go
+++ b/pkg/routes/routes.go
@@ -52,16 +52,13 @@
package routes
import (
- "errors"
"fmt"
"log/slog"
- "net/http"
"net/url"
"strings"
"time"
"code.vikunja.io/api/pkg/config"
- "code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth/openid"
@@ -142,17 +139,8 @@ func NewEcho() *echo.Echo {
// Add some overhead for multipart form data (headers, boundaries, etc.)
e.Use(middleware.BodyLimit(fmt.Sprintf("%dM", config.GetMaxFileSizeInMBytes()+2)))
- // Set up custom error handler for body limit exceeded when Sentry is not enabled
- if !config.SentryEnabled.GetBool() {
- e.HTTPErrorHandler = func(err error, c echo.Context) {
- // Convert HTTP 413 errors to custom ErrFileIsTooLarge error
- var herr *echo.HTTPError
- if errors.As(err, &herr) && herr.Code == http.StatusRequestEntityTooLarge {
- err = handler.HandleHTTPError(files.ErrFileIsTooLarge{})
- }
- e.DefaultHTTPErrorHandler(err, c)
- }
- }
+ // Set up centralized error handler
+ e.HTTPErrorHandler = CreateHTTPErrorHandler(e, config.SentryEnabled.GetBool())
return e
}
@@ -174,36 +162,6 @@ func setupSentry(e *echo.Echo) {
e.Use(sentryecho.New(sentryecho.Options{
Repanic: true,
}))
-
- e.HTTPErrorHandler = func(err error, c echo.Context) {
- // Convert HTTP 413 errors to custom ErrFileIsTooLarge error
- var herr *echo.HTTPError
- if errors.As(err, &herr) && herr.Code == http.StatusRequestEntityTooLarge {
- err = handler.HandleHTTPError(files.ErrFileIsTooLarge{})
- }
-
- // Only capture errors not already handled by echo
- if errors.As(err, &herr) && herr.Code > 499 {
- var errToReport = err
- if herr.Internal == nil {
- errToReport = herr.Internal
- }
-
- hub := sentryecho.GetHubFromContext(c)
- if hub != nil {
- hub.WithScope(func(scope *sentry.Scope) {
- scope.SetExtra("url", c.Request().URL)
- hub.CaptureException(errToReport)
- })
- } else {
- sentry.CaptureException(errToReport)
- log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
- }
- log.Debugf("Error '%s' sent to sentry", err.Error())
- }
-
- e.DefaultHTTPErrorHandler(err, c)
- }
}
// RegisterRoutes registers all routes for the application
diff --git a/pkg/web/handler/create.go b/pkg/web/handler/create.go
index 3a23c27ee..3595f15c1 100644
--- a/pkg/web/handler/create.go
+++ b/pkg/web/handler/create.go
@@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4"
@@ -38,14 +39,14 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
// Validate the struct
if err := ctx.Validate(currentStruct); err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, err).SetInternal(err)
+ return err
}
// Get the user to pass for later checks
@@ -67,7 +68,7 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
canCreate, err := currentStruct.CanCreate(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
if !canCreate {
_ = s.Rollback()
@@ -79,17 +80,13 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
err = currentStruct.Create(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
- return HandleHTTPError(err)
+ return err
}
- err = ctx.JSON(http.StatusCreated, currentStruct)
- if err != nil {
- return HandleHTTPError(err)
- }
- return err
+ return ctx.JSON(http.StatusCreated, currentStruct)
}
diff --git a/pkg/web/handler/delete.go b/pkg/web/handler/delete.go
index de3b70d86..bb8fef4d8 100644
--- a/pkg/web/handler/delete.go
+++ b/pkg/web/handler/delete.go
@@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4"
@@ -43,9 +44,9 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
// Check if the user has the permission to delete
@@ -66,7 +67,7 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
canDelete, err := currentStruct.CanDelete(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
if !canDelete {
_ = s.Rollback()
@@ -77,17 +78,13 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
err = currentStruct.Delete(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
- return HandleHTTPError(err)
+ return err
}
- err = ctx.JSON(http.StatusOK, message{"Successfully deleted."})
- if err != nil {
- return HandleHTTPError(err)
- }
- return err
+ return ctx.JSON(http.StatusOK, message{"Successfully deleted."})
}
diff --git a/pkg/web/handler/helper.go b/pkg/web/handler/helper.go
index 1f5f4966f..2cdc095cd 100644
--- a/pkg/web/handler/helper.go
+++ b/pkg/web/handler/helper.go
@@ -17,12 +17,7 @@
package handler
import (
- "net/http"
-
- "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/web"
-
- "github.com/labstack/echo/v4"
)
// WebHandler defines the webhandler object
@@ -36,13 +31,3 @@ type CObject interface {
web.CRUDable
web.Permissions
}
-
-// HandleHTTPError does what it says
-func HandleHTTPError(err error) *echo.HTTPError {
- log.Error(err.Error())
- if a, has := err.(web.HTTPErrorProcessor); has {
- errDetails := a.HTTPError()
- return echo.NewHTTPError(errDetails.HTTPCode, errDetails).SetInternal(err)
- }
- return echo.NewHTTPError(http.StatusInternalServerError).SetInternal(err)
-}
diff --git a/pkg/web/handler/read_all.go b/pkg/web/handler/read_all.go
index dc556b45c..9afcc0290 100644
--- a/pkg/web/handler/read_all.go
+++ b/pkg/web/handler/read_all.go
@@ -27,6 +27,7 @@ import (
vconfig "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4"
@@ -47,9 +48,9 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
// Pagination
@@ -104,7 +105,7 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
// Calculate the number of pages from the number of items
@@ -126,7 +127,7 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
err = s.Commit()
if err != nil {
- return HandleHTTPError(err)
+ return err
}
// Ensure we return an empty array instead of null when there are no results.
@@ -136,9 +137,5 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
result = []interface{}{}
}
- err = ctx.JSON(http.StatusOK, result)
- if err != nil {
- return HandleHTTPError(err)
- }
- return err
+ return ctx.JSON(http.StatusOK, result)
}
diff --git a/pkg/web/handler/read_one.go b/pkg/web/handler/read_one.go
index 1cf6270a8..ceee70735 100644
--- a/pkg/web/handler/read_one.go
+++ b/pkg/web/handler/read_one.go
@@ -24,6 +24,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4"
@@ -39,9 +40,9 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
// Check permissions
@@ -62,7 +63,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
canRead, maxPermission, err := currentStruct.CanRead(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
if !canRead {
_ = s.Rollback()
@@ -74,7 +75,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
err = currentStruct.ReadOne(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
// Set the headers
@@ -85,12 +86,8 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
err = s.Commit()
if err != nil {
- return HandleHTTPError(err)
+ return err
}
- err = ctx.JSON(http.StatusOK, currentStruct)
- if err != nil {
- return HandleHTTPError(err)
- }
- return err
+ return ctx.JSON(http.StatusOK, currentStruct)
}
diff --git a/pkg/web/handler/update.go b/pkg/web/handler/update.go
index 2ef6bd180..68b801ad8 100644
--- a/pkg/web/handler/update.go
+++ b/pkg/web/handler/update.go
@@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4"
@@ -39,14 +40,14 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError
if errors.As(err, &he) {
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err)
+ return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
}
- return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err)
+ return models.ErrInvalidModel{Err: err}
}
// Validate the struct
if err := ctx.Validate(currentStruct); err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, err).SetInternal(err)
+ return err
}
// Check if the user has the permission to do that
@@ -67,7 +68,7 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
canUpdate, err := currentStruct.CanUpdate(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
if !canUpdate {
_ = s.Rollback()
@@ -79,17 +80,13 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
err = currentStruct.Update(s, currentAuth)
if err != nil {
_ = s.Rollback()
- return HandleHTTPError(err)
+ return err
}
err = s.Commit()
if err != nil {
- return HandleHTTPError(err)
+ return err
}
- err = ctx.JSON(http.StatusOK, currentStruct)
- if err != nil {
- return HandleHTTPError(err)
- }
- return err
+ return ctx.JSON(http.StatusOK, currentStruct)
}
diff --git a/pkg/webtests/error_responses_test.go b/pkg/webtests/error_responses_test.go
new file mode 100644
index 000000000..a6cb9ad46
--- /dev/null
+++ b/pkg/webtests/error_responses_test.go
@@ -0,0 +1,150 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "code.vikunja.io/api/pkg/modules/auth"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// ErrorResponse represents the expected JSON error structure for standard errors
+type ErrorResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+// ValidationErrorResponse represents the expected JSON error structure for validation errors
+type ValidationErrorResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ InvalidFields []string `json:"invalid_fields"`
+}
+
+// TestErrorResponseFormats tests that error responses are correctly serialized to JSON
+// This is critical because the error response format is part of the API contract
+func TestErrorResponseFormats(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ // Get auth token for testuser1
+ token, err := auth.NewUserJWTAuthtoken(&testuser1, false)
+ require.NoError(t, err)
+
+ t.Run("validation error returns invalid_fields in JSON body", func(t *testing.T) {
+ // Update a project with empty title - this should trigger validation error
+ req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/1", strings.NewReader(`{"title":""}`))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ // Should be 412 Precondition Failed for validation errors
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+
+ var errResp ValidationErrorResponse
+ err := json.Unmarshal(rec.Body.Bytes(), &errResp)
+ require.NoError(t, err, "Response body: %s", rec.Body.String())
+
+ // Verify the error structure includes invalid_fields
+ assert.Equal(t, 2002, errResp.Code, "Expected error code 2002 (ErrCodeInvalidData)")
+ require.NotEmpty(t, errResp.InvalidFields, "invalid_fields should not be empty")
+ require.GreaterOrEqual(t, len(errResp.InvalidFields), 1, "invalid_fields should have at least one element")
+ assert.Contains(t, errResp.InvalidFields[0], "title", "invalid_fields should mention 'title'")
+ })
+
+ t.Run("bind error returns 400 with message", func(t *testing.T) {
+ // Send malformed JSON
+ req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/1", strings.NewReader(`{invalid json`))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ assert.Equal(t, http.StatusBadRequest, rec.Code)
+ })
+
+ t.Run("not found error returns 404 with correct structure", func(t *testing.T) {
+ // Try to get a project that doesn't exist
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/99999", nil)
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ assert.Equal(t, http.StatusNotFound, rec.Code)
+
+ var errResp ErrorResponse
+ err := json.Unmarshal(rec.Body.Bytes(), &errResp)
+ require.NoError(t, err, "Response body: %s", rec.Body.String())
+
+ // Should have a proper error code
+ assert.NotZero(t, errResp.Code, "Error code should be non-zero")
+ assert.NotEmpty(t, errResp.Message, "Error message should not be empty")
+ })
+
+ t.Run("forbidden error returns 403", func(t *testing.T) {
+ // Try to access a project owned by user13 (project 20)
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/20", nil)
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ assert.Equal(t, http.StatusForbidden, rec.Code)
+ })
+
+ t.Run("domain error returns correct code and message", func(t *testing.T) {
+ // Try to create a project with a nonexistent parent
+ req := httptest.NewRequest(http.MethodPut, "/api/v1/projects", strings.NewReader(`{"title":"Test","parent_project_id":99999}`))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ // Should be 404 for nonexistent parent project
+ assert.Equal(t, http.StatusNotFound, rec.Code)
+
+ var errResp ErrorResponse
+ err := json.Unmarshal(rec.Body.Bytes(), &errResp)
+ require.NoError(t, err, "Response body: %s", rec.Body.String())
+
+ // Verify the error has proper structure
+ assert.NotZero(t, errResp.Code, "Error code should be non-zero")
+ assert.NotEmpty(t, errResp.Message, "Error message should not be empty")
+ })
+
+ t.Run("unauthorized request returns 401", func(t *testing.T) {
+ // Make request without auth token
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/1", nil)
+
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ assert.Equal(t, http.StatusUnauthorized, rec.Code)
+ })
+}
diff --git a/pkg/webtests/integrations.go b/pkg/webtests/integrations.go
index 661dee55b..e7455643c 100644
--- a/pkg/webtests/integrations.go
+++ b/pkg/webtests/integrations.go
@@ -181,19 +181,60 @@ func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
t.Error("Error is nil")
t.FailNow()
}
+
+ // First, try to get error code from HTTPErrorProcessor (domain errors like ValidationHTTPError)
+ if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
+ assert.Equal(t, expectedErrorCode, httpErr.HTTPError().Code)
+ return
+ }
+
+ // Fall back to echo.HTTPError for middleware/auth errors
var httperr *echo.HTTPError
if !errors.As(err, &httperr) {
- t.Error("Error is not *echo.HTTPError")
+ t.Errorf("Error is not *echo.HTTPError or web.HTTPErrorProcessor: %T", err)
t.FailNow()
}
webhttperr, ok := httperr.Message.(web.HTTPError)
if !ok {
- t.Error("Error is not *web.HTTPError")
+ t.Errorf("Error message is not web.HTTPError: %T", httperr.Message)
t.FailNow()
}
assert.Equal(t, expectedErrorCode, webhttperr.Code)
}
+// getHTTPErrorCode extracts the HTTP status code from various error types
+func getHTTPErrorCode(err error) int {
+ // First, try domain errors that implement HTTPErrorProcessor
+ if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
+ return httpErr.HTTPError().HTTPCode
+ }
+
+ // Fall back to echo.HTTPError
+ var httperr *echo.HTTPError
+ if errors.As(err, &httperr) {
+ return httperr.Code
+ }
+
+ return 0
+}
+
+// getHTTPErrorMessage extracts the message from various error types
+func getHTTPErrorMessage(err error) interface{} {
+ // First, try domain errors that implement HTTPErrorProcessor
+ if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
+ return httpErr.HTTPError().Message
+ }
+
+ // Then try echo.HTTPError (for Forbidden etc.)
+ var httperr *echo.HTTPError
+ if errors.As(err, &httperr) {
+ return httperr.Message
+ }
+
+ // Fall back to error string
+ return err.Error()
+}
+
type webHandlerTest struct {
user *user.User
linkShare *models.LinkSharing
diff --git a/pkg/webtests/kanban_test.go b/pkg/webtests/kanban_test.go
index b169f670c..03ac2cbf7 100644
--- a/pkg/webtests/kanban_test.go
+++ b/pkg/webtests/kanban_test.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -90,7 +89,7 @@ func TestBucket(t *testing.T) {
"view": "4",
}, `{"title":""}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
+ assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
@@ -101,7 +100,7 @@ func TestBucket(t *testing.T) {
"view": "80",
}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{
@@ -110,7 +109,7 @@ func TestBucket(t *testing.T) {
"view": "24",
}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@@ -138,7 +137,7 @@ func TestBucket(t *testing.T) {
"view": "36",
}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@@ -166,7 +165,7 @@ func TestBucket(t *testing.T) {
"view": "48",
}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@@ -194,7 +193,7 @@ func TestBucket(t *testing.T) {
"view": "60",
}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@@ -236,12 +235,12 @@ func TestBucket(t *testing.T) {
// Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@@ -269,7 +268,7 @@ func TestBucket(t *testing.T) {
"view": "36",
})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@@ -297,7 +296,7 @@ func TestBucket(t *testing.T) {
"view": "48",
})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@@ -325,7 +324,7 @@ func TestBucket(t *testing.T) {
"view": "60",
})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@@ -380,7 +379,7 @@ func TestBucket(t *testing.T) {
"view": "80",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{
@@ -388,7 +387,7 @@ func TestBucket(t *testing.T) {
"view": "24",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@@ -413,7 +412,7 @@ func TestBucket(t *testing.T) {
"view": "36",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@@ -438,7 +437,7 @@ func TestBucket(t *testing.T) {
"view": "48",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@@ -463,7 +462,7 @@ func TestBucket(t *testing.T) {
"view": "60",
}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
diff --git a/pkg/webtests/link_sharing_test.go b/pkg/webtests/link_sharing_test.go
index 92d948b5c..a4ddc527e 100644
--- a/pkg/webtests/link_sharing_test.go
+++ b/pkg/webtests/link_sharing_test.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -69,34 +68,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":0}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":2}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Read only access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":0}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":2}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Write access", func(t *testing.T) {
@@ -113,7 +112,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"permission":2}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Admin access", func(t *testing.T) {
@@ -197,7 +196,7 @@ func TestLinkSharing(t *testing.T) {
// Project 2, not shared with this token
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the permission to see this`)
+ assert.Contains(t, getHTTPErrorMessage(err), `You don't have the permission to see this`)
})
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
@@ -226,12 +225,12 @@ func TestLinkSharing(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
@@ -255,17 +254,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
@@ -280,23 +279,23 @@ func TestLinkSharing(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Permissions check", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -345,34 +344,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
@@ -380,17 +379,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -437,34 +436,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
@@ -472,17 +471,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -590,7 +589,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
@@ -607,7 +606,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`)
@@ -625,7 +624,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"})
@@ -683,17 +682,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
@@ -701,17 +700,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -759,34 +758,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
@@ -794,17 +793,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
diff --git a/pkg/webtests/project_test.go b/pkg/webtests/project_test.go
index bd6b537ed..19bd6e20f 100644
--- a/pkg/webtests/project_test.go
+++ b/pkg/webtests/project_test.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -89,7 +88,7 @@ func TestProject(t *testing.T) {
// Owned by user13
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the permission to see this`)
+ assert.Contains(t, getHTTPErrorMessage(err), `You don't have the permission to see this`)
assert.Empty(t, rec.Result().Header.Get("x-max-permissions"))
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
@@ -192,24 +191,24 @@ func TestProject(t *testing.T) {
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
+ assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
+ assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`)
@@ -225,7 +224,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`)
@@ -241,7 +240,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
@@ -257,7 +256,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
@@ -287,17 +286,17 @@ func TestProject(t *testing.T) {
// Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"})
@@ -308,12 +307,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"})
@@ -324,12 +323,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
@@ -340,12 +339,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
@@ -380,24 +379,24 @@ func TestProject(t *testing.T) {
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
+ assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
+ assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
@@ -419,7 +418,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
diff --git a/pkg/webtests/task_comment_test.go b/pkg/webtests/task_comment_test.go
index 641d4196a..8876845fb 100644
--- a/pkg/webtests/task_comment_test.go
+++ b/pkg/webtests/task_comment_test.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -68,70 +67,70 @@ func TestTaskComments(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "15", "commentid": "3"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "18", "commentid": "6"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -151,70 +150,70 @@ func TestTaskComments(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "15", "commentid": "3"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "18", "commentid": "6"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
})
})
@@ -234,12 +233,12 @@ func TestTaskComments(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "34"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "15"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "16"}, `{"comment":"Lorem Ipsum"}`)
@@ -255,7 +254,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "18"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "19"}, `{"comment":"Lorem Ipsum"}`)
@@ -271,7 +270,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`)
@@ -287,7 +286,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)
diff --git a/pkg/webtests/task_test.go b/pkg/webtests/task_test.go
index aebb8307f..d5e537a34 100644
--- a/pkg/webtests/task_test.go
+++ b/pkg/webtests/task_test.go
@@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -223,12 +222,12 @@ func TestTask(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`)
@@ -244,7 +243,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`)
@@ -260,7 +259,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
@@ -276,7 +275,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
@@ -323,12 +322,12 @@ func TestTask(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "16"})
@@ -344,7 +343,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "19"})
@@ -360,7 +359,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
@@ -376,7 +375,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
@@ -406,12 +405,12 @@ func TestTask(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
@@ -427,7 +426,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
@@ -443,7 +442,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
@@ -459,7 +458,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err)
- assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
+ assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
diff --git a/pkg/webtests/user_confirm_email_test.go b/pkg/webtests/user_confirm_email_test.go
index c1fca8d21..5c0bf50a9 100644
--- a/pkg/webtests/user_confirm_email_test.go
+++ b/pkg/webtests/user_confirm_email_test.go
@@ -23,7 +23,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -37,7 +36,7 @@ func TestUserConfirmEmail(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`, nil, nil)
require.Error(t, err)
- assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code)
+ assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
t.Run("Empty token", func(t *testing.T) {
diff --git a/pkg/webtests/user_export_download_test.go b/pkg/webtests/user_export_download_test.go
index a51b1b061..f64ec96ae 100644
--- a/pkg/webtests/user_export_download_test.go
+++ b/pkg/webtests/user_export_download_test.go
@@ -22,7 +22,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -33,8 +32,8 @@ func TestUserExportDownload(t *testing.T) {
body := `{"password": "12345678"}`
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser15, body, nil, nil)
require.Error(t, err)
- assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code)
- assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found")
+ assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
+ assert.Contains(t, getHTTPErrorMessage(err), "No user data export found")
})
t.Run("export file metadata exists but physical file does not exist", func(t *testing.T) {
@@ -43,7 +42,7 @@ func TestUserExportDownload(t *testing.T) {
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser1, body, nil, nil)
require.Error(t, err)
// This should return 404 when the physical file doesn't exist
- assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code)
- assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found")
+ assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
+ assert.Contains(t, getHTTPErrorMessage(err), "No user data export found")
})
}
diff --git a/pkg/webtests/user_password_request_token_test.go b/pkg/webtests/user_password_request_token_test.go
index e4ebccaca..e24a14125 100644
--- a/pkg/webtests/user_password_request_token_test.go
+++ b/pkg/webtests/user_password_request_token_test.go
@@ -22,7 +22,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -41,7 +40,7 @@ func TestUserRequestResetPasswordToken(t *testing.T) {
t.Run("Invalid email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`, nil, nil)
require.Error(t, err)
- assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
+ assert.Equal(t, http.StatusBadRequest, getHTTPErrorCode(err))
})
t.Run("No user with that email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`, nil, nil)
diff --git a/pkg/webtests/user_password_reset_test.go b/pkg/webtests/user_password_reset_test.go
index 601f30447..68fcd6c72 100644
--- a/pkg/webtests/user_password_reset_test.go
+++ b/pkg/webtests/user_password_reset_test.go
@@ -23,7 +23,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
- "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -40,7 +39,7 @@ func TestUserPasswordReset(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`, nil, nil)
require.Error(t, err)
- assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
+ assert.Equal(t, http.StatusBadRequest, getHTTPErrorCode(err))
})
t.Run("No new password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{