@@ -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
6472func 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
212220func 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.
306354func WrapError (targetError , originalError error , msg string ) error {
0 commit comments