Skip to content

Commit 1166a46

Browse files
committed
✨ Added more helpers with regards to failure
1 parent 3b788a7 commit 1166a46

File tree

4 files changed

+78
-10
lines changed

4 files changed

+78
-10
lines changed

utils/commonerrors/errors.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"errors"
1212
"fmt"
1313
"strings"
14+
15+
"github.com/hashicorp/go-multierror"
1416
)
1517

1618
// List of common errors used to qualify and categorise go errors
@@ -46,19 +48,25 @@ var (
4648
ErrMalicious = errors.New("suspected malicious intent")
4749
ErrOutOfRange = errors.New("out of range")
4850
// ErrFailed should be used as a generic error where an error is an expected and valid state.
49-
// For example a failing command may cause subproccess.Execute to return an error if the command exits with 1 but
50-
// this wouldn't be a system error and you might want to distinguish between this and the subprocess wrapper erroring
51+
// For example a failing command may cause subprocess.Execute to return an error if the command exits with 1 but
52+
// this wouldn't be a system error, and you might want to distinguish between this and the subprocess wrapper erroring
5153
// when you pass the message up the stack.
52-
ErrFailed = errors.New("failed")
54+
ErrFailed = errors.New(failureStr)
5355
// ErrWarning is a generic error that can be used when an error should be raised but it shouldn't necessary be
5456
// passed up the chain, for example in cases where an error should be logged but the program should continue. In
5557
// these situations it should be handled immediately and then ignored/set to nil.
5658
ErrWarning = errors.New(warningStr)
5759
)
5860

59-
const warningStr = "warning"
61+
const (
62+
warningStr = "warning"
63+
failureStr = "failed"
64+
)
6065

61-
var warningStrPrepend = fmt.Sprintf("%v: ", warningStr)
66+
var (
67+
warningStrPrepend = fmt.Sprintf("%v%v ", warningStr, string(TypeReasonErrorSeparator))
68+
failureStrPrepend = fmt.Sprintf("%v%v ", failureStr, string(TypeReasonErrorSeparator))
69+
)
6270

6371
// IsCommonError returns whether an error is a commonerror
6472
func IsCommonError(target error) bool {
@@ -210,11 +218,24 @@ func ConvertContextError(err error) error {
210218

211219
// IsWarning will return whether an error is actually a warning
212220
func IsWarning(target error) bool {
221+
return isSpecialCase(target, ErrWarning, warningStrPrepend)
222+
}
223+
224+
// IsFailure returns whether an error is unexpected (i.e. deviation from an expected state) but not a system error e.g. test failure
225+
func IsFailure(target error) bool {
226+
return isSpecialCase(target, ErrFailed, failureStrPrepend)
227+
}
228+
229+
func isSpecialCase(target, specialErrorCase error, prefix string) bool {
213230
if target == nil {
214231
return false
215232
}
216233

217-
if Any(target, ErrWarning) {
234+
if Any(target, specialErrorCase) {
235+
return true
236+
}
237+
238+
if strings.HasPrefix(target.Error(), prefix) {
218239
return true
219240
}
220241

@@ -223,7 +244,33 @@ func IsWarning(target error) bool {
223244
return false
224245
}
225246

226-
return strings.TrimSuffix(target.Error(), underlyingErr.Error()) == warningStrPrepend
247+
return strings.TrimSuffix(target.Error(), underlyingErr.Error()) == prefix
248+
}
249+
250+
// MarkAsFailure will tent an error as failure. It will retain its original error type but IsFailure should return true.
251+
func MarkAsFailure(err error) error {
252+
if Any(err, nil, ErrFailed) {
253+
return err
254+
}
255+
result := multierror.Append(err, ErrFailed)
256+
result.ErrorFormat = func(e []error) string {
257+
builder := strings.Builder{}
258+
_, _ = builder.WriteString(failureStr)
259+
for i := range e {
260+
if None(e[i], nil, ErrFailed) {
261+
_, _ = builder.WriteString(string(TypeReasonErrorSeparator))
262+
_, _ = builder.WriteString(" ")
263+
_, _ = builder.WriteString(e[i].Error())
264+
}
265+
}
266+
return builder.String()
267+
}
268+
return result.ErrorOrNil()
269+
}
270+
271+
// NewFailure creates a failure object.
272+
func NewFailure(msgFormat string, args ...any) error {
273+
return Newf(ErrFailed, msgFormat, args...)
227274
}
228275

229276
// NewWarning will create a warning wrapper around an existing commonerror so that it can be easily recovered. If the
@@ -300,7 +347,8 @@ func Errorf(targetErr error, format string, args ...any) error {
300347
}
301348
}
302349

303-
// WrapError wraps an error into a particular targetError. However, if the original error has to do with a contextual error (i.e. ErrCancelled or ErrTimeout), it will be passed through without having is type changed.
350+
// WrapError wraps an error into a particular targetError. However, if the original error has to do with a contextual error (i.e. ErrCancelled or ErrTimeout) or should be considered as a failure rather than an error, it will be passed through without having its type changed.
351+
// Same is true with warnings.
304352
// This method should be used to safely wrap errors without losing information about context control information.
305353
// If the target error is not set, the wrapped error will be of type ErrUnknown.
306354
func WrapError(targetError, originalError error, msg string) error {

utils/commonerrors/errors_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,22 @@ func TestIsWarning(t *testing.T) {
128128
assert.True(t, IsWarning(ErrWarning))
129129
assert.False(t, IsWarning(ErrUnexpected))
130130
assert.False(t, IsWarning(nil))
131-
assert.True(t, IsWarning(fmt.Errorf("%w: i am i warning", ErrWarning)))
132-
assert.True(t, IsWarning(fmt.Errorf("%w: i am i warning too: %v", ErrWarning, ErrUnknown)))
131+
assert.True(t, IsWarning(fmt.Errorf("%w: i am a warning", ErrWarning)))
132+
assert.True(t, IsWarning(fmt.Errorf("%w: i am a warning too: %v", ErrWarning, ErrUnknown)))
133+
}
134+
135+
func TestIsFailure(t *testing.T) {
136+
assert.True(t, IsFailure(ErrFailed))
137+
assert.False(t, IsFailure(ErrUnexpected))
138+
assert.True(t, IsFailure(MarkAsFailure(ErrUnexpected)))
139+
assert.True(t, Any(MarkAsFailure(ErrUnexpected), ErrUnexpected))
140+
assert.True(t, Any(MarkAsFailure(ErrUnexpected), ErrFailed))
141+
assert.False(t, IsFailure(nil))
142+
assert.True(t, IsFailure(NewFailure("i am a failure and I know it")))
143+
assert.True(t, IsFailure(fmt.Errorf("%w: i am a failure", ErrFailed)))
144+
assert.True(t, IsFailure(fmt.Errorf("%w: i am a failure too: %v", ErrFailed, ErrUnknown)))
145+
assert.True(t, IsFailure(fmt.Errorf("%v: %w", ErrFailed, ErrUnknown)))
146+
assert.True(t, IsFailure(fmt.Errorf("%v: %w: i am a failure too too", ErrFailed, ErrUnknown)))
133147
}
134148

135149
func TestNewWarning(t *testing.T) {

utils/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
2727
github.com/hashicorp/go-cleanhttp v0.5.2
2828
github.com/hashicorp/go-hclog v1.6.3
29+
github.com/hashicorp/go-multierror v1.1.1
2930
github.com/hashicorp/go-retryablehttp v0.7.8
3031
github.com/iamacarpet/go-win64api v0.0.0-20240507095429-873e84e85847
3132
github.com/joho/godotenv v1.5.1
@@ -73,6 +74,7 @@ require (
7374
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
7475
github.com/google/cabbie v1.0.2 // indirect
7576
github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect
77+
github.com/hashicorp/errwrap v1.0.0 // indirect
7678
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
7779
github.com/kevinburke/ssh_config v1.2.0 // indirect
7880
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect

utils/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,14 @@ github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3
126126
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
127127
github.com/google/winops v0.0.0-20210803215038-c8511b84de2b/go.mod h1:ShbX8v8clPm/3chw9zHVwtW3QhrFpL8mXOwNxClt4pg=
128128
github.com/groob/plist v0.0.0-20210519001750-9f754062e6d6/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw=
129+
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
130+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
129131
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
130132
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
131133
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
132134
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
135+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
136+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
133137
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
134138
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
135139
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

0 commit comments

Comments
 (0)