From bdd8b3c72e5e477b45994271f1040fe9f8aa8163 Mon Sep 17 00:00:00 2001 From: mitchell Date: Mon, 4 Nov 2024 17:45:56 -0500 Subject: [PATCH] Implement user-facing errors for environment setup commands. --- internal/locale/locales/en-us.yaml | 4 -- internal/runbits/checkout/checkout.go | 16 ++++---- internal/runners/checkout/checkout.go | 17 ++++++++- internal/runners/initialize/init.go | 20 ++++------ internal/runners/initialize/rationalize.go | 5 +++ internal/runners/swtch/switch.go | 44 ++++++++++++++++++---- 6 files changed, 73 insertions(+), 33 deletions(-) diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index 6eb637bf9f..9a4deb3a3a 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -646,8 +646,6 @@ err_incompatible_move_file_dir: other: | Could not move [NOTICE]{{.V0}}[/RESET] to [NOTICE]{{.V1}}[/RESET]. One is a file, the other a directory. This indicates that the requested build may be corrupted. -err_init_lang: - other: "Invalid language: {{.V0}}@{{.V1}}" initializing_project: other: | Initializing Project @@ -1217,8 +1215,6 @@ err_findproject_notfound: other: "Could not load project [ACTIONABLE]{{.V0}}[/RESET] from path: [ACTIONABLE]{{.V1}}[/RESET]" arg_state_shell_namespace_description: other: The namespace of the project you wish to start a virtual environment shell/prompt for, or just the project name if previously used -err_language_by_commit: - other: Could not get language from commit ID {{.V0}} err_parse_project: other: Could not parse project file at {{.V0}} err_uninstall_privilege_mismatch: diff --git a/internal/runbits/checkout/checkout.go b/internal/runbits/checkout/checkout.go index 74efe258b8..8e25d88b4e 100644 --- a/internal/runbits/checkout/checkout.go +++ b/internal/runbits/checkout/checkout.go @@ -3,9 +3,6 @@ package checkout import ( "path/filepath" - "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/internal/runbits/buildscript" "github.com/go-openapi/strfmt" "github.com/ActiveState/cli/internal/constants" @@ -13,6 +10,8 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/osutils" + "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/buildscript" "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" @@ -47,6 +46,7 @@ func (e errCommitDoesNotBelong) Error() string { } var errNoCommitID = errs.New("commitID is nil") +var ErrNoOrg = errs.New("unable to get org name") func New(repo git.Repository, prime primeable) *Checkout { return &Checkout{repo, prime} @@ -87,7 +87,7 @@ func (r *Checkout) Run(ns *project.Namespaced, branchName, cachePath, targetPath if !noClone && repoURL != nil && *repoURL != "" { err := r.repo.CloneProject(ns.Owner, ns.Project, path, r.prime.Output(), r.prime.Analytics()) if err != nil { - return "", locale.WrapError(err, "err_clone_project", "Could not clone associated git repository") + return "", errs.Wrap(err, "Could not clone associated git repository") } } } else if commitID == nil { @@ -119,7 +119,7 @@ func (r *Checkout) fetchProject( // the project and create the project file pj, err := model.FetchProjectByName(ns.Owner, ns.Project, r.prime.Auth()) if err != nil { - return "", "", nil, "", "", nil, locale.WrapError(err, "err_fetch_project", "", ns.String()) + return "", "", nil, "", "", nil, errs.Wrap(err, "Unable to fetch project '%s'", ns.String()) } proj := pj.Name @@ -144,7 +144,7 @@ func (r *Checkout) fetchProject( case branchName != "": branch, err = model.BranchForProjectByName(pj, branchName) if err != nil { - return "", "", nil, "", "", nil, locale.WrapError(err, "err_fetch_branch", "", branchName) + return "", "", nil, "", "", nil, errs.Wrap(err, "Could not get branch '%s'", branchName) } commitID = branch.CommitID @@ -175,7 +175,7 @@ func (r *Checkout) fetchProject( return "", "", nil, "", "", nil, errs.Wrap(err, "Unable to get the project's org") } if len(owners) == 0 { - return "", "", nil, "", "", nil, locale.NewInputError("err_no_org_name", "Your project's organization name could not be found") + return "", "", nil, "", "", nil, ErrNoOrg } owner := owners[0].URLName @@ -211,7 +211,7 @@ func CreateProjectFiles(checkoutPath, cachePath, owner, name, branch, commitID, func getLanguage(commitID strfmt.UUID, auth *authentication.Auth) (language.Language, error) { modelLanguage, err := model.LanguageByCommit(commitID, auth) if err != nil { - return language.Unset, locale.WrapError(err, "err_language_by_commit", "", string(commitID)) + return language.Unset, errs.Wrap(err, "Could not get language from commit ID '%s'", string(commitID)) } return language.MakeByNameAndVersion(modelLanguage.Name, modelLanguage.Version), nil diff --git a/internal/runners/checkout/checkout.go b/internal/runners/checkout/checkout.go index 576dcbd453..ea2105031e 100644 --- a/internal/runners/checkout/checkout.go +++ b/internal/runners/checkout/checkout.go @@ -1,6 +1,7 @@ package checkout import ( + "errors" "os" "path/filepath" "strings" @@ -77,6 +78,19 @@ func NewCheckout(prime primeable) *Checkout { } } +func rationalizeError(rerr *error) { + if rerr == nil { + return + } + + switch { + case errors.Is(*rerr, checkout.ErrNoOrg): + *rerr = errs.WrapUserFacing(*rerr, + locale.Tl("err_no_org_name", "Your project's organization name could not be found"), + errs.SetInput()) + } +} + func (u *Checkout) Run(params *Params) (rerr error) { var err error var ns *project.Namespaced @@ -101,6 +115,7 @@ func (u *Checkout) Run(params *Params) (rerr error) { } defer func() { runtime_runbit.RationalizeSolveError(u.prime.Project(), u.auth, &rerr) }() + defer rationalizeError(&rerr) logging.Debug("Checking out %s to %s", ns.String(), params.PreferredPath) @@ -113,7 +128,7 @@ func (u *Checkout) Run(params *Params) (rerr error) { proj, err := project.FromPath(projectDir) if err != nil { - return locale.WrapError(err, "err_project_frompath") + return errs.Wrap(err, "Could not read created project file") } u.prime.SetProject(proj) diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 607809fd38..6707251202 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -20,7 +20,6 @@ import ( "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/runbits/buildscript" "github.com/ActiveState/cli/internal/runbits/dependencies" - "github.com/ActiveState/cli/internal/runbits/errors" "github.com/ActiveState/cli/internal/runbits/org" "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/internal/runbits/runtime" @@ -90,6 +89,8 @@ func (e errUnrecognizedLanguage) Error() string { return fmt.Sprintf("unrecognized language: %s", e.Name) } +var errDeleteProjectAfterError = errs.New("could not delete initialized project") + // New returns a prepared ptr to Initialize instance. func New(prime primeable) *Initialize { return &Initialize{prime, prime.Auth(), prime.Config(), prime.Output(), prime.Analytics(), prime.SvcModel()} @@ -164,16 +165,15 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { err := fileutils.MkdirUnlessExists(path) if err != nil { - return locale.WrapError(err, "err_init_preparedir", "Could not create directory at [NOTICE]{{.V0}}[/RESET]. Error: {{.V1}}", params.Path, err.Error()) + return errs.Wrap(err, "Could not create directory '%s'", params.Path) } path, err = filepath.Abs(params.Path) if err != nil { - return locale.WrapExternalError(err, "err_init_abs_path", "Could not determine absolute path to [NOTICE]{{.V0}}[/RESET]. Error: {{.V1}}", path, err.Error()) + return errs.Wrap(err, "Could not determine absolute path to '%s'", params.Path) } var languageName, languageVersion string - var inferred bool if params.Language != "" { langParts := strings.Split(params.Language, "@") languageName = langParts[0] @@ -181,7 +181,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { languageVersion = langParts[1] } } else { - languageName, languageVersion, inferred = inferLanguage(r.config, r.auth) + languageName, languageVersion, _ = inferLanguage(r.config, r.auth) } if languageName == "" { @@ -200,11 +200,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { version, err := deriveVersion(lang, languageVersion, r.auth) if err != nil { - if inferred || errors.IsReportableError(err) { - return locale.WrapError(err, "err_init_lang", "", languageName, languageVersion) - } else { - return locale.WrapExternalError(err, "err_init_lang", "", languageName, languageVersion) - } + return errs.Wrap(err, "Unable to get language version") } resolvedOwner, err = org.Get(paramOwner, r.auth, r.config) @@ -226,7 +222,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { pjfile, err := projectfile.Create(createParams) if err != nil { - return locale.WrapError(err, "err_init_pjfile", "Could not create project file") + return errs.Wrap(err, "Could not create project file") } // If an error occurs, remove the created activestate.yaml file so the user can try again. @@ -296,7 +292,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { err2 := model.DeleteProject(namespace.Owner, namespace.Project, r.auth) if err2 != nil { multilog.Error("Error deleting remotely created project after runtime setup error: %v", errs.JoinMessage(err2)) - return locale.WrapError(err, "err_init_refresh_delete_project", "Could not setup runtime after init, and could not delete newly created Platform project. Please delete it manually before trying again") + return errDeleteProjectAfterError } return errs.Wrap(err, "Failed to fetch build result") } diff --git a/internal/runners/initialize/rationalize.go b/internal/runners/initialize/rationalize.go index 11c786e6ea..540e350060 100644 --- a/internal/runners/initialize/rationalize.go +++ b/internal/runners/initialize/rationalize.go @@ -83,5 +83,10 @@ func rationalizeError(owner, project string, rerr *error) { errs.SetTips(locale.T("err_init_authenticated"))) } + case errors.Is(*rerr, errDeleteProjectAfterError): + *rerr = errs.WrapUserFacing(*rerr, + locale.Tl("err_init_refresh_delete_project", "Could not setup runtime after init, and could not delete newly created Platform project. Please delete it manually before trying again"), + ) + } } diff --git a/internal/runners/swtch/switch.go b/internal/runners/swtch/switch.go index b0ee096628..c5c997d426 100644 --- a/internal/runners/swtch/switch.go +++ b/internal/runners/swtch/switch.go @@ -1,6 +1,8 @@ package swtch import ( + "errors" + "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/errs" @@ -74,6 +76,15 @@ func (b branchIdentifier) Locale() string { return locale.Tl("branch_identifier_type", "branch") } +type errCommitNotOnBranch struct { + commitID string + branch string +} + +func (e errCommitNotOnBranch) Error() string { + return "commit is not on branch" +} + func New(prime primeable) *Switch { return &Switch{ prime: prime, @@ -86,7 +97,24 @@ func New(prime primeable) *Switch { } } -func (s *Switch) Run(params SwitchParams) error { +func rationalizeError(rerr *error) { + if rerr == nil { + return + } + + var commitNotOnBranchErr *errCommitNotOnBranch + + switch { + case errors.As(*rerr, &commitNotOnBranchErr): + *rerr = errs.WrapUserFacing(*rerr, + locale.Tl("err_identifier_branch_not_on_branch", "Commit does not belong to history for branch [ACTIONABLE]{{.V0}}[/RESET]", commitNotOnBranchErr.branch), + errs.SetInput(), + ) + } +} + +func (s *Switch) Run(params SwitchParams) (rerr error) { + defer rationalizeError(&rerr) logging.Debug("ExecuteSwitch") if s.project == nil { @@ -96,27 +124,27 @@ func (s *Switch) Run(params SwitchParams) error { project, err := model.LegacyFetchProjectByName(s.project.Owner(), s.project.Name()) if err != nil { - return locale.WrapError(err, "err_fetch_project", "", s.project.Namespace().String()) + return errs.Wrap(err, "Could not fetch project '%s'", s.project.Namespace().String()) } identifier, err := resolveIdentifier(project, params.Identifier) if err != nil { - return locale.WrapError(err, "err_resolve_identifier", "Could not resolve identifier '{{.V0}}'", params.Identifier) + return errs.Wrap(err, "Could not resolve identifier '%s'", params.Identifier) } if id, ok := identifier.(branchIdentifier); ok { err = s.project.Source().SetBranch(id.branch.Label) if err != nil { - return locale.WrapError(err, "err_switch_set_branch", "Could not update branch") + return errs.Wrap(err, "Could not update branch") } } belongs, err := model.CommitBelongsToBranch(s.project.Owner(), s.project.Name(), s.project.BranchName(), identifier.CommitID(), s.auth) if err != nil { - return locale.WrapError(err, "err_identifier_branch", "Could not determine if commit belongs to branch") + return errs.Wrap(err, "Could not determine if commit belongs to branch") } if !belongs { - return locale.NewInputError("err_identifier_branch_not_on_branch", "Commit does not belong to history for branch [ACTIONABLE]{{.V0}}[/RESET]", s.project.BranchName()) + return &errCommitNotOnBranch{identifier.CommitID().String(), s.project.BranchName()} } err = localcommit.Set(s.project.Dir(), identifier.CommitID().String()) @@ -126,7 +154,7 @@ func (s *Switch) Run(params SwitchParams) error { _, err = runtime_runbit.Update(s.prime, trigger.TriggerSwitch) if err != nil { - return locale.WrapError(err, "err_refresh_runtime") + return errs.Wrap(err, "Could not setup runtime") } s.out.Print(output.Prepare( @@ -148,7 +176,7 @@ func resolveIdentifier(project *mono_models.Project, idParam string) (identifier branch, err := model.BranchForProjectByName(project, idParam) if err != nil { - return nil, locale.WrapError(err, "err_identifier_branch", "Could not get branch '{{.V0}}' for current project", idParam) + return nil, errs.Wrap(err, "Could not get branch '%s'", idParam) }