diff --git a/builder.go b/builder.go index 1cc7a25..42dd955 100644 --- a/builder.go +++ b/builder.go @@ -99,6 +99,13 @@ func (eb ErrorBuilder) Create() *Error { transparent: eb.isTransparent, stackTrace: eb.assembleStackTrace(), } + if err.transparent { + if cause := Cast(eb.cause); cause != nil { + err.errorType = cause.errorType + } else { + err.errorType = foreignType + } + } return err } diff --git a/builder_test.go b/builder_test.go index d623bd3..66df776 100644 --- a/builder_test.go +++ b/builder_test.go @@ -15,8 +15,8 @@ func TestBuilderTransparency(t *testing.T) { }) t.Run("RawWithModifier", func(t *testing.T) { - err := NewErrorBuilder(testTypeTransparent).WithCause(errors.New("bad thing")).Create() - require.False(t, err.IsOfType(testType)) - require.NotEqual(t, testType, err.Type()) + err := NewErrorBuilder(transparentWrapper).WithCause(errors.New("bad thing")).Create() + require.False(t, err.IsOfType(transparentWrapper)) + require.NotEqual(t, transparentWrapper, err.Type()) }) } diff --git a/error.go b/error.go index bbff82d..87f3ebe 100644 --- a/error.go +++ b/error.go @@ -91,32 +91,14 @@ func (e *Error) Property(key Property) (interface{}, bool) { // Traits are always properties of a type rather than of an instance, so trait check is an alternative to a type check. // This alternative is preferable, though, as it is less brittle and generally creates less of a dependency. func (e *Error) HasTrait(key Trait) bool { - cause := e - for cause != nil { - if !cause.transparent { - return cause.errorType.HasTrait(key) - } - - cause = Cast(cause.Cause()) - } - - return false + return e.errorType.HasTrait(key) } // IsOfType is a proper type check for an error. // It takes the transparency and error types hierarchy into account, // so that type check against any supertype of the original cause passes. func (e *Error) IsOfType(t *Type) bool { - cause := e - for cause != nil { - if !cause.transparent { - return cause.errorType.IsOfType(t) - } - - cause = Cast(cause.Cause()) - } - - return false + return e.errorType.IsOfType(t) } // Type returns the exact type of this error. @@ -127,16 +109,7 @@ func (e *Error) IsOfType(t *Type) bool { // This may happen if a type is checked against one of its supertypes, for example. // Therefore, handle direct type checks with care or avoid it altogether and use TypeSwitch() or IsForType() instead. func (e *Error) Type() *Type { - cause := e - for cause != nil { - if !cause.transparent { - return cause.errorType - } - - cause = Cast(cause.Cause()) - } - - return foreignType + return e.errorType } // Message returns a message of this particular error, disregarding the cause. diff --git a/error_test.go b/error_test.go index a8f6262..9c95077 100644 --- a/error_test.go +++ b/error_test.go @@ -9,14 +9,13 @@ import ( ) var ( - testNamespace = NewNamespace("foo") - testType = testNamespace.NewType("bar") - testTypeSilent = testType.NewSubtype("silent").ApplyModifiers(TypeModifierOmitStackTrace) - testTypeTransparent = testType.NewSubtype("transparent").ApplyModifiers(TypeModifierTransparent) - testSubtype0 = testType.NewSubtype("internal") - testSubtype1 = testSubtype0.NewSubtype("wat") - testTypeBar1 = testNamespace.NewType("bar1") - testTypeBar2 = testNamespace.NewType("bar2") + testNamespace = NewNamespace("foo") + testType = testNamespace.NewType("bar") + testTypeSilent = testType.NewSubtype("silent").ApplyModifiers(TypeModifierOmitStackTrace) + testSubtype0 = testType.NewSubtype("internal") + testSubtype1 = testSubtype0.NewSubtype("wat") + testTypeBar1 = testNamespace.NewType("bar1") + testTypeBar2 = testNamespace.NewType("bar2") ) func TestError(t *testing.T) { diff --git a/modifier.go b/modifier.go index 601329f..2de95f3 100644 --- a/modifier.go +++ b/modifier.go @@ -7,8 +7,8 @@ package errorx type TypeModifier int const ( - // TypeModifierTransparent is a type modifier; an error type with such modifier creates transparent wrappers by default - TypeModifierTransparent TypeModifier = 1 + // typeModifierTransparent is a type modifier; an error type with such modifier creates transparent wrappers by default + typeModifierTransparent TypeModifier = 1 // TypeModifierOmitStackTrace is a type modifier; an error type with such modifier omits the stack trace collection upon creation of an error instance TypeModifierOmitStackTrace TypeModifier = 2 ) @@ -49,7 +49,7 @@ func newTypeModifiers(modifiers ...TypeModifier) modifiers { switch modifier { case TypeModifierOmitStackTrace: m.omitStackTrace = true - case TypeModifierTransparent: + case typeModifierTransparent: m.transparent = true } } diff --git a/modifier_test.go b/modifier_test.go index 3330db6..f6732e8 100644 --- a/modifier_test.go +++ b/modifier_test.go @@ -9,7 +9,7 @@ import ( var ( modifierTestNamespace = NewNamespace("modifier") - modifierTestNamespaceTransparent = NewNamespace("modifierTransparent").ApplyModifiers(TypeModifierTransparent) + modifierTestNamespaceTransparent = NewNamespace("modifierTransparent").ApplyModifiers(typeModifierTransparent) modifierTestNamespaceTransparentChild = modifierTestNamespaceTransparent.NewSubNamespace("child") modifierTestError = modifierTestNamespace.NewType("foo") modifierTestErrorNoTrace = modifierTestNamespace.NewType("bar").ApplyModifiers(TypeModifierOmitStackTrace) diff --git a/panic.go b/panic.go index f10dfab..fdc6265 100644 --- a/panic.go +++ b/panic.go @@ -47,7 +47,7 @@ func ErrorFromPanic(recoverResult interface{}) (error, bool) { func newPanicErrorWrapper(err error) *panicErrorWrapper { originalError, errWithStackTrace := err, err if typedErr, ok := errWithStackTrace.(*Error); !ok || typedErr.stackTrace == nil { - builder := NewErrorBuilder(panicPayloadWrap).WithConditionallyFormattedMessage("").WithCause(err) + builder := NewErrorBuilder(transparentWrapper).WithConditionallyFormattedMessage("").WithCause(err) errWithStackTrace = builder.Create() } @@ -70,6 +70,3 @@ func (w *panicErrorWrapper) Error() string { func (w *panicErrorWrapper) String() string { return w.Error() } - -// Only required to transform panic into error while preserving the stack trace -var panicPayloadWrap = syntheticErrors.NewType("panic").ApplyModifiers(TypeModifierTransparent) diff --git a/wrap.go b/wrap.go index 53b7a29..0c21929 100644 --- a/wrap.go +++ b/wrap.go @@ -7,11 +7,9 @@ var ( // Private error type for non-errors errors, used as a not-nil substitute that cannot be type-checked directly foreignType = syntheticErrors.NewType("foreign") // Private error type used as a universal wrapper, meant to ann nothing at all to the error apart from some message - transparentWrapper = syntheticErrors.NewType("decorate").ApplyModifiers(TypeModifierTransparent) + transparentWrapper = syntheticErrors.NewType("decorate").ApplyModifiers(typeModifierTransparent) // Private error type used as a densely opaque wrapper which hides both the original error and its own type opaqueWrapper = syntheticErrors.NewType("wrap") - // Private error type used for stack trace capture - stackTraceWrapper = syntheticErrors.NewType("stacktrace").ApplyModifiers(TypeModifierTransparent) ) // Decorate allows to pass some text info along with a message, leaving its semantics totally intact. @@ -22,7 +20,6 @@ func Decorate(err error, message string, args ...interface{}) *Error { return NewErrorBuilder(transparentWrapper). WithConditionallyFormattedMessage(message, args...). WithCause(err). - Transparent(). Create() } @@ -34,7 +31,6 @@ func EnhanceStackTrace(err error, message string, args ...interface{}) *Error { return NewErrorBuilder(transparentWrapper). WithConditionallyFormattedMessage(message, args...). WithCause(err). - Transparent(). EnhanceStackTrace(). Create() } @@ -47,10 +43,9 @@ func EnsureStackTrace(err error) *Error { return typedErr } - return NewErrorBuilder(stackTraceWrapper). + return NewErrorBuilder(transparentWrapper). WithConditionallyFormattedMessage(""). WithCause(err). - Transparent(). EnhanceStackTrace(). Create() }