mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-03-13 06:00:23 +00:00
feat: go 1.25.5, dependency updates, renamed internal/errors (#129)
* 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
This commit is contained in:
195
internal/apperrors/errors.go
Normal file
195
internal/apperrors/errors.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user