Skip to content

Commit

Permalink
Initial attempt at user-facing errors for state push.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchell-as committed Sep 26, 2023
1 parent 99bbe2f commit a3acc6c
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 27 deletions.
13 changes: 13 additions & 0 deletions internal/runbits/errors/centralized.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ import "github.com/ActiveState/cli/internal/errs"
type ErrNoProject struct {
*errs.WrapperError
}

type ErrNotAuthenticated struct {
*errs.WrapperError
}

type ErrActionAborted struct {
*errs.WrapperError
}

type ErrPermission struct {
*errs.WrapperError
Details interface{}
}
93 changes: 66 additions & 27 deletions internal/runners/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ActiveState/cli/internal/output"
"github.com/ActiveState/cli/internal/primer"
"github.com/ActiveState/cli/internal/prompt"
runbitsErrors "github.com/ActiveState/cli/internal/runbits/errors"
"github.com/ActiveState/cli/pkg/localcommit"
"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
"github.com/ActiveState/cli/pkg/platform/authentication"
Expand Down Expand Up @@ -61,14 +62,60 @@ const (
intendCreateProject = 0x0008
)

func (r *Push) Run(params PushParams) error {
type errNoChanges struct{ *errs.WrapperError }
type errProjectInUse struct {
*errs.WrapperError
Namespace *project.Namespaced
}
type errHistoryMismatch struct{ *errs.WrapperError }
type errFastForward struct{ *errs.WrapperError }

func processError(err *error) {
if err == nil {
return
}

switch {
case errs.Matches(*err, &runbitsErrors.ErrNotAuthenticated{}):
*err = errs.WrapUserFacingError(*err, locale.Tl("err_push_not_authenticated", "In order to update your project you need to be authenticated, please run '[ACTIONABLE]state auth[/RESET]' to authenticate."))
case errs.Matches(*err, &runbitsErrors.ErrNoProject{}):
*err = errs.WrapUserFacingError(*err, locale.Tl("err_push_headless", "You must first create a project."),
errs.WithTips(
locale.Tl("push_headless_push_tip_state_init", "Run [ACTIONABLE]state init[/RESET] to create a project with the State Tool."),
))
case errs.Matches(*err, &errNoChanges{}):
*err = errs.WrapUserFacingError(*err, locale.Tl("err_push_nocommit", "You have nothing to push, make some changes first with [ACTIONABLE]state install[/RESET]."))
case errs.Matches(*err, &errProjectInUse{}):
e := &errProjectInUse{}
errors.As(*err, &e)
*err = errs.WrapUserFacingError(*err, locale.Tl(
"err_push_create_nonunique",
"The project [NOTICE]{{.V0}}[/RESET] is already in use.", e.Namespace.String()))
case errs.Matches(*err, &runbitsErrors.ErrActionAborted{}):
*err = errs.WrapUserFacingError(*err, locale.Tl("push_create_project_aborted", "Project creation aborted by user"))
case errs.Matches(*err, &runbitsErrors.ErrPermission{}):
e := &runbitsErrors.ErrPermission{}
errors.As(*err, &e)
*err = errs.WrapUserFacingError(*err, locale.Tl("push_project_branch_no_permission", "You do not have permission to push to {{.V0}}.", e.Details.(*project.Namespaced).String()))
case errs.Matches(*err, &errHistoryMismatch{}):
*err = errs.WrapUserFacingError(*err, locale.Tl("err_mergecommit_customtarget", "The targets commit history does not match your local commit history. Are you pushing to the right project?"))
case errs.Matches(*err, &errFastForward{}):
*err = errs.WrapUserFacingError(*err, locale.T("err_push_outdated"),
errs.WithTips(locale.Tl("err_tip_push_outdated", "Run `[ACTIONABLE]state pull[/RESET]`")))
}
}

func (r *Push) Run(params PushParams) (rerr error) {
defer processError(&rerr)

if err := r.verifyInput(); err != nil {
return errs.Wrap(err, "verifyInput failed")
}
r.out.Notice(locale.Tl("operating_message", "", r.project.NamespaceString(), r.project.Dir()))

commitID, err := localcommit.Get(r.project.Dir()) // The commit we want to push
if err != nil {
// Note: should not get here, as verifyInput() ensures there is a local commit
return errs.Wrap(err, "Unable to get local commit")
}

Expand All @@ -78,7 +125,7 @@ func (r *Push) Run(params PushParams) error {
var err error
targetNamespace, err = r.namespaceFromProject()
if err != nil {
return locale.WrapError(err, "err_valid_namespace", "Could not get a valid namespace, is your activestate.yaml malformed?")
return errs.Wrap(err, "Could not get a valid namespace, is your activestate.yaml malformed?")
}
}

Expand Down Expand Up @@ -117,7 +164,7 @@ func (r *Push) Run(params PushParams) error {
}
targetNamespace, err = r.promptNamespace()
if err != nil {
return locale.WrapError(err, "err_prompt_namespace", "Could not prompt for namespace")
return errs.Wrap(err, "Could not prompt for namespace")
}
}

Expand All @@ -126,17 +173,15 @@ func (r *Push) Run(params PushParams) error {
targetPjm, err = model.FetchProjectByName(targetNamespace.Owner, targetNamespace.Project)
if err != nil {
if !errs.Matches(err, &model.ErrProjectNotFound{}) {
return locale.WrapError(err, "err_push_try_project", "Failed to check for existence of project.")
return errs.Wrap(err, "Failed to check for existence of project")
}
}

// Create remote project
var projectCreated bool
if intend&intendCreateProject > 0 || targetPjm == nil {
if targetPjm != nil {
return locale.NewInputError(
"err_push_create_nonunique",
"The project [NOTICE]{{.V0}}[/RESET] is already in use.", targetNamespace.String())
return &errProjectInUse{errs.New("The project is already in use"), targetNamespace}
}

// If the user didn't necessarily intend to create the project we should ask them for confirmation
Expand All @@ -150,14 +195,14 @@ func (r *Push) Run(params PushParams) error {
return errs.Wrap(err, "Confirmation failed")
}
if !createProject {
return locale.WrapInputError(err, "push_create_project_aborted", "Project creation aborted by user")
return &runbitsErrors.ErrActionAborted{errs.New("Aborted")}
}
}

r.out.Notice(locale.Tl("push_creating_project", "Creating project [NOTICE]{{.V1}}[/RESET] under [NOTICE]{{.V0}}[/RESET] on the ActiveState Platform", targetNamespace.Owner, targetNamespace.Project))
targetPjm, err = model.CreateEmptyProject(targetNamespace.Owner, targetNamespace.Project, r.project.Private())
if err != nil {
return locale.WrapError(err, "push_project_create_empty_err", "Failed to create a project {{.V0}}.", r.project.Namespace().String())
return errs.Wrap(err, "Failed to create project %s", r.project.Namespace().String())
}

projectCreated = true
Expand All @@ -173,12 +218,12 @@ func (r *Push) Run(params PushParams) error {
// If we have created an empty project the only existing branch will be the default one
branch, err = model.DefaultBranchForProject(targetPjm)
if err != nil {
return locale.NewInputError("err_no_default_branch")
return errs.Wrap(err, "Project has no default branch")
}
} else {
branch, err = model.BranchForProjectByName(targetPjm, r.project.BranchName())
if err != nil {
return locale.WrapError(err, "err_fetch_branch", "", r.project.BranchName())
return errs.Wrap(err, "Could not get branch %s", r.project.BranchName())
}
}

Expand All @@ -198,25 +243,23 @@ func (r *Push) Run(params PushParams) error {
}
if !errors.Is(err, model.ErrMergeFastForward) {
if params.Namespace.IsValid() {
return locale.WrapError(err, "err_mergecommit_customtarget", "The targets commit history does not match your local commit history.")
return &errHistoryMismatch{errs.New("Commit history does not match")}
}
return locale.WrapError(err, "err_mergecommit", "Could not detect if merge is necessary.")
return errs.Wrap(err, "Could not detect if merge is necessary")
}
}
if mergeStrategy != nil {
return errs.AddTips(
locale.NewInputError("err_push_outdated"),
locale.Tl("err_tip_push_outdated", "Run `[ACTIONABLE]state pull[/RESET]`"))
return &errFastForward{errs.New("Merge needed")}
}
}

// Update the project at the given commit id.
err = model.UpdateProjectBranchCommitWithModel(targetPjm, branch.Label, commitID)
if err != nil {
if errs.Matches(err, &model.ErrUpdateBranchAuth{}) {
return locale.WrapInputError(err, "push_project_branch_no_permission", "You do not have permission to push to {{.V0}}.", targetNamespace.String())
return &runbitsErrors.ErrPermission{errs.New("No permission to push"), targetNamespace}
} else {
return locale.WrapError(err, "push_project_branch_commit_err", "Failed to update new project {{.V0}} to current commitID.", targetNamespace.String())
return errs.Wrap(err, "Failed to update new project %s to current commitID", targetNamespace.String())
}
}

Expand Down Expand Up @@ -247,24 +290,20 @@ func (r *Push) Run(params PushParams) error {

func (r *Push) verifyInput() error {
if !r.auth.Authenticated() {
return locale.NewInputError("err_push_not_authenticated", "In order to update your project you need to be authenticated, please run '[ACTIONABLE]state auth[/RESET]' to authenticate.")
return &runbitsErrors.ErrNotAuthenticated{errs.New("Not authenticated")}
}

// Check if as.yaml exists
if r.project == nil {
return errs.AddTips(locale.NewInputError(
"err_push_headless",
"You must first create a project."),
locale.Tl("push_headless_push_tip_state_init", "Run [ACTIONABLE]state init[/RESET] to create a project with the State Tool."),
)
return &runbitsErrors.ErrNoProject{errs.New("Not in project")}
}

commitID, err := localcommit.Get(r.project.Dir())
if err != nil && !localcommit.IsFileDoesNotExistError(err) {
return errs.Wrap(err, "Unable to get local commit")
}
if commitID == "" {
return locale.NewInputError("err_push_nocommit", "You have nothing to push, make some changes first with [ACTIONABLE]state install[/RESET].")
return &errNoChanges{errs.New("No changes")}
}

return nil
Expand Down Expand Up @@ -317,12 +356,12 @@ func (r *Push) promptNamespace() (*project.Namespaced, error) {
func fetchLanguage(commitID strfmt.UUID) (*language.Supported, string, error) {
lang, err := model.FetchLanguageForCommit(commitID)
if err != nil {
return nil, "", errs.Wrap(err, "Failed to retrieve language information for headless commit.")
return nil, "", errs.Wrap(err, "Failed to retrieve language information for headless commit")
}

l, err := language.MakeByNameAndVersion(lang.Name, lang.Version)
if err != nil {
return nil, "", errs.Wrap(err, "Failed to convert commit language to supported language.")
return nil, "", errs.Wrap(err, "Failed to convert commit language to supported language")
}

ls := language.Supported{Language: l}
Expand Down

0 comments on commit a3acc6c

Please sign in to comment.