diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 950b7860c4e..5d6decec671 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "errors" "fmt" "os" "path" @@ -195,9 +196,14 @@ func deleteDashboard(c *models.ReqContext) Response { } err := dashboards.NewService().DeleteDashboard(dash.Id, c.OrgId) - if err == models.ErrDashboardCannotDeleteProvisionedDashboard { - return Error(400, "Dashboard cannot be deleted because it was provisioned", err) - } else if err != nil { + if err != nil { + var dashboardErr models.DashboardErr + if ok := errors.As(err, &dashboardErr); ok { + if errors.Is(err, models.ErrDashboardCannotDeleteProvisionedDashboard) { + return Error(dashboardErr.StatusCode, dashboardErr.Error(), err) + } + } + return Error(500, "Failed to delete dashboard", err) } @@ -267,50 +273,36 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa } func dashboardSaveErrorToApiResponse(err error) Response { - if err == models.ErrDashboardTitleEmpty || - err == models.ErrDashboardWithSameNameAsFolder || - err == models.ErrDashboardFolderWithSameNameAsDashboard || - err == models.ErrDashboardTypeMismatch || - err == models.ErrDashboardInvalidUid || - err == models.ErrDashboardUidToLong || - err == models.ErrDashboardWithSameUIDExists || - err == models.ErrFolderNotFound || - err == models.ErrDashboardFolderCannotHaveParent || - err == models.ErrDashboardFolderNameExists || - err == models.ErrDashboardRefreshIntervalTooShort || - err == models.ErrDashboardCannotSaveProvisionedDashboard { + var dashboardErr models.DashboardErr + if ok := errors.As(err, &dashboardErr); ok { + if body := dashboardErr.Body(); body != nil { + return JSON(dashboardErr.StatusCode, body) + } + if errors.Is(dashboardErr, models.ErrDashboardUpdateAccessDenied) { + return Error(dashboardErr.StatusCode, dashboardErr.Error(), err) + } + return Error(dashboardErr.StatusCode, dashboardErr.Error(), nil) + } + + if errors.Is(err, models.ErrFolderNotFound) { return Error(400, err.Error(), nil) } - if err == models.ErrDashboardUpdateAccessDenied { - return Error(403, err.Error(), err) - } - - if validationErr, ok := err.(alerting.ValidationError); ok { + var validationErr alerting.ValidationError + if ok := errors.As(err, &validationErr); ok { return Error(422, validationErr.Error(), nil) } - if err == models.ErrDashboardWithSameNameInFolderExists { - return JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()}) - } - - if err == models.ErrDashboardVersionMismatch { - return JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()}) - } - - if pluginErr, ok := err.(models.UpdatePluginDashboardError); ok { - message := "The dashboard belongs to plugin " + pluginErr.PluginId + "." + var pluginErr models.UpdatePluginDashboardError + if ok := errors.As(err, &pluginErr); ok { + message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId) // look up plugin name if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist { - message = "The dashboard belongs to plugin " + pluginDef.Name + "." + message = fmt.Sprintf("The dashboard belongs to plugin %s.", pluginDef.Name) } return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message}) } - if err == models.ErrDashboardNotFound { - return JSON(404, util.DynMap{"status": "not-found", "message": err.Error()}) - } - return Error(500, "Failed to save dashboard", err) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 7050f96ccb4..eccf4085dc5 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -788,7 +788,7 @@ func TestDashboardApiEndpoint(t *testing.T) { {SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, {SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, {SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardUidToLong, ExpectedStatusCode: 400}, + {SaveError: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, {SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, } diff --git a/pkg/api/folder.go b/pkg/api/folder.go index b0900a83a95..93fb8089069 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -132,7 +132,7 @@ func toFolderError(err error) Response { err == models.ErrFolderWithSameUIDExists || err == models.ErrDashboardTypeMismatch || err == models.ErrDashboardInvalidUid || - err == models.ErrDashboardUidToLong { + err == models.ErrDashboardUidTooLong { return Error(400, err.Error(), nil) } diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index af1dd8f94e6..74144d5d15d 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -49,7 +49,7 @@ func TestFoldersApiEndpoint(t *testing.T) { {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400}, {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidToLong, ExpectedStatusCode: 400}, + {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, @@ -107,7 +107,7 @@ func TestFoldersApiEndpoint(t *testing.T) { {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400}, {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidToLong, ExpectedStatusCode: 400}, + {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 31fa82a9aa6..e3310134ac2 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -2,7 +2,6 @@ package models import ( "encoding/base64" - "errors" "fmt" "strings" "time" @@ -10,34 +9,130 @@ import ( "github.com/gosimple/slug" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) // Typed errors var ( - ErrDashboardNotFound = errors.New("Dashboard not found") - ErrDashboardFolderNotFound = errors.New("Folder not found") - ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found") - ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists") - ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists") - ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else") - ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty") - ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder") - ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists") - ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id") - ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder") - ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards") - ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder") - ErrDashboardFolderNameExists = errors.New("A folder with that name already exists") - ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard") - ErrDashboardInvalidUid = errors.New("uid contains illegal characters") - ErrDashboardUidToLong = errors.New("uid to long. max 40 characters") - ErrDashboardCannotSaveProvisionedDashboard = errors.New("Cannot save provisioned dashboard") - ErrDashboardRefreshIntervalTooShort = errors.New("Dashboard refresh interval is too low") - ErrDashboardCannotDeleteProvisionedDashboard = errors.New("provisioned dashboard cannot be deleted") - ErrDashboardIdentifierNotSet = errors.New("Unique identifier needed to be able to get a dashboard") - RootFolderName = "General" + ErrDashboardNotFound = DashboardErr{ + Reason: "Dashboard not found", + StatusCode: 404, + Status: "not-found", + } + ErrDashboardFolderNotFound = DashboardErr{ + Reason: "Folder not found", + StatusCode: 404, + } + ErrDashboardSnapshotNotFound = DashboardErr{ + Reason: "Dashboard snapshot not found", + StatusCode: 404, + } + ErrDashboardWithSameUIDExists = DashboardErr{ + Reason: "A dashboard with the same uid already exists", + StatusCode: 400, + } + ErrDashboardWithSameNameInFolderExists = DashboardErr{ + Reason: "A dashboard with the same name in the folder already exists", + StatusCode: 412, + Status: "name-exists", + } + ErrDashboardVersionMismatch = DashboardErr{ + Reason: "The dashboard has been changed by someone else", + StatusCode: 412, + Status: "version-mismatch", + } + ErrDashboardTitleEmpty = DashboardErr{ + Reason: "Dashboard title cannot be empty", + StatusCode: 400, + } + ErrDashboardFolderCannotHaveParent = DashboardErr{ + Reason: "A Dashboard Folder cannot be added to another folder", + StatusCode: 400, + } + ErrDashboardsWithSameSlugExists = DashboardErr{ + Reason: "Multiple dashboards with the same slug exists", + StatusCode: 412, + } + ErrDashboardFailedGenerateUniqueUid = DashboardErr{ + Reason: "Failed to generate unique dashboard id", + StatusCode: 500, + } + ErrDashboardTypeMismatch = DashboardErr{ + Reason: "Dashboard cannot be changed to a folder", + StatusCode: 400, + } + ErrDashboardFolderWithSameNameAsDashboard = DashboardErr{ + Reason: "Folder name cannot be the same as one of its dashboards", + StatusCode: 400, + } + ErrDashboardWithSameNameAsFolder = DashboardErr{ + Reason: "Dashboard name cannot be the same as folder", + StatusCode: 400, + } + ErrDashboardFolderNameExists = DashboardErr{ + Reason: "A folder with that name already exists", + StatusCode: 400, + } + ErrDashboardUpdateAccessDenied = DashboardErr{ + Reason: "Access denied to save dashboard", + StatusCode: 403, + } + ErrDashboardInvalidUid = DashboardErr{ + Reason: "uid contains illegal characters", + StatusCode: 400, + } + ErrDashboardUidTooLong = DashboardErr{ + Reason: "uid too long, max 40 characters", + StatusCode: 400, + } + ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{ + Reason: "Cannot save provisioned dashboard", + StatusCode: 400, + } + ErrDashboardRefreshIntervalTooShort = DashboardErr{ + Reason: "Dashboard refresh interval is too low", + StatusCode: 400, + } + ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{ + Reason: "provisioned dashboard cannot be deleted", + StatusCode: 400, + } + ErrDashboardIdentifierNotSet = DashboardErr{ + Reason: "Unique identifier needed to be able to get a dashboard", + StatusCode: 400, + } + RootFolderName = "General" ) +// DashboardErr represents a dashboard error. +type DashboardErr struct { + StatusCode int + Status string + Reason string +} + +// Equal returns whether equal to another DashboardErr. +func (e DashboardErr) Equal(o DashboardErr) bool { + return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason +} + +// Error returns the error message. +func (e DashboardErr) Error() string { + if e.Reason != "" { + return e.Reason + } + return "Dashboard Error" +} + +// Body returns the error's response body, if applicable. +func (e DashboardErr) Body() util.DynMap { + if e.Status == "" { + return nil + } + + return util.DynMap{"status": e.Status, "message": e.Error()} +} + type UpdatePluginDashboardError struct { PluginId string } diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 9a224880f0c..0674e74c115 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/pkg/services/dashboards/dashboard_service.go @@ -103,7 +103,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, if !util.IsValidShortUID(dash.Uid) { return nil, models.ErrDashboardInvalidUid } else if len(dash.Uid) > 40 { - return nil, models.ErrDashboardUidToLong + return nil, models.ErrDashboardUidTooLong } if err := validateDashboardRefreshInterval(dash); err != nil { diff --git a/pkg/services/dashboards/dashboard_service_test.go b/pkg/services/dashboards/dashboard_service_test.go index 1d2ff667741..77651774db5 100644 --- a/pkg/services/dashboards/dashboard_service_test.go +++ b/pkg/services/dashboards/dashboard_service_test.go @@ -75,7 +75,7 @@ func TestDashboardService(t *testing.T) { {Uid: "asdf90_-", Error: nil}, {Uid: "asdf/90", Error: models.ErrDashboardInvalidUid}, {Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil}, - {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidToLong}, + {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidTooLong}, } for _, tc := range testCases {