Skip to content

Commit

Permalink
error code wrapping with some generic reuse
Browse files Browse the repository at this point in the history
The goal is to wrap an ErrorCode
while maintaining its type.
This allows for using UserCode as a marke type
that a user message will be returned
and still allows for wrapping the error
with extra information.

In-place modification accomplishes this,
and will be used if available (via ErrorWrapper)
However, requiring this in the ErrorCode interface
is too burdensome.
Instead fallback to a wrapper type
that will be different per-interface.
There are different wrapping functions for each interface
to maintain the type and not downcast to ErrorCode.
  • Loading branch information
Greg Weber committed Oct 4, 2024
1 parent 39445d0 commit a65d526
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 185 deletions.
83 changes: 34 additions & 49 deletions codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package errcode
import (
"fmt"
"net/http"

"github.com/gregwebs/errors"

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / build

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / build

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/gregwebs/errors (invalid package name: "")

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/gregwebs/errors (invalid package name: "")

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / build

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:

Check failure on line 20 in codes.go

View workflow job for this annotation

GitHub Actions / build

missing go.sum entry for module providing package github.com/gregwebs/errors (imported by github.com/gregwebs/errcode); to add:
)

var (
Expand Down Expand Up @@ -78,11 +80,7 @@ var (
// Look at the implementation of invalidInput, InternalErr, and notFound.
type CodedError struct {
GetCode Code
Err error
}

func (ce *CodedError) WrapError(apply func(error) error) {
ce.Err = apply(ce.Err)
*errors.ErrorWrap
}

// NewCodedError is for constructing broad error kinds (e.g. those representing HTTP codes)
Expand All @@ -99,50 +97,42 @@ func NewCodedError(err error, code Code) CodedError {
if errcode, ok := err.(ErrorCode); ok {
code = errcode.Code()
}
return CodedError{GetCode: code, Err: err}
}

var _ ErrorCode = (*CodedError)(nil) // assert implements interface
var _ unwrapError = (*CodedError)(nil) // assert implements interface
var _ ErrorWrap = (*CodedError)(nil) // assert implements interface

func (e CodedError) Error() string {
return e.Err.Error()
return CodedError{
GetCode: code,
ErrorWrap: &errors.ErrorWrap{Err: err},
}
}

// Unwrap satisfies the errors package Unwrwap function.
func (e CodedError) Unwrap() error {
return e.Err
}
var _ ErrorCode = (*CodedError)(nil) // assert implements interface

Check failure on line 106 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*CodedError)(nil) (value of type *CodedError) as ErrorCode value in variable declaration: *CodedError does not implement ErrorCode (missing method Error) (typecheck)

Check failure on line 106 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*CodedError)(nil) (value of type *CodedError) as ErrorCode value in variable declaration: *CodedError does not implement ErrorCode (missing method Error)

Check failure on line 106 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*CodedError)(nil) (value of type *CodedError) as ErrorCode value in variable declaration: *CodedError does not implement ErrorCode (missing method Error)

Check failure on line 106 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*CodedError)(nil) (value of type *CodedError) as ErrorCode value in variable declaration: *CodedError does not implement ErrorCode (missing method Error) (typecheck)
var _ unwrapError = (*CodedError)(nil) // assert implements interface

Check failure on line 107 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*CodedError)(nil) (value of type *CodedError) as unwrapError value in variable declaration: *CodedError does not implement unwrapError (missing method Unwrap) (typecheck)

Check failure on line 107 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*CodedError)(nil) (value of type *CodedError) as unwrapError value in variable declaration: *CodedError does not implement unwrapError (missing method Unwrap)

Check failure on line 107 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*CodedError)(nil) (value of type *CodedError) as unwrapError value in variable declaration: *CodedError does not implement unwrapError (missing method Unwrap)

Check failure on line 107 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*CodedError)(nil) (value of type *CodedError) as unwrapError value in variable declaration: *CodedError does not implement unwrapError (missing method Unwrap) (typecheck)
var _ errors.ErrorWrapper = (*CodedError)(nil) // assert implements interface

// Code returns the GetCode field
func (e CodedError) Code() Code {
return e.GetCode
}

// invalidInputErr gives the code InvalidInputCode.
type invalidInputErr struct{ *CodedError }
type invalidInputErr struct{ CodedError }

// NewInvalidInputErr creates an invalidInputErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use InvalidInputCode which gives HTTP 400.
func NewInvalidInputErr(err error) ErrorCode {
coded := NewCodedError(err, InvalidInputCode)
return invalidInputErr{&coded}
return invalidInputErr{NewCodedError(err, InvalidInputCode)}
}

var _ ErrorCode = (*invalidInputErr)(nil) // assert implements interface

Check failure on line 125 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as ErrorCode value in variable declaration: *invalidInputErr does not implement ErrorCode (missing method Error) (typecheck)

Check failure on line 125 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as ErrorCode value in variable declaration: *invalidInputErr does not implement ErrorCode (missing method Error)

Check failure on line 125 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as ErrorCode value in variable declaration: *invalidInputErr does not implement ErrorCode (missing method Error)

Check failure on line 125 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as ErrorCode value in variable declaration: *invalidInputErr does not implement ErrorCode (missing method Error) (typecheck)
var _ unwrapError = (*invalidInputErr)(nil) // assert implements interface

Check failure on line 126 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as unwrapError value in variable declaration: *invalidInputErr does not implement unwrapError (missing method Unwrap) (typecheck)

Check failure on line 126 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as unwrapError value in variable declaration: *invalidInputErr does not implement unwrapError (missing method Unwrap)

Check failure on line 126 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as unwrapError value in variable declaration: *invalidInputErr does not implement unwrapError (missing method Unwrap)

Check failure on line 126 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*invalidInputErr)(nil) (value of type *invalidInputErr) as unwrapError value in variable declaration: *invalidInputErr does not implement unwrapError (missing method Unwrap) (typecheck)

// badReqeustErr gives the code BadRequestErr.
type BadRequestErr struct{ *CodedError }
type BadRequestErr struct{ CodedError }

// NewBadRequestErr creates a BadReqeustErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use BadRequestCode which gives HTTP 400.
func NewBadRequestErr(err error) BadRequestErr {
coded := NewCodedError(err, InvalidInputCode)
return BadRequestErr{&coded}
return BadRequestErr{NewCodedError(err, InvalidInputCode)}
}

// InternalErr gives the code InternalCode
Expand Down Expand Up @@ -178,7 +168,10 @@ func makeInternalStackCode(defaultCode Code) func(error) StackCode {
code = errCode
}
}
return NewStackCode(&CodedError{GetCode: code, Err: err}, 3)
return NewStackCode(CodedError{
GetCode: code,
ErrorWrap: &errors.ErrorWrap{Err: err},
}, 3)
}
}

Expand Down Expand Up @@ -209,97 +202,89 @@ func NewUnavailableErr(err error) UnavailableErr {
}

// notFound gives the code NotFoundCode.
type NotFoundErr struct{ *CodedError }
type NotFoundErr struct{ CodedError }

// NewNotFoundErr creates a notFound from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use NotFoundCode which gives HTTP 404.
func NewNotFoundErr(err error) NotFoundErr {
coded := NewCodedError(err, NotFoundCode)
return NotFoundErr{&coded}
return NotFoundErr{NewCodedError(err, NotFoundCode)}
}

var _ ErrorCode = (*NotFoundErr)(nil) // assert implements interface

Check failure on line 214 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as ErrorCode value in variable declaration: *NotFoundErr does not implement ErrorCode (missing method Error) (typecheck)

Check failure on line 214 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as ErrorCode value in variable declaration: *NotFoundErr does not implement ErrorCode (missing method Error)

Check failure on line 214 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as ErrorCode value in variable declaration: *NotFoundErr does not implement ErrorCode (missing method Error)

Check failure on line 214 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as ErrorCode value in variable declaration: *NotFoundErr does not implement ErrorCode (missing method Error) (typecheck)
var _ unwrapError = (*NotFoundErr)(nil) // assert implements interface

Check failure on line 215 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as unwrapError value in variable declaration: *NotFoundErr does not implement unwrapError (missing method Unwrap) (typecheck)

Check failure on line 215 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as unwrapError value in variable declaration: *NotFoundErr does not implement unwrapError (missing method Unwrap)

Check failure on line 215 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as unwrapError value in variable declaration: *NotFoundErr does not implement unwrapError (missing method Unwrap)

Check failure on line 215 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotFoundErr)(nil) (value of type *NotFoundErr) as unwrapError value in variable declaration: *NotFoundErr does not implement unwrapError (missing method Unwrap) (typecheck)

// NotAuthenticatedErr gives the code NotAuthenticatedCode.
type NotAuthenticatedErr struct{ *CodedError }
type NotAuthenticatedErr struct{ CodedError }

// NewNotAuthenticatedErr creates a NotAuthenticatedErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use NotAuthenticatedCode which gives HTTP 401.
func NewNotAuthenticatedErr(err error) NotAuthenticatedErr {
coded := NewCodedError(err, NotAuthenticatedCode)
return NotAuthenticatedErr{&coded}
return NotAuthenticatedErr{NewCodedError(err, NotAuthenticatedCode)}
}

var _ ErrorCode = (*NotAuthenticatedErr)(nil) // assert implements interface

Check failure on line 227 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as ErrorCode value in variable declaration: *NotAuthenticatedErr does not implement ErrorCode (missing method Error) (typecheck)

Check failure on line 227 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as ErrorCode value in variable declaration: *NotAuthenticatedErr does not implement ErrorCode (missing method Error)

Check failure on line 227 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as ErrorCode value in variable declaration: *NotAuthenticatedErr does not implement ErrorCode (missing method Error)

Check failure on line 227 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as ErrorCode value in variable declaration: *NotAuthenticatedErr does not implement ErrorCode (missing method Error) (typecheck)
var _ unwrapError = (*NotAuthenticatedErr)(nil) // assert implements interface

Check failure on line 228 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as unwrapError value in variable declaration: *NotAuthenticatedErr does not implement unwrapError (missing method Unwrap) (typecheck)

Check failure on line 228 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as unwrapError value in variable declaration: *NotAuthenticatedErr does not implement unwrapError (missing method Unwrap)

Check failure on line 228 in codes.go

View workflow job for this annotation

GitHub Actions / govulncheck

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as unwrapError value in variable declaration: *NotAuthenticatedErr does not implement unwrapError (missing method Unwrap)

Check failure on line 228 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*NotAuthenticatedErr)(nil) (value of type *NotAuthenticatedErr) as unwrapError value in variable declaration: *NotAuthenticatedErr does not implement unwrapError (missing method Unwrap) (typecheck)

// ForbiddenErr gives the code ForbiddenCode.
type ForbiddenErr struct{ *CodedError }
type ForbiddenErr struct{ CodedError }

// NewForbiddenErr creates a ForbiddenErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use ForbiddenCode which gives HTTP 401.
func NewForbiddenErr(err error) ForbiddenErr {
coded := NewCodedError(err, ForbiddenCode)
return ForbiddenErr{&coded}
return ForbiddenErr{NewCodedError(err, ForbiddenCode)}
}

var _ ErrorCode = (*ForbiddenErr)(nil) // assert implements interface

Check failure on line 240 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*ForbiddenErr)(nil) (value of type *ForbiddenErr) as ErrorCode value in variable declaration: *ForbiddenErr does not implement ErrorCode (missing method Error) (typecheck)

Check failure on line 240 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*ForbiddenErr)(nil) (value of type *ForbiddenErr) as ErrorCode value in variable declaration: *ForbiddenErr does not implement ErrorCode (missing method Error) (typecheck)
var _ unwrapError = (*ForbiddenErr)(nil) // assert implements interface

Check failure on line 241 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*ForbiddenErr)(nil) (value of type *ForbiddenErr) as unwrapError value in variable declaration: *ForbiddenErr does not implement unwrapError (missing method Unwrap) (typecheck)

Check failure on line 241 in codes.go

View workflow job for this annotation

GitHub Actions / lint

cannot use (*ForbiddenErr)(nil) (value of type *ForbiddenErr) as unwrapError value in variable declaration: *ForbiddenErr does not implement unwrapError (missing method Unwrap) (typecheck)

// UnprocessableErr gives the code UnprocessibleCode.
type UnprocessableErr struct{ *CodedError }
type UnprocessableErr struct{ CodedError }

// NewUnprocessableErr creates an UnprocessableErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use UnprocessableEntityCode which gives HTTP 422.
func NewUnprocessableErr(err error) UnprocessableErr {
coded := NewCodedError(err, UnprocessableEntityCode)
return UnprocessableErr{&coded}
return UnprocessableErr{NewCodedError(err, UnprocessableEntityCode)}
}

// NotAcceptableErr gives the code NotAcceptableCode.
type NotAcceptableErr struct{ *CodedError }
type NotAcceptableErr struct{ CodedError }

// NewUnprocessableErr creates an UnprocessableErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use NotAcceptableCode which gives HTTP 406.
func NewNotAcceptableErr(err error) NotAcceptableErr {
coded := NewCodedError(err, NotAcceptableCode)
return NotAcceptableErr{&coded}
return NotAcceptableErr{NewCodedError(err, NotAcceptableCode)}
}

type AlreadyExistsErr struct{ *CodedError }
type AlreadyExistsErr struct{ CodedError }

// NewAlreadyExistsErr creates an AlreadyExistsErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use AlreadyExistsCode which gives HTTP 409.
func NewAlreadyExistsErr(err error) AlreadyExistsErr {
coded := NewCodedError(err, AlreadyExistsCode)
return AlreadyExistsErr{&coded}
return AlreadyExistsErr{NewCodedError(err, AlreadyExistsCode)}
}

// TimeoutGatewayErr gives the code TimeoutGatewayCode
type TimeoutGatewayErr struct{ *CodedError }
type TimeoutGatewayErr struct{ CodedError }

// NewTimeoutGatewayErr creates a TimeoutGatewayErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use TimeoutGatewayErr which gives HTTP 504.
func NewTimeoutGatewayErr(err error) TimeoutGatewayErr {
coded := NewCodedError(err, TimeoutGatewayCode)
return TimeoutGatewayErr{&coded}
return TimeoutGatewayErr{NewCodedError(err, TimeoutGatewayCode)}
}

// TimeoutRequestErr gives the code TimeoutRequestCode
type TimeoutRequestErr struct{ *CodedError }
type TimeoutRequestErr struct{ CodedError }

// NewTimeoutRequestErr creates a TimeoutRequestErr from an err.
// If the error is already an ErrorCode it will use that code.
// Otherwise it will use TimeoutRequestErr which gives HTTP 408.
func NewTimeoutRequestErr(err error) TimeoutRequestErr {
coded := NewCodedError(err, TimeoutRequestCode)
return TimeoutRequestErr{&coded}
return TimeoutRequestErr{NewCodedError(err, TimeoutRequestCode)}
}
54 changes: 0 additions & 54 deletions error_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ package errcode
import (
"fmt"
"strings"

"github.com/gregwebs/errors"
)

// CodeStr is the name of the error code.
Expand Down Expand Up @@ -133,58 +131,6 @@ func (code Code) IsAncestor(ancestorCode Code) bool {
type ErrorCode interface {
error
Code() Code
ErrorWrap
}

// The WrapError method allows for modifying the inner error while maintaining the same outer type.
// This is most useful when wrapping an extended ErrorCode interface such as UserCode.
// These are used by the errcode.Wrap* functions.
type ErrorWrap interface {
WrapError(func(error) error)
}

// unwrapError allows the abstract retrieval of the underlying error.
// Formalize the Unwrap interface, but don't export it.
// The standard library errors package should export it.
// Types that wrap errors should implement this to allow viewing of the underlying error.
type unwrapError interface {
Unwrap() error
}

// Wrap calls errors.Wrap on the inner error.
// This uses the WrapError method of ErrorWrap
// If a nil is given it is a noop
func Wrap(errCode ErrorWrap, msg string) {
if errCode == nil {
return
}
errCode.WrapError(func(err error) error {
return errors.Wrap(err, msg)
})
}

// Wrapf calls errors.Wrapf on the inner error.
// This uses the WrapError method of ErrorWrap
// If a nil is given it is a noop
func Wrapf(errCode ErrorWrap, msg string, args ...interface{}) {
if errCode == nil {
return
}
errCode.WrapError(func(err error) error {
return errors.Wrapf(err, msg, args...)
})
}

// Wraps calls errors.Wraps on the inner error.
// This uses the WrapError method of ErrorWrap
// If a nil is given it is a noop
func Wraps(errCode ErrorWrap, msg string, args ...interface{}) {
if errCode == nil {
return
}
errCode.WrapError(func(err error) error {
return errors.Wraps(err, msg, args...)
})
}

// HasClientData is used to defined how to retrieve the data portion of an ErrorCode to be returned to the client.
Expand Down
Loading

0 comments on commit a65d526

Please sign in to comment.