-
Notifications
You must be signed in to change notification settings - Fork 1
/
errors.go
118 lines (106 loc) · 3.11 KB
/
errors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package main
import (
"errors"
"fmt"
"os"
)
// ErrorHandler is the type of function called when dfm encounters an error with
// a particular file. The encountered error will be passed in. Dfm's behavior is
// based on the result of the handler. If the handler returns nil, dfm will
// ignore the failure and continue. If the handler returns `dfm.Retry`, dfm will
// attempt the operation again (and call the handler with the new error, if
// any). If the handler returns anything else, dfm will abort and return the
// error.
type ErrorHandler func(err *FileError) error
// Retry is used by ErrorHandler to signal to dfm to attempt the file operation
// again. The type cast is to suppress golint complaining about the variable not
// being named ErrRetry.
var Retry = errors.New("retry this file").(error)
// ErrNotNeeded means that the file was not updated because it was already up to
// date. This is only used in logging.
var ErrNotNeeded = errors.New("already up to date")
// IsNotNeeded checks if the given error is ErrNotNeeded, after unwrapping
func IsNotNeeded(err error) bool {
if err == ErrNotNeeded {
return true
}
if fileErr, ok := err.(*FileError); ok {
if fileErr.Cause() == ErrNotNeeded {
return true
}
}
return false
}
// FileError represents any error dfm encountered while managing files.
type FileError struct {
Message string
Filename string
cause error
}
// NewFileError creates a new FileError for the provided file.
func NewFileError(filename string, message string) *FileError {
return &FileError{
Message: message,
Filename: filename,
}
}
// NewFileErrorf creates a new FileError for the provided file with a format
// string.
func NewFileErrorf(filename string, message string, args ...interface{}) *FileError {
return &FileError{
Message: fmt.Sprintf(message, args...),
Filename: filename,
}
}
// WrapFileError takes an existing error and creates a new FileError for the
// given file.
func WrapFileError(cause error, filename string) *FileError {
if fileErr, ok := cause.(*FileError); ok {
return fileErr
}
var message string
switch err := cause.(type) {
case *os.PathError:
message = err.Err.Error()
case *os.LinkError:
message = err.Err.Error()
default:
message = cause.Error()
}
return &FileError{
Message: message,
Filename: filename,
cause: cause,
}
}
func (err *FileError) Error() string {
return fmt.Sprintf("%s: %s", err.Filename, err.Message)
}
// Cause is the underlying cause of the error
func (err *FileError) Cause() error {
if err.cause == nil {
return nil
}
return err.cause
}
// processWithRetry calls the given function one or more times. If the function
// returns an error, the ErrorHandler can indicate to retry the function again.
func processWithRetry(
errorHandler ErrorHandler,
process func() *FileError,
) (skipped, aborted bool, reason error) {
retry:
rawErr := process()
if rawErr == nil {
return false, false, nil
} else if IsNotNeeded(rawErr) {
return true, false, rawErr
}
newErr := errorHandler(rawErr)
if newErr == nil {
return true, false, rawErr
} else if newErr == Retry {
goto retry
}
return false, true, newErr
}