mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
* feat: rename internal/errors to internal/apperrors * fix(tests): clear env values before using in tests * feat: rename internal/errors to internal/apperrors * chore(deps): update go and all dependencies * chore: remove renovate from pre-commit, formatting * chore: sonarcloud fixes * feat: consolidate constants to appconstants/constants.go * chore: sonarcloud fixes * feat: simplification, deduplication, test utils * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: clean up * fix: config discovery, const deduplication * chore: fixes
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
// Package apperrors provides enhanced error types with contextual information and suggestions.
|
|
package apperrors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/ivuorinen/gh-action-readme/appconstants"
|
|
)
|
|
|
|
// Sentinel errors for typed error checking.
|
|
var (
|
|
// ErrFileNotFound indicates a file was not found.
|
|
ErrFileNotFound = errors.New("file not found")
|
|
// ErrPermissionDenied indicates a permission error.
|
|
ErrPermissionDenied = errors.New("permission denied")
|
|
// ErrInvalidYAML indicates YAML parsing failed.
|
|
ErrInvalidYAML = errors.New("invalid YAML")
|
|
// ErrGitHubAPI indicates a GitHub API error.
|
|
ErrGitHubAPI = errors.New("GitHub API error")
|
|
// ErrConfiguration indicates a configuration error.
|
|
ErrConfiguration = errors.New("configuration error")
|
|
)
|
|
|
|
// ContextualError provides enhanced error information with actionable suggestions.
|
|
type ContextualError struct {
|
|
Code appconstants.ErrorCode
|
|
Err error
|
|
Context string
|
|
Suggestions []string
|
|
HelpURL string
|
|
Details map[string]string
|
|
}
|
|
|
|
// Error implements the error interface.
|
|
func (ce *ContextualError) Error() string {
|
|
var b strings.Builder
|
|
|
|
// Primary error message
|
|
if ce.Context != "" {
|
|
b.WriteString(fmt.Sprintf("%s: %v", ce.Context, ce.Err))
|
|
} else {
|
|
b.WriteString(ce.Err.Error())
|
|
}
|
|
|
|
// Add error code for reference
|
|
b.WriteString(fmt.Sprintf(" [%s]", ce.Code))
|
|
|
|
// Add details if available
|
|
if len(ce.Details) > 0 {
|
|
b.WriteString("\n\nDetails:")
|
|
for key, value := range ce.Details {
|
|
b.WriteString(fmt.Sprintf("\n %s: %s", key, value))
|
|
}
|
|
}
|
|
|
|
// Add suggestions
|
|
if len(ce.Suggestions) > 0 {
|
|
b.WriteString("\n\nSuggestions:")
|
|
for _, suggestion := range ce.Suggestions {
|
|
b.WriteString("\n • " + suggestion)
|
|
}
|
|
}
|
|
|
|
// Add help URL
|
|
if ce.HelpURL != "" {
|
|
b.WriteString("\n\nFor more help: " + ce.HelpURL)
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// Unwrap returns the wrapped error.
|
|
func (ce *ContextualError) Unwrap() error {
|
|
return ce.Err
|
|
}
|
|
|
|
// Is implements errors.Is support.
|
|
func (ce *ContextualError) Is(target error) bool {
|
|
if target == nil {
|
|
return false
|
|
}
|
|
|
|
// Check if target is also a ContextualError with same code
|
|
if targetCE, ok := target.(*ContextualError); ok {
|
|
return ce.Code == targetCE.Code
|
|
}
|
|
|
|
// Check wrapped error
|
|
return errors.Is(ce.Err, target)
|
|
}
|
|
|
|
// New creates a new ContextualError with the given code and message.
|
|
func New(code appconstants.ErrorCode, message string) *ContextualError {
|
|
return &ContextualError{
|
|
Code: code,
|
|
Err: errors.New(message),
|
|
}
|
|
}
|
|
|
|
// Wrap wraps an existing error with contextual information.
|
|
func Wrap(err error, code appconstants.ErrorCode, context string) *ContextualError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// If already a ContextualError, preserve existing info by creating a copy
|
|
if ce, ok := err.(*ContextualError); ok {
|
|
// Create a copy to avoid mutating the original
|
|
errCopy := &ContextualError{
|
|
Code: ce.Code,
|
|
Err: ce.Err,
|
|
Context: ce.Context,
|
|
Suggestions: ce.Suggestions,
|
|
HelpURL: ce.HelpURL,
|
|
Details: make(map[string]string),
|
|
}
|
|
|
|
// Copy details map
|
|
for k, v := range ce.Details {
|
|
errCopy.Details[k] = v
|
|
}
|
|
|
|
// Only update if not already set
|
|
if errCopy.Code == appconstants.ErrCodeUnknown {
|
|
errCopy.Code = code
|
|
}
|
|
if errCopy.Context == "" {
|
|
errCopy.Context = context
|
|
}
|
|
|
|
return errCopy
|
|
}
|
|
|
|
return &ContextualError{
|
|
Code: code,
|
|
Err: err,
|
|
Context: context,
|
|
}
|
|
}
|
|
|
|
// WithSuggestions adds suggestions to a ContextualError.
|
|
func (ce *ContextualError) WithSuggestions(suggestions ...string) *ContextualError {
|
|
ce.Suggestions = append(ce.Suggestions, suggestions...)
|
|
|
|
return ce
|
|
}
|
|
|
|
// WithDetails adds detail key-value pairs to a ContextualError.
|
|
func (ce *ContextualError) WithDetails(details map[string]string) *ContextualError {
|
|
if ce.Details == nil {
|
|
ce.Details = make(map[string]string)
|
|
}
|
|
for k, v := range details {
|
|
ce.Details[k] = v
|
|
}
|
|
|
|
return ce
|
|
}
|
|
|
|
// WithHelpURL adds a help URL to a ContextualError.
|
|
func (ce *ContextualError) WithHelpURL(url string) *ContextualError {
|
|
ce.HelpURL = url
|
|
|
|
return ce
|
|
}
|
|
|
|
// GetHelpURL returns a help URL for the given error code.
|
|
func GetHelpURL(code appconstants.ErrorCode) string {
|
|
baseURL := "https://github.com/ivuorinen/gh-action-readme/blob/main/docs/troubleshooting.md"
|
|
|
|
anchors := map[appconstants.ErrorCode]string{
|
|
appconstants.ErrCodeFileNotFound: "#file-not-found",
|
|
appconstants.ErrCodePermission: "#permission-denied",
|
|
appconstants.ErrCodeInvalidYAML: "#invalid-yaml",
|
|
appconstants.ErrCodeInvalidAction: "#invalid-action-file",
|
|
appconstants.ErrCodeNoActionFiles: "#no-action-files",
|
|
appconstants.ErrCodeGitHubAPI: "#github-api-errors",
|
|
appconstants.ErrCodeGitHubRateLimit: "#rate-limit-exceeded",
|
|
appconstants.ErrCodeGitHubAuth: "#authentication-errors",
|
|
appconstants.ErrCodeConfiguration: "#configuration-errors",
|
|
appconstants.ErrCodeValidation: "#validation-errors",
|
|
appconstants.ErrCodeTemplateRender: "#template-errors",
|
|
appconstants.ErrCodeFileWrite: "#file-write-errors",
|
|
appconstants.ErrCodeDependencyAnalysis: "#dependency-analysis",
|
|
appconstants.ErrCodeCacheAccess: "#cache-errors",
|
|
}
|
|
|
|
if anchor, ok := anchors[code]; ok {
|
|
return baseURL + anchor
|
|
}
|
|
|
|
return baseURL
|
|
}
|