Problem
errors.Error discards the underlying cause. Two related gaps:
1. No Unwrap(). Wrap/Wrapf flatten the cause to a string:
func Wrap(code ErrCode, msg string) error {
return &Error{code: code, msg: msg}
}
Call sites do errors.Wrap(errors.NotFound, err.Error()), so the original error is gone. errors.Is(err, context.DeadlineExceeded), errors.As(err, &netErr), and mongo.IsNetworkError(err) all stop working the moment an error crosses into this package.
2. GetErrCode uses a bare type assertion, not errors.As:
func GetErrCode(err error) ErrCode {
val, ok := err.(*Error)
if ok {
return ErrCode(val.code)
}
return Unknown
}
So a *Error wrapped by anything else — fmt.Errorf("%w", ...) included — reports Unknown. Codes only survive while the *Error is the outermost error.
Why it matters more now
#121 adds errors.Unavailable and makes interpretMongoError classify transient MongoDB failures into it. That makes correct one-shot classification load-bearing: if isTransientMongoError misjudges a given driver error, no downstream caller can recover the original to re-check it, because the cause has already been flattened to a string. Before #121 the codes were coarse enough that this rarely bit; afterwards, callers are explicitly encouraged to branch on IsUnavailable vs IsNotFound.
Proposal
func (e *Error) Unwrap() error { return e.cause }
// WrapErr preserves the cause chain alongside the code.
func WrapErr(code ErrCode, err error, msg string) error {
return &Error{code: code, msg: msg, cause: err}
}
and switch GetErrCode to errors.As:
func GetErrCode(err error) ErrCode {
var e *Error
if errors.As(err, &e) {
return ErrCode(e.code)
}
return Unknown
}
Existing Wrap/Wrapf stay as-is for source compatibility (cause simply nil), so this is additive. Migrating call sites to WrapErr can happen incrementally.
Notes
Deliberately scoped out of #121 — this touches every Wrap call site in the repo and has its own blast radius. Filing separately so it can be evaluated on its own.
Problem
errors.Errordiscards the underlying cause. Two related gaps:1. No
Unwrap().Wrap/Wrapfflatten the cause to a string:Call sites do
errors.Wrap(errors.NotFound, err.Error()), so the original error is gone.errors.Is(err, context.DeadlineExceeded),errors.As(err, &netErr), andmongo.IsNetworkError(err)all stop working the moment an error crosses into this package.2.
GetErrCodeuses a bare type assertion, noterrors.As:So a
*Errorwrapped by anything else —fmt.Errorf("%w", ...)included — reportsUnknown. Codes only survive while the*Erroris the outermost error.Why it matters more now
#121 adds
errors.Unavailableand makesinterpretMongoErrorclassify transient MongoDB failures into it. That makes correct one-shot classification load-bearing: ifisTransientMongoErrormisjudges a given driver error, no downstream caller can recover the original to re-check it, because the cause has already been flattened to a string. Before #121 the codes were coarse enough that this rarely bit; afterwards, callers are explicitly encouraged to branch onIsUnavailablevsIsNotFound.Proposal
and switch
GetErrCodetoerrors.As:Existing
Wrap/Wrapfstay as-is for source compatibility (cause simplynil), so this is additive. Migrating call sites toWrapErrcan happen incrementally.Notes
Deliberately scoped out of #121 — this touches every
Wrapcall site in the repo and has its own blast radius. Filing separately so it can be evaluated on its own.