gitlink-cli/internal/errors/errors.go

116 lines
3.4 KiB
Go

package errors
import (
"fmt"
"strings"
)
// ErrorKind categorizes errors by user-actionability.
type ErrorKind string
const (
KindAuth ErrorKind = "auth" // Login/token issues — user can re-login
KindInput ErrorKind = "input" // Parameter issues — user can fix arguments
KindConfig ErrorKind = "config" // Config file issues — user can edit config
KindNetwork ErrorKind = "network" // Network issues — user can check/retry
KindGit ErrorKind = "git" // Git repo issues — user needs correct directory
KindServer ErrorKind = "server" // Server-side error — user should wait or contact admin
KindNotFound ErrorKind = "not_found" // Resource not found — user can check ID
KindForbidden ErrorKind = "forbidden" // Permission denied — user can request access
KindUnknown ErrorKind = "unknown" // Unclassified error
)
// CLIError is the unified CLI error type with multi-layered information.
type CLIError struct {
Kind ErrorKind // Error category for programmatic handling
Message string // Human-readable description of what went wrong
Detail string // Low-level technical detail (shown in debug mode)
Suggestion string // Actionable advice for the user
Command string // The command that triggered the error (e.g., "issue +create")
Cause error // The underlying error
}
func (e *CLIError) Error() string {
var b strings.Builder
// Header line: kind + command
b.WriteString(string(e.Kind))
b.WriteString(" error")
if e.Command != "" {
b.WriteString(" — ")
b.WriteString(e.Command)
}
// Body: message
if e.Message != "" {
b.WriteString("\n\n reason: ")
b.WriteString(e.Message)
}
// Suggestion
if e.Suggestion != "" {
b.WriteString("\n suggestion: ")
b.WriteString(e.Suggestion)
}
// Detail (always included in Error() so users see the raw cause)
if e.Detail != "" {
b.WriteString("\n detail: ")
b.WriteString(e.Detail)
}
return b.String()
}
func (e *CLIError) Unwrap() error {
return e.Cause
}
// New creates a CLIError with the given parameters.
func New(kind ErrorKind, message, suggestion string) *CLIError {
return &CLIError{
Kind: kind,
Message: message,
Suggestion: suggestion,
}
}
// Wrap creates a CLIError that wraps an underlying cause.
func Wrap(kind ErrorKind, message, suggestion string, cause error) *CLIError {
return &CLIError{
Kind: kind,
Message: message,
Suggestion: suggestion,
Cause: cause,
Detail: cause.Error(),
}
}
// WithCommand sets the command context on the error.
func (e *CLIError) WithCommand(cmd string) *CLIError {
e.Command = cmd
return e
}
// InputError is a convenience constructor for parameter errors.
func InputError(message, suggestion string) *CLIError {
return New(KindInput, message, suggestion)
}
// AuthError is a convenience constructor for authentication errors.
func AuthError(message, suggestion string) *CLIError {
return New(KindAuth, message, suggestion)
}
// ConfigError creates a config-related error with the config file path in the suggestion.
func ConfigError(message string, cause error) *CLIError {
return Wrap(KindConfig, message,
fmt.Sprintf("检查配置文件 %s 是否正确", configPathPlaceholder()), cause)
}
// configPathPlaceholder avoids circular import; the actual path will be resolved
// in output formatting.
func configPathPlaceholder() string {
return "~/.config/gitlink-cli/config.yaml"
}