Skip to content

Commit

Permalink
more generic unwrapping
Browse files Browse the repository at this point in the history
This would allow the caller to more easily retrieve the recently wrapped
ErrorCode.
  • Loading branch information
Greg Weber committed Nov 18, 2024
1 parent f9cbafe commit cb7fd23
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 53 deletions.
11 changes: 6 additions & 5 deletions error_code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,22 +362,23 @@ func TestUserMsg(t *testing.T) {
if errcode.GetUserMsg(umEmpty.AddTo(MinimalError{})) != "" {
t.Errorf("expected empty string")
}
if umEmpty.AddTo(nil) != nil {
t.Errorf("expected nil")
nilAdd := umEmpty.AddTo(nil)
if nilAdd != nil {
t.Errorf("expected nil but got %+v", nilAdd)
}

UserMsgEquals(t, &ErrorWrapper{Err: ue}, "user")
UserMsgEquals(t, &ErrorWrapper{Err: UserMsgErrorEmbed{EmbedUserMsg: errcode.EmbedUserMsg{Msg: "field"}}}, "field")

msgErrCode := errcode.UserMsgErrCode{Msg: "msg", ErrorCode: MinimalError{}}
msgErrCode := errcode.WithUserMsg("msg", MinimalError{})
AssertUserMsg(t, msgErrCode, "msg")
UserMsgEquals(t, msgErrCode, "msg")

UserMsgEquals(t, &ErrorWrapper{Err: msgErrCode}, "msg")
wrappedUser := &ErrorWrapper{Err: errcode.UserMsgErrCode{Msg: "msg", ErrorCode: ue}}
wrappedUser := &ErrorWrapper{Err: errcode.WithUserMsg("msg", ue)}
AssertUserMsg(t, wrappedUser, "msg")
UserMsgEquals(t, wrappedUser, "msg")
UserMsgEquals(t, errcode.UserMsgErrCode{Msg: "msg", ErrorCode: ue}, "msg")
UserMsgEquals(t, errcode.WithUserMsg("msg", ue), "msg")
}

func AssertCodes(t *testing.T, code errcode.ErrorCode, codeStrs ...errcode.CodeStr) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/gregwebs/errcode

go 1.21.9

require github.com/gregwebs/errors v1.16.0
require github.com/gregwebs/errors v1.17.0
35 changes: 22 additions & 13 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,33 @@ func (e EmbedUserMsg) GetUserMsg() string {
// UserMsgErrCode is an ErrorCode with a Msg field attached.
// This can be conveniently constructed with NewUserMsg and AddTo or WithUserMsg
// see the HasUserMsg documentation for alternatives.
type UserMsgErrCode struct {
ErrorCode
Msg string
type UserMsgErrCode[Err ErrorCode] struct {
ErrorCode Err
Msg string
}

// Unwrap satisfies the errors package Unwrap function
func (e UserMsgErrCode) Unwrap() error {
func (e UserMsgErrCode[Err]) Unwrap() error {
return e.ErrorCode
}

// Error prefixes the user message to the underlying Err Error.
func (e UserMsgErrCode) Error() string {
func (e UserMsgErrCode[Err]) Error() string {
return e.Msg + ": " + e.ErrorCode.Error()
}

func (e UserMsgErrCode[Err]) Code() Code {
return e.ErrorCode.Code()
}

// GetUserMsg satisfies the [HasUserMsg] interface.
func (e UserMsgErrCode) GetUserMsg() string {
func (e UserMsgErrCode[Err]) GetUserMsg() string {
return e.Msg
}

var _ ErrorCode = (*UserMsgErrCode)(nil) // assert implements interface
var _ HasUserMsg = (*UserMsgErrCode)(nil) // assert implements interface
var _ unwrapError = (*UserMsgErrCode)(nil) // assert implements interface
var _ ErrorCode = (*UserMsgErrCode[ErrorCode])(nil) // assert implements interface
var _ HasUserMsg = (*UserMsgErrCode[ErrorCode])(nil) // assert implements interface
var _ unwrapError = (*UserMsgErrCode[ErrorCode])(nil) // assert implements interface

// AddUserMsg is constructed by UserMsg. It allows method chaining with AddTo.
type AddUserMsg func(ErrorCode) UserCode
Expand All @@ -99,15 +103,20 @@ func (add AddUserMsg) AddTo(err ErrorCode) UserCode {
// }
func UserMsg(msg string) AddUserMsg {
return func(err ErrorCode) UserCode {
return WithUserMsg(msg, err)
res := WithUserMsg(msg, err)
// return a nil UserCode rather than a nil UserCode(*UserMsgErrCode)
if res == nil {
return nil
}
return res
}
}

// WithUserMsg creates a UserMsgErrCode
// Panics if msg is empty or err is nil.
func WithUserMsg(msg string, err ErrorCode) UserCode {
if err == nil {
func WithUserMsg[Err ErrorCode](msg string, err Err) *UserMsgErrCode[Err] {
if ErrorCode(err) == nil {
return nil
}
return UserMsgErrCode{Msg: msg, ErrorCode: err}
return &UserMsgErrCode[Err]{Msg: msg, ErrorCode: err}
}
42 changes: 26 additions & 16 deletions wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ func Wraps(errCode ErrorCode, msg string, args ...interface{}) ErrorCode {
// WrapUser calls errors.Wrap on the inner error.
// This will wrap in place via errors.ErrorWrapper if available
// If a nil is given it is a noop
func WrapUser(errCode UserCode, msg string) UserCode {
//nolint:govet
return wrapG(wrapUserWith, errCode, msg)
func WrapUser[Err UserCode](errCode Err, msg string) *wrappedUserCode[Err] {
return wrapUserWith(errCode, errors.WrapFn(msg))
}

// WrapUserf calls errors.Wrapf on the inner error.
// This will wrap in place via errors.ErrorWrapper if available
// If a nil is given it is a noop
func WrapfUser(errCode UserCode, msg string, args ...interface{}) UserCode {
return wrapG(wrapUserWith, errCode, msg, args...)
func WrapfUser[Err UserCode](errCode Err, msg string, args ...interface{}) *wrappedUserCode[Err] {
if len(args) == 0 {
return wrapUserWith(errCode, errors.WrapFn(msg))
}
return wrapUserWith(errCode, errors.WrapfFn(msg, args...))
}

// WrapsUser calls errors.Wraps on the inner error.
Expand Down Expand Up @@ -78,18 +80,22 @@ func wrapWith(errCode ErrorCode, wrap func(error) error) ErrorCode {
if ok {
return errCode
}
return wrappedErrorCode{newWithError(errCode, wrap)}
return wrappedErrorCode[ErrorCode]{newWithError(errCode, wrap)}
}

func wrapUserWith(errCode UserCode, wrap func(error) error) UserCode {
if errCode == nil {
return errCode
func id[T any](x T) T {
return x
}

func wrapUserWith[Err UserCode](errCode Err, wrap func(error) error) *wrappedUserCode[Err] {
if UserCode(errCode) == nil {
return nil
}
ok := errors.WrapInPlace(errCode, wrap)
if ok {
return errCode
return &wrappedUserCode[Err]{newWithError(errCode, id)}
}
return wrappedUserCode{newWithError(errCode, wrap)}
return &wrappedUserCode[Err]{newWithError(errCode, wrap)}
}

func wrapOpWith(errCode OpCode, wrap func(error) error) OpCode {
Expand Down Expand Up @@ -124,19 +130,23 @@ func newWithError[Err error](errCode Err, wrapErr func(error) error) withError[E
}
}

type wrappedErrorCode struct{ withError[ErrorCode] }
type wrappedErrorCode[Err ErrorCode] struct{ withError[ErrorCode] }

func (wec wrappedErrorCode) Code() Code {
func (wec wrappedErrorCode[Err]) Code() Code {
return wec.With.Code()
}

type wrappedUserCode struct{ withError[UserCode] }
type wrappedUserCode[Err UserCode] struct{ withError[Err] }

func (wec wrappedUserCode) Code() Code {
func (wec wrappedUserCode[Err]) Code() Code {
return wec.With.Code()
}

func (wec wrappedUserCode) GetUserMsg() string {
func (wec wrappedUserCode[Err]) Unwrap() error {
return wec.With
}

func (wec wrappedUserCode[Err]) GetUserMsg() string {
return wec.With.GetUserMsg()
}

Expand Down
36 changes: 18 additions & 18 deletions wrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,46 +74,46 @@ func TestUserWrapperFunctions(t *testing.T) {
errcode.NewBadRequestErr(underlying),
)
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
coded = errcode.WrapUser(coded, "wrapped")
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
if errMsg := coded.Error(); errMsg != "user: wrapped: underlying" {
userCode := errcode.WrapUser(coded, "wrapped")
AssertCode(t, userCode, errcode.InvalidInputCode.CodeStr())
if errMsg := userCode.Error(); errMsg != "user: wrapped: underlying" {
t.Errorf("Wrap unexpected: %s", errMsg)
}
tripleUnwrap := errors.Unwrap(errors.Unwrap(errors.Unwrap(coded)))
if tripleUnwrap.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s", tripleUnwrap.Error())
cause := errors.Cause(userCode)
if cause.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s. expected: %s", cause.Error(), underlying.Error())
}
}

{
coded := errcode.WithUserMsg("user",
orig := errcode.WithUserMsg("user",
errcode.NewBadRequestErr(underlying),
)
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
coded = errcode.WrapfUser(coded, "wrapped %s", "arg")
AssertCode(t, orig, errcode.InvalidInputCode.CodeStr())
coded := errcode.WrapfUser(orig, "wrapped %s", "arg")
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
if errMsg := coded.Error(); errMsg != "user: wrapped arg: underlying" {
t.Errorf("Wrap unexpected: %s", errMsg)
}
tripleUnwrap := errors.Unwrap(errors.Unwrap(errors.Unwrap(coded)))
if tripleUnwrap.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s", tripleUnwrap.Error())
cause := errors.Cause(coded)
if cause.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s", cause.Error())
}
}

{
coded := errcode.WithUserMsg("user",
orig := errcode.WithUserMsg("user",
errcode.NewBadRequestErr(underlying),
)
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
coded = errcode.WrapsUser(coded, "wrapped", "arg", 1)
AssertCode(t, orig, errcode.InvalidInputCode.CodeStr())
coded := errcode.WrapsUser(orig, "wrapped", "arg", 1)
AssertCode(t, coded, errcode.InvalidInputCode.CodeStr())
if errMsg := coded.Error(); errMsg != "user: wrapped arg=1: underlying" {
t.Errorf("Wrap unexpected: %s", errMsg)
}
tripleUnwrap := errors.Unwrap(errors.Unwrap(errors.Unwrap(coded)))
if tripleUnwrap.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s", tripleUnwrap.Error())
cause := errors.Cause(coded)
if cause.Error() != underlying.Error() {
t.Errorf("bad unwrap: %s. expected: %s", cause.Error(), underlying.Error())
}
}
}
Expand Down

0 comments on commit cb7fd23

Please sign in to comment.