Skip to content

Commit

Permalink
Merge pull request #2818 from ActiveState/DX-2149
Browse files Browse the repository at this point in the history
  • Loading branch information
Naatan authored Oct 16, 2023
2 parents faebd57 + a019466 commit b5d6047
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 28 deletions.
18 changes: 15 additions & 3 deletions internal/locale/locales/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1761,8 +1761,8 @@ operation_success_local:
other: |
Your local project has been updated.
Run [ACTIONABLE]state push[/RESET] to save changes to the platform.
solver_err:
other: "The Platform failed to resolve the dependencies for this build. {{.V0}}\n{{.V1}}"
buildplan_err:
other: "Could not plan build, platform responded with:\n{{.V0}}\n{{.V1}}"
transient_solver_tip:
other: You may want to retry this command if the failure was related to a resourcing or networking issue.
warning_git_project_mismatch:
Expand Down Expand Up @@ -2059,7 +2059,7 @@ err_init_authenticated:
err_country_blocked:
other: This service is not available in your region.
err_commit_id_invalid:
other: "Invalid project commit ID: {{.V0}}"
other: "Expected commit ID to be UUID formatted, but got: {{.V0}}"
commit_id_gitignore:
other: |
# Ignore {{.V0}}/{{.V1}} file; tracking this via VCS will cause users to have to resolve redundant conflicts
Expand All @@ -2083,3 +2083,15 @@ err_edit_project_mapping:
other: Could not update project mapping
notice_runtime_disabled:
other: Skipping runtime setup because it was disabled by an environment variable
err_local_commit_file:
other: |
Could not find or read the commit file for your project at '[ACTIONABLE]{{.V0}}[/RESET]'.
You can fix this by either running '[ACTIONABLE]state pull[/RESET]' to update to the latest commit, or manually switch to a specific
commit by running '[ACTIONABLE]state switch <commit-id>[/RESET]'.
To view your projects commits run '[ACTIONABLE]state history[/RESET]'.
Please avoid deleting or editing this file directly.
err_searchingredient_toomany:
other: Too many ingredients match the query '[ACTIONABLE]{{.V0}}[/RESET]', please try to be more specific.
15 changes: 14 additions & 1 deletion internal/runbits/rationalize/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package rationalize

import "errors"
import (
"errors"
"fmt"
)

// Inner is just an alias because otherwise external use of this struct would not be able to construct the error
// property, and we want to keep the boilerplate minimal.
Expand All @@ -13,3 +16,13 @@ var ErrNoProject = errors.New("no project")
var ErrNotAuthenticated = errors.New("not authenticated")

var ErrActionAborted = errors.New("aborted by user")

type ErrAPI struct {
Wrapped error
Code int
Message string
}

func (e *ErrAPI) Error() string { return fmt.Sprintf("API code %d: %s", e.Code, e.Message) }

func (e *ErrAPI) Unwrap() error { return e.Wrapped }
54 changes: 54 additions & 0 deletions internal/runbits/requirements/rationalize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package requirements

import (
"errors"

"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/pkg/localcommit"
bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model"
"github.com/ActiveState/cli/pkg/platform/model"
)

func (r *RequirementOperation) rationalizeError(err *error) {
var localCommitFileErr *localcommit.ErrLocalCommitFile
var tooManyMatchesErr *model.ErrTooManyMatches
var noMatchesErr *ErrNoMatches
var buildPlannerErr *bpModel.BuildPlannerError

switch {
case err == nil:
return

// Local commit file not found or malformed
case errors.As(*err, &localCommitFileErr):
*err = errs.WrapUserFacing(*err,
locale.Tr("err_local_commit_file", localCommitFileErr.File),
errs.SetInput())

// Too many matches
case errors.As(*err, &tooManyMatchesErr):
*err = errs.WrapUserFacing(*err,
locale.Tr("err_searchingredient_toomany", tooManyMatchesErr.Query),
errs.SetInput())

// No matches, and no alternate suggestions
case errors.As(*err, &noMatchesErr) && noMatchesErr.Alternatives == nil:
*err = errs.WrapUserFacing(*err,
locale.Tr("package_ingredient_alternatives_nosuggest", noMatchesErr.Query),
errs.SetInput())

// No matches, but have alternate suggestions
case errors.As(*err, &noMatchesErr) && noMatchesErr.Alternatives != nil:
*err = errs.WrapUserFacing(*err,
locale.Tr("package_ingredient_alternatives", noMatchesErr.Query, *noMatchesErr.Alternatives),
errs.SetInput())

// We communicate buildplanner errors verbatim as the intend is that these are curated by the buildplanner
case errors.As(*err, &buildPlannerErr):
*err = errs.WrapUserFacing(*err,
buildPlannerErr.LocalizedError(),
errs.SetIf(buildPlannerErr.InputError(), errs.SetInput()))

}
}
20 changes: 17 additions & 3 deletions internal/runbits/requirements/requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ActiveState/cli/internal/output"
"github.com/ActiveState/cli/internal/primer"
"github.com/ActiveState/cli/internal/prompt"
"github.com/ActiveState/cli/internal/rtutils/ptr"
"github.com/ActiveState/cli/internal/runbits"
"github.com/ActiveState/cli/internal/runbits/rtusage"
"github.com/ActiveState/cli/pkg/localcommit"
Expand Down Expand Up @@ -80,7 +81,16 @@ func NewRequirementOperation(prime primeable) *RequirementOperation {

const latestVersion = "latest"

func (r *RequirementOperation) ExecuteRequirementOperation(requirementName, requirementVersion string, requirementBitWidth int, operation bpModel.Operation, nsType model.NamespaceType) (rerr error) {
type ErrNoMatches struct {
*locale.LocalizedError
Query string
Alternatives *string
}

func (r *RequirementOperation) ExecuteRequirementOperation(requirementName, requirementVersion string,
requirementBitWidth int, operation bpModel.Operation, nsType model.NamespaceType) (rerr error) {
defer r.rationalizeError(&rerr)

var ns model.Namespace
var langVersion string
langName := "undetermined"
Expand Down Expand Up @@ -182,9 +192,13 @@ func (r *RequirementOperation) ExecuteRequirementOperation(requirementName, requ
multilog.Error("Failed to retrieve suggestions: %v", err)
}
if len(suggestions) == 0 {
return locale.WrapInputError(err, "package_ingredient_alternatives_nosuggest", "", requirementName)
return &ErrNoMatches{
locale.WrapInputError(err, "package_ingredient_alternatives_nosuggest", "", requirementName),
requirementName, nil}
}
return locale.WrapInputError(err, "package_ingredient_alternatives", "", requirementName, strings.Join(suggestions, "\n"))
return &ErrNoMatches{
locale.WrapInputError(err, "package_ingredient_alternatives", "", requirementName, strings.Join(suggestions, "\n")),
requirementName, ptr.To(strings.Join(suggestions, "\n"))}
}

requirementName = normalized
Expand Down
1 change: 1 addition & 0 deletions internal/testhelpers/tagsuite/tagsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
Init = "init"
InstallScripts = "install-scripts"
Installer = "installer"
Install = "install"
Invite = "invite"
RemoteInstaller = "remote-installer"
Interrupt = "interrupt"
Expand Down
35 changes: 28 additions & 7 deletions pkg/localcommit/localcommit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@ package localcommit

import (
"bytes"
"errors"
"fmt"
"path/filepath"

"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/fileutils"
"github.com/ActiveState/cli/internal/locale"
"github.com/go-openapi/strfmt"
)

type FileDoesNotExistError struct{ *locale.LocalizedError }
type ErrLocalCommitFile struct {
*locale.LocalizedError // for backwards compatibility with runners that don't implement rationalizers
errorMsg string
IsDoesNotExist bool
File string
}

func (e *ErrLocalCommitFile) Error() string {
return e.errorMsg
}

func IsFileDoesNotExistError(err error) bool {
return errs.Matches(err, &FileDoesNotExistError{})
var errLocalCommit *ErrLocalCommitFile
if errors.As(err, &errLocalCommit) {
return errLocalCommit.IsDoesNotExist
}
return false
}

func getCommitFile(projectDir string) string {
Expand All @@ -26,18 +39,26 @@ func Get(projectDir string) (strfmt.UUID, error) {
configDir := filepath.Join(projectDir, constants.ProjectConfigDirName)
commitFile := getCommitFile(projectDir)
if !fileutils.DirExists(configDir) || !fileutils.TargetExists(commitFile) {
return "", &FileDoesNotExistError{locale.NewError("err_commit_file_does_not_exist",
"Your project runtime's commit ID file '{{.V0}}' does not exist", commitFile)}
return "", &ErrLocalCommitFile{
locale.NewError("err_local_commit_file", commitFile),
"local commit file does not exist",
true, commitFile}
}

b, err := fileutils.ReadFile(commitFile)
if err != nil {
return "", locale.WrapError(err, "err_get_commit_file", "Could not read your project runtime's commit ID file")
return "", &ErrLocalCommitFile{
locale.NewError("err_local_commit_file", commitFile),
"local commit could not be read",
false, commitFile}
}

commitID := string(b)
if !strfmt.IsUUID(commitID) {
return "", locale.NewError("err_commit_id_invalid", commitID)
return "", &ErrLocalCommitFile{
locale.NewError("err_local_commit_file", commitFile),
"local commit is not uuid formatted",
false, commitFile}
}

return strfmt.UUID(commitID), nil
Expand Down
16 changes: 16 additions & 0 deletions pkg/platform/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"reflect"
"strings"

"github.com/ActiveState/cli/internal/runbits/rationalize"
"github.com/alecthomas/template"

"github.com/ActiveState/cli/pkg/sysinfo"
Expand Down Expand Up @@ -144,3 +145,18 @@ func ErrorMessageFromPayload(err error) string {
}
return codeVal.String()
}

func ErrorFromPayload(err error) error {
return ErrorFromPayloadTyped(err)
}

func ErrorFromPayloadTyped(err error) *rationalize.ErrAPI {
if err == nil {
return nil
}
return &rationalize.ErrAPI{
err,
ErrorCodeFromPayload(err),
ErrorMessageFromPayload(err),
}
}
2 changes: 1 addition & 1 deletion pkg/platform/api/buildplanner/model/buildplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (e *BuildPlannerError) Error() string {
}

var err error
err = locale.NewError("solver_err", "", croppedMessage, errorLines)
err = locale.NewError("buildplan_err", "", croppedMessage, errorLines)
if e.IsTransient {
err = errs.AddTips(err, locale.Tr("transient_solver_tip"))
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/platform/model/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ func FetchAuthors(ingredID, ingredVersionID *strfmt.UUID) (Authors, error) {
return results.Payload.Authors, nil
}

type ErrTooManyMatches struct {
*locale.LocalizedError
Query string
}

func searchIngredientsNamespace(ns Namespace, name string, includeVersions bool, exactOnly bool) ([]*IngredientAndVersion, error) {
limit := int64(100)
offset := int64(0)
Expand All @@ -123,7 +128,7 @@ func searchIngredientsNamespace(ns Namespace, name string, includeVersions bool,
for offset == 0 || len(entries) == int(limit) {
if offset > (limit * 10) { // at most we will get 10 pages of ingredients (that's ONE THOUSAND ingredients)
// Guard against queries that match TOO MANY ingredients
return nil, locale.NewError("err_searchingredient_toomany", "Query matched too many ingredients. Please use a more specific query.")
return nil, &ErrTooManyMatches{locale.NewInputError("err_searchingredient_toomany", "", name), name}
}

params.SetOffset(&offset)
Expand Down
2 changes: 1 addition & 1 deletion pkg/platform/model/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type SolverError struct {
}

func (e *SolverError) Error() string {
return "solver_error"
return "buildplan_error"
}

func (e *SolverError) Unwrap() error {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/activate_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivatePythonByHostOnly() {
cp.ExpectNotExitCode(0)

if strings.Count(cp.Snapshot(), " x ") != 1 {
suite.Fail("Expected exactly ONE error message, got: %s", cp.Snapshot())
suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/integration/checkout_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutNonEmptyDir() {
cp.ExpectExitCode(1)

if strings.Count(cp.Snapshot(), " x ") != 1 {
suite.Fail("Expected exactly ONE error message, got: %s", cp.Snapshot())
suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
}

// remove file
Expand Down Expand Up @@ -217,7 +217,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutNotFound() {
cp.ExpectExitCode(1)

if strings.Count(cp.Snapshot(), " x ") != 1 {
suite.Fail("Expected exactly ONE error message, got: %s", cp.Snapshot())
suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot())
}
}

Expand Down
Loading

0 comments on commit b5d6047

Please sign in to comment.