mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
chore: Better user friendly error messages (#64)
This commit is contained in:
+4
-5
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
@@ -21,7 +20,7 @@ func NewBunCommand() *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := executeBunFlow(cmd.Context(), args)
|
err := executeBunFlow(cmd.Context(), args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to execute bun flow: %s", err)
|
ui.ErrorExit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -33,12 +32,12 @@ func executeBunFlow(ctx context.Context, args []string) error {
|
|||||||
analytics.TrackCommandBun()
|
analytics.TrackCommandBun()
|
||||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultBunPackageManagerConfig())
|
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultBunPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create bun package manager proxy: %s", err)
|
return fmt.Errorf("failed to create bun package manager proxy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := config.FromContext(ctx)
|
config, err := config.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to get config: %s", err)
|
return fmt.Errorf("failed to get config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCommand, err := packageManager.ParseCommand(args)
|
parsedCommand, err := packageManager.ParseCommand(args)
|
||||||
@@ -53,7 +52,7 @@ func executeBunFlow(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
return fmt.Errorf("failed to create dependency resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||||
|
|||||||
+4
-5
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
@@ -21,7 +20,7 @@ func NewNpmCommand() *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := executeNpmFlow(cmd.Context(), args)
|
err := executeNpmFlow(cmd.Context(), args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to execute npm flow: %s", err)
|
ui.ErrorExit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -33,12 +32,12 @@ func executeNpmFlow(ctx context.Context, args []string) error {
|
|||||||
analytics.TrackCommandNpm()
|
analytics.TrackCommandNpm()
|
||||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultNpmPackageManagerConfig())
|
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultNpmPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create npm package manager proxy: %s", err)
|
return fmt.Errorf("failed to create npm package manager proxy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := config.FromContext(ctx)
|
config, err := config.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to get config: %s", err)
|
return fmt.Errorf("failed to get config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCommand, err := packageManager.ParseCommand(args)
|
parsedCommand, err := packageManager.ParseCommand(args)
|
||||||
@@ -53,7 +52,7 @@ func executeNpmFlow(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
return fmt.Errorf("failed to create dependency resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||||
|
|||||||
+6
-4
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
@@ -21,7 +20,7 @@ func NewPnpmCommand() *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := executePnpmFlow(cmd.Context(), args)
|
err := executePnpmFlow(cmd.Context(), args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to execute pnpm flow: %s", err)
|
ui.ErrorExit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -33,12 +32,12 @@ func executePnpmFlow(ctx context.Context, args []string) error {
|
|||||||
analytics.TrackCommandPnpm()
|
analytics.TrackCommandPnpm()
|
||||||
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultPnpmPackageManagerConfig())
|
packageManager, err := packagemanager.NewNpmPackageManager(packagemanager.DefaultPnpmPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create pnpm package manager proxy: %s", err)
|
return fmt.Errorf("failed to create pnpm package manager proxy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := config.FromContext(ctx)
|
config, err := config.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to get config: %s", err)
|
return fmt.Errorf("failed to get config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCommand, err := packageManager.ParseCommand(args)
|
parsedCommand, err := packageManager.ParseCommand(args)
|
||||||
@@ -52,6 +51,9 @@ func executePnpmFlow(ctx context.Context, args []string) error {
|
|||||||
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||||
|
|
||||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create dependency resolver: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
@@ -21,7 +20,7 @@ func NewPipCommand() *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := executePipFlow(cmd.Context(), args)
|
err := executePipFlow(cmd.Context(), args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to execute pip flow: %s", err)
|
ui.ErrorExit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -33,12 +32,12 @@ func executePipFlow(ctx context.Context, args []string) error {
|
|||||||
analytics.TrackCommandPip()
|
analytics.TrackCommandPip()
|
||||||
packageManager, err := packagemanager.NewPypiPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
packageManager, err := packagemanager.NewPypiPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create pip package manager: %w", err)
|
return fmt.Errorf("failed to create pip package manager proxy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := config.FromContext(ctx)
|
config, err := config.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to get config: %s", err)
|
return fmt.Errorf("failed to get config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCommand, err := packageManager.ParseCommand(args)
|
parsedCommand, err := packageManager.ParseCommand(args)
|
||||||
@@ -55,7 +54,7 @@ func executePipFlow(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
return fmt.Errorf("failed to create dependency resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||||
|
|||||||
+3
-5
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/dry/log"
|
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
"github.com/safedep/pmg/internal/analytics"
|
"github.com/safedep/pmg/internal/analytics"
|
||||||
"github.com/safedep/pmg/internal/flows"
|
"github.com/safedep/pmg/internal/flows"
|
||||||
@@ -21,7 +20,7 @@ func NewUvCommand() *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := executeUvFlow(cmd.Context(), args)
|
err := executeUvFlow(cmd.Context(), args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to execute uv flow: %s", err)
|
ui.ErrorExit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -38,7 +37,7 @@ func executeUvFlow(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
config, err := config.FromContext(ctx)
|
config, err := config.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to get config: %s", err)
|
return fmt.Errorf("failed to get config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCommand, err := packageManager.ParseCommand(args)
|
parsedCommand, err := packageManager.ParseCommand(args)
|
||||||
@@ -46,7 +45,6 @@ func executeUvFlow(ctx context.Context, args []string) error {
|
|||||||
return fmt.Errorf("failed to parse command: %w", err)
|
return fmt.Errorf("failed to parse command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the args right here
|
|
||||||
packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig()
|
packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig()
|
||||||
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||||
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||||
@@ -55,7 +53,7 @@ func executeUvFlow(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
return fmt.Errorf("failed to create dependency resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
|
||||||
|
|||||||
+4
-2
@@ -32,7 +32,8 @@ func NewInstallCommand() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
aliasManager := alias.New(*config, rcFileManager)
|
|
||||||
|
aliasManager := alias.New(config, rcFileManager)
|
||||||
return aliasManager.Install()
|
return aliasManager.Install()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,8 @@ func NewRemoveCommand() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
aliasManager := alias.New(*config, rcFileManager)
|
|
||||||
|
aliasManager := alias.New(config, rcFileManager)
|
||||||
return aliasManager.Remove()
|
return aliasManager.Remove()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-9
@@ -32,25 +32,28 @@ type RcFileManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRcFileManager implements RcFileManager for managing the RC file.
|
// DefaultRcFileManager implements RcFileManager for managing the RC file.
|
||||||
type DefaultRcFileManager struct {
|
type defaultRcFileManager struct {
|
||||||
HomeDir string
|
HomeDir string
|
||||||
RcFileName string
|
RcFileName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ RcFileManager = &defaultRcFileManager{}
|
||||||
|
|
||||||
// NewDefaultRcFileManager creates a new DefaultRcFileManager.
|
// NewDefaultRcFileManager creates a new DefaultRcFileManager.
|
||||||
func NewDefaultRcFileManager(rcFileName string) (*DefaultRcFileManager, error) {
|
func NewDefaultRcFileManager(rcFileName string) (*defaultRcFileManager, error) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DefaultRcFileManager{
|
|
||||||
|
return &defaultRcFileManager{
|
||||||
HomeDir: homeDir,
|
HomeDir: homeDir,
|
||||||
RcFileName: rcFileName,
|
RcFileName: rcFileName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates the RC file with the given aliases.
|
// Create creates the RC file with the given aliases.
|
||||||
func (m *DefaultRcFileManager) Create(aliases []string) (string, error) {
|
func (m *defaultRcFileManager) Create(aliases []string) (string, error) {
|
||||||
rcPath := m.GetRcPath()
|
rcPath := m.GetRcPath()
|
||||||
f, err := os.Create(rcPath)
|
f, err := os.Create(rcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -67,7 +70,7 @@ func (m *DefaultRcFileManager) Create(aliases []string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes the RC file.
|
// Remove deletes the RC file.
|
||||||
func (m *DefaultRcFileManager) Remove() error {
|
func (m *defaultRcFileManager) Remove() error {
|
||||||
rcPath := m.GetRcPath()
|
rcPath := m.GetRcPath()
|
||||||
if err := os.Remove(rcPath); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(rcPath); err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("could not delete %s: %w", rcPath, err)
|
return fmt.Errorf("could not delete %s: %w", rcPath, err)
|
||||||
@@ -76,12 +79,12 @@ func (m *DefaultRcFileManager) Remove() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRcPath returns the full path to the RC file.
|
// GetRcPath returns the full path to the RC file.
|
||||||
func (m *DefaultRcFileManager) GetRcPath() string {
|
func (m *defaultRcFileManager) GetRcPath() string {
|
||||||
return filepath.Join(m.HomeDir, m.RcFileName)
|
return filepath.Join(m.HomeDir, m.RcFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default configuration for alias management.
|
// DefaultConfig returns the default configuration for alias management.
|
||||||
func DefaultConfig() *AliasConfig {
|
func DefaultConfig() AliasConfig {
|
||||||
var shells []Shell
|
var shells []Shell
|
||||||
|
|
||||||
fishShell, _ := NewFishShell()
|
fishShell, _ := NewFishShell()
|
||||||
@@ -90,7 +93,7 @@ func DefaultConfig() *AliasConfig {
|
|||||||
|
|
||||||
shells = append(shells, fishShell, zshShell, bashShell)
|
shells = append(shells, fishShell, zshShell, bashShell)
|
||||||
|
|
||||||
return &AliasConfig{
|
return AliasConfig{
|
||||||
RcFileName: ".pmg.rc",
|
RcFileName: ".pmg.rc",
|
||||||
PackageManagers: []string{"npm", "pip", "pnpm", "bun", "uv"},
|
PackageManagers: []string{"npm", "pip", "pnpm", "bun", "uv"},
|
||||||
Shells: shells,
|
Shells: shells,
|
||||||
@@ -243,8 +246,9 @@ func (a *AliasManager) addSourceLine(configPath, sourceLine string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
_, err = f.WriteString(fmt.Sprintf("\n%s", sourceLine))
|
_, err = fmt.Fprintf(f, "\n%s", sourceLine)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package flows
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/safedep/pmg/analyzer"
|
"github.com/safedep/pmg/analyzer"
|
||||||
"github.com/safedep/pmg/config"
|
"github.com/safedep/pmg/config"
|
||||||
@@ -33,14 +34,14 @@ func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagem
|
|||||||
if f.config.Paranoid {
|
if f.config.Paranoid {
|
||||||
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create malware analyzer: %s", err)
|
return fmt.Errorf("failed to create malware analyzer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzers = append(analyzers, malysisActiveScanAnalyzer)
|
analyzers = append(analyzers, malysisActiveScanAnalyzer)
|
||||||
} else {
|
} else {
|
||||||
malysisQueryAnalyzer, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{})
|
malysisQueryAnalyzer, err := analyzer.NewMalysisQueryAnalyzer(analyzer.MalysisQueryAnalyzerConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create malware analyzer: %s", err)
|
return fmt.Errorf("failed to create malware analyzer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzers = append(analyzers, malysisQueryAnalyzer)
|
analyzers = append(analyzers, malysisQueryAnalyzer)
|
||||||
@@ -60,12 +61,12 @@ func (f *commonFlow) Run(ctx context.Context, args []string, parsedCmd *packagem
|
|||||||
|
|
||||||
proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("Failed to create package manager guard: %s", err)
|
return fmt.Errorf("failed to create package manager guard: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = proxy.Run(ctx, args, parsedCmd)
|
err = proxy.Run(ctx, args, parsedCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Fatalf("pmg: failed to execute command: %s", err)
|
return fmt.Errorf("failed to run package manager guard: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/safedep/dry/log"
|
||||||
|
"github.com/safedep/pmg/usefulerror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorExit prints the error message and exits the program with a non-zero status code.
|
||||||
|
func ErrorExit(err error) {
|
||||||
|
log.Errorf("Exiting due to error: %s", err)
|
||||||
|
|
||||||
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
||||||
|
if !ok {
|
||||||
|
Fatalf("Error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalHelp := usefulErr.AdditionalHelp()
|
||||||
|
if additionalHelp == "" {
|
||||||
|
additionalHelp = fmt.Sprintf("If you believe this is a bug, please report it at: %s",
|
||||||
|
"https://github.com/safedep/pmg/issues/new?assignees=&labels=bug")
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearStatus()
|
||||||
|
|
||||||
|
fmt.Println(Colors.Red(fmt.Sprintf("Error occurred: %s", usefulErr.HumanError())))
|
||||||
|
fmt.Println(Colors.Yellow(usefulErr.Help()))
|
||||||
|
fmt.Println(Colors.Yellow(additionalHelp))
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -1,15 +1,48 @@
|
|||||||
package packagemanager
|
package packagemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"github.com/safedep/pmg/usefulerror"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPackageNotFound = errors.New("package not found")
|
ErrPackageNotFound = usefulerror.Useful().
|
||||||
ErrFailedToFetchPackage = errors.New("failed to fetch package")
|
WithCode("package_not_found").
|
||||||
ErrFailedToParsePackage = errors.New("failed to parse package")
|
WithHumanError("The requested package could not be found.").
|
||||||
ErrNoPackagesFound = errors.New("no packages found")
|
WithHelp("Please check the package name and try again.")
|
||||||
ErrAuthorNotFound = errors.New("author not found")
|
|
||||||
|
|
||||||
ErrGitHubRateLimitExceeded = errors.New("github api rate limit exceeded")
|
ErrFailedToFetchPackage = usefulerror.Useful().
|
||||||
|
WithCode("fetch_failed").
|
||||||
|
WithHumanError("Failed to retrieve the requested package.").
|
||||||
|
WithHelp("Check your network connection and try again. If the problem persists, the package repository may be temporarily unavailable.").
|
||||||
|
Msg("failed to fetch package")
|
||||||
|
|
||||||
|
ErrFailedToResolveVersion = usefulerror.Useful().
|
||||||
|
WithCode("resolve_version_failed").
|
||||||
|
WithHumanError("Failed to resolve the requested package version.").
|
||||||
|
WithHelp("Check your network connection and try again. If the problem persists, the package repository may be temporarily unavailable.").
|
||||||
|
Msg("failed to resolve package version")
|
||||||
|
|
||||||
|
ErrFailedToResolveDependencies = usefulerror.Useful().
|
||||||
|
WithCode("resolve_failed").
|
||||||
|
WithHumanError("Failed to resolve dependencies.").
|
||||||
|
WithHelp("Check your network connection and try again. If the problem persists, the package repository may be temporarily unavailable.").
|
||||||
|
Msg("failed to resolve dependencies")
|
||||||
|
|
||||||
|
ErrFailedToParsePackage = usefulerror.Useful().
|
||||||
|
WithCode("parse_failed").
|
||||||
|
WithHumanError("The package data could not be processed.").
|
||||||
|
WithHelp("The package may be corrupted or in an unsupported format.").
|
||||||
|
Msg("failed to parse package")
|
||||||
|
|
||||||
|
ErrAuthorNotFound = usefulerror.Useful().
|
||||||
|
WithCode("author_not_found").
|
||||||
|
WithHumanError("The package author information could not be found.").
|
||||||
|
WithHelp("This may be due to incomplete package metadata or network issues.").
|
||||||
|
Msg("author not found")
|
||||||
|
|
||||||
|
ErrGitHubRateLimitExceeded = usefulerror.Useful().
|
||||||
|
WithCode("github_rate_limit").
|
||||||
|
WithHumanError("GitHub API rate limit has been exceeded.").
|
||||||
|
WithHelp("Wait for the rate limit to reset or configure authentication to increase your rate limit.").
|
||||||
|
Msg("github api rate limit exceeded")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error
|
|||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
packageName, version, err := npmParsePackageInfo(pkg)
|
packageName, version, err := npmParsePackageInfo(pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse package info: %w", err)
|
return nil, ErrFailedToParsePackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean version if specified
|
// Clean version if specified
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (r *npmDependencyResolver) ResolveLatestVersion(ctx context.Context,
|
|||||||
|
|
||||||
pkgInfo, err := pd.GetPackage(pkg.Name)
|
pkgInfo, err := pd.GetPackage(pkg.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get package: %w", err)
|
return nil, ErrFailedToFetchPackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Resolved npm/%s to latest version %s", pkg.Name, pkgInfo.LatestVersion)
|
log.Debugf("Resolved npm/%s to latest version %s", pkg.Name, pkgInfo.LatestVersion)
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func (p *pipCommandParser) ParseCommand(args []string) (*ParsedCommand, error) {
|
|||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
packageName, version, extras, err := pypiParsePackageInfo(pkg)
|
packageName, version, extras, err := pypiParsePackageInfo(pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse package info: %w", err)
|
return nil, ErrFailedToParsePackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != "" {
|
if version != "" {
|
||||||
@@ -150,7 +150,7 @@ func (p *pipCommandParser) ParseCommand(args []string) (*ParsedCommand, error) {
|
|||||||
} else {
|
} else {
|
||||||
version, err = pypiGetMatchingVersion(packageName, version)
|
version, err = pypiGetMatchingVersion(packageName, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error resolving version for %s: %s", packageName, err.Error())
|
return nil, ErrFailedToResolveVersion.Wrap(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ func (u *uvCommandParser) ParseCommand(args []string) (*ParsedCommand, error) {
|
|||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
packageName, version, extras, err := pypiParsePackageInfo(pkg)
|
packageName, version, extras, err := pypiParsePackageInfo(pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse package info: %w", err)
|
return nil, ErrFailedToParsePackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != "" {
|
if version != "" {
|
||||||
@@ -266,7 +266,7 @@ func (u *uvCommandParser) ParseCommand(args []string) (*ParsedCommand, error) {
|
|||||||
} else {
|
} else {
|
||||||
version, err = pypiGetMatchingVersion(packageName, version)
|
version, err = pypiGetMatchingVersion(packageName, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error resolving version for %s: %s", packageName, err.Error())
|
return nil, ErrFailedToResolveVersion.Wrap(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,22 +158,22 @@ func getPypiPackageDependencies(packageName, version string, packageTargets []*P
|
|||||||
|
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToFetchPackage
|
return nil, ErrFailedToFetchPackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode == 404 {
|
if res.StatusCode == 404 {
|
||||||
return nil, ErrPackageNotFound
|
return nil, ErrPackageNotFound.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return nil, ErrFailedToFetchPackage
|
return nil, ErrFailedToFetchPackage.Wrap(err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
var pypipkg pypiPackage
|
var pypipkg pypiPackage
|
||||||
err = json.NewDecoder(res.Body).Decode(&pypipkg)
|
err = json.NewDecoder(res.Body).Decode(&pypipkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToParsePackage
|
return nil, ErrFailedToParsePackage.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find if this package has any specified extras in the install targets
|
// Find if this package has any specified extras in the install targets
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package usefulerror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsefulError is an interface that can be implemented for custom error types
|
||||||
|
// that are actually useful for the user. Think of this as a way out of showing
|
||||||
|
// weird internal errors to the user, which actually don't help them
|
||||||
|
type UsefulError interface {
|
||||||
|
// Error returns a string that is useful for the user.
|
||||||
|
// Maintains compatibility with the standard error interface.
|
||||||
|
Error() string
|
||||||
|
|
||||||
|
// HumanError returns a string that is more human-readable.
|
||||||
|
HumanError() string
|
||||||
|
|
||||||
|
// Help returns a string that provides help or guidance specific to the
|
||||||
|
// business logic of the error.
|
||||||
|
Help() string
|
||||||
|
|
||||||
|
// AdditionalHelp returns a string that provides additional help or guidance
|
||||||
|
// This is useful for providing specific tooling related instructions such
|
||||||
|
// as common line flags to use to fix the error.
|
||||||
|
AdditionalHelp() string
|
||||||
|
|
||||||
|
// Code returns a string that can be used to identify the error types
|
||||||
|
// Meant for programmatic use, such as logging or categorization
|
||||||
|
Code() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type usefulErrorBuilder struct {
|
||||||
|
originalError error
|
||||||
|
humanError string
|
||||||
|
help string
|
||||||
|
additionalHelp string
|
||||||
|
code string
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UsefulError = (*usefulErrorBuilder)(nil)
|
||||||
|
|
||||||
|
func Useful() *usefulErrorBuilder {
|
||||||
|
return &usefulErrorBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *usefulErrorBuilder) Wrap(originalError error) *usefulErrorBuilder {
|
||||||
|
b.originalError = originalError
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHumanError sets a string that is more human-readable.
|
||||||
|
func (b *usefulErrorBuilder) WithHumanError(humanError string) *usefulErrorBuilder {
|
||||||
|
b.humanError = humanError
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHelp sets a string that provides additional help or guidance.
|
||||||
|
func (b *usefulErrorBuilder) WithHelp(help string) *usefulErrorBuilder {
|
||||||
|
b.help = help
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCode sets a code that can be used to identify the error types.
|
||||||
|
func (b *usefulErrorBuilder) WithCode(code string) *usefulErrorBuilder {
|
||||||
|
b.code = code
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMsg sets a message that is useful for the user, but not necessarily human-readable.
|
||||||
|
func (b *usefulErrorBuilder) Msg(msg string) *usefulErrorBuilder {
|
||||||
|
b.msg = msg
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAdditionalHelp sets a string that provides additional help or guidance.
|
||||||
|
func (b *usefulErrorBuilder) WithAdditionalHelp(additionalHelp string) *usefulErrorBuilder {
|
||||||
|
b.additionalHelp = additionalHelp
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the standard error interface. It returns the original error's message if present;
|
||||||
|
// otherwise, it returns a constructed message based on the code and msg fields, or "unknown error" if none are set.
|
||||||
|
func (b *usefulErrorBuilder) Error() string {
|
||||||
|
if b.originalError != nil {
|
||||||
|
return b.originalError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.msg == "" {
|
||||||
|
return "unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
|
msgParts := []string{}
|
||||||
|
if b.code != "" {
|
||||||
|
msgParts = append(msgParts, b.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.msg != "" {
|
||||||
|
msgParts = append(msgParts, b.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(msgParts, ": ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HumanError returns a string that is more human-readable.
|
||||||
|
func (b *usefulErrorBuilder) HumanError() string {
|
||||||
|
if b.humanError == "" {
|
||||||
|
return "An error occurred, but no human-readable message is available."
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.humanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help returns a string that provides additional help or guidance.
|
||||||
|
func (b *usefulErrorBuilder) Help() string {
|
||||||
|
if b.help == "" {
|
||||||
|
return "No additional help is available for this error."
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.help
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns a string that can be used to identify the error types.
|
||||||
|
func (b *usefulErrorBuilder) Code() string {
|
||||||
|
if b.code == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdditionalHelp returns a string that provides additional help or guidance.
|
||||||
|
func (b *usefulErrorBuilder) AdditionalHelp() string {
|
||||||
|
if b.additionalHelp == "" {
|
||||||
|
return "No additional help is available for this error."
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.additionalHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsUsefulError attempts to convert a given error into a UsefulError.
|
||||||
|
func AsUsefulError(err error) (UsefulError, bool) {
|
||||||
|
if err == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var usefulErr *usefulErrorBuilder
|
||||||
|
if errors.As(err, &usefulErr) {
|
||||||
|
return usefulErr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if usefulErr, ok := err.(UsefulError); ok {
|
||||||
|
return usefulErr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
package usefulerror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_Error(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
builder func() *usefulErrorBuilder
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with original error",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().Wrap(errors.New("original error"))
|
||||||
|
},
|
||||||
|
expected: "original error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with msg only",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().Msg("test message")
|
||||||
|
},
|
||||||
|
expected: "test message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with code and msg",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithCode("TEST001").Msg("test message")
|
||||||
|
},
|
||||||
|
expected: "TEST001: test message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with code only",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithCode("TEST001")
|
||||||
|
},
|
||||||
|
expected: "unknown error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty builder",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful()
|
||||||
|
},
|
||||||
|
expected: "unknown error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.builder()
|
||||||
|
assert.Equal(t, tt.expected, err.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_HumanError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
builder func() *usefulErrorBuilder
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with human error set",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithHumanError("Something went wrong")
|
||||||
|
},
|
||||||
|
expected: "Something went wrong",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty human error",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful()
|
||||||
|
},
|
||||||
|
expected: "An error occurred, but no human-readable message is available.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.builder()
|
||||||
|
assert.Equal(t, tt.expected, err.HumanError())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_Help(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
builder func() *usefulErrorBuilder
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with help set",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithHelp("Try running with --verbose flag")
|
||||||
|
},
|
||||||
|
expected: "Try running with --verbose flag",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty help",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful()
|
||||||
|
},
|
||||||
|
expected: "No additional help is available for this error.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.builder()
|
||||||
|
assert.Equal(t, tt.expected, err.Help())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_AdditionalHelp(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
builder func() *usefulErrorBuilder
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with additional help set",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithAdditionalHelp("Use --force to override")
|
||||||
|
},
|
||||||
|
expected: "Use --force to override",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty additional help",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful()
|
||||||
|
},
|
||||||
|
expected: "No additional help is available for this error.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.builder()
|
||||||
|
assert.Equal(t, tt.expected, err.AdditionalHelp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_Code(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
builder func() *usefulErrorBuilder
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with code set",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful().WithCode("ERR001")
|
||||||
|
},
|
||||||
|
expected: "ERR001",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty code",
|
||||||
|
builder: func() *usefulErrorBuilder {
|
||||||
|
return Useful()
|
||||||
|
},
|
||||||
|
expected: "unknown",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.builder()
|
||||||
|
assert.Equal(t, tt.expected, err.Code())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_ChainedMethods(t *testing.T) {
|
||||||
|
err := Useful().
|
||||||
|
WithCode("TEST001").
|
||||||
|
Msg("test message").
|
||||||
|
WithHumanError("User friendly error").
|
||||||
|
WithHelp("Try this fix").
|
||||||
|
WithAdditionalHelp("Or try this")
|
||||||
|
|
||||||
|
assert.Equal(t, "TEST001: test message", err.Error())
|
||||||
|
assert.Equal(t, "User friendly error", err.HumanError())
|
||||||
|
assert.Equal(t, "Try this fix", err.Help())
|
||||||
|
assert.Equal(t, "Or try this", err.AdditionalHelp())
|
||||||
|
assert.Equal(t, "TEST001", err.Code())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsUsefulError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input error
|
||||||
|
expectOk bool
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil error",
|
||||||
|
input: nil,
|
||||||
|
expectOk: false,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "useful error builder",
|
||||||
|
input: Useful().Msg("test"),
|
||||||
|
expectOk: true,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "regular error",
|
||||||
|
input: errors.New("regular error"),
|
||||||
|
expectOk: false,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, ok := AsUsefulError(tt.input)
|
||||||
|
assert.Equal(t, tt.expectOk, ok)
|
||||||
|
if tt.expectOk {
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Implements(t, (*UsefulError)(nil), result)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_ImplementsUsefulError(t *testing.T) {
|
||||||
|
var _ UsefulError = (*usefulErrorBuilder)(nil)
|
||||||
|
|
||||||
|
builder := Useful()
|
||||||
|
assert.Implements(t, (*UsefulError)(nil), builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_Wrap(t *testing.T) {
|
||||||
|
originalErr := errors.New("original error")
|
||||||
|
wrappedErr := Useful().Wrap(originalErr)
|
||||||
|
|
||||||
|
assert.Equal(t, "original error", wrappedErr.Error())
|
||||||
|
assert.Equal(t, "An error occurred, but no human-readable message is available.", wrappedErr.HumanError())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsefulErrorBuilder_ComplexScenario(t *testing.T) {
|
||||||
|
originalErr := errors.New("file not found")
|
||||||
|
|
||||||
|
err := Useful().
|
||||||
|
Wrap(originalErr).
|
||||||
|
WithCode("FILE001").
|
||||||
|
WithHumanError("The configuration file could not be found").
|
||||||
|
WithHelp("Make sure the config file exists in the current directory").
|
||||||
|
WithAdditionalHelp("Use --config flag to specify a different location")
|
||||||
|
|
||||||
|
assert.Equal(t, "file not found", err.Error())
|
||||||
|
assert.Equal(t, "The configuration file could not be found", err.HumanError())
|
||||||
|
assert.Equal(t, "Make sure the config file exists in the current directory", err.Help())
|
||||||
|
assert.Equal(t, "Use --config flag to specify a different location", err.AdditionalHelp())
|
||||||
|
assert.Equal(t, "FILE001", err.Code())
|
||||||
|
|
||||||
|
usefulErr, ok := AsUsefulError(err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, err, usefulErr)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user