Skip to content

Commit

Permalink
Merge pull request #59 from tlm/shush-errors
Browse files Browse the repository at this point in the history
[JUJU-1339] Adds a shush method to the library and IsType.
  • Loading branch information
tlm authored Jun 21, 2022
2 parents b38fca4 + 54eecbc commit 0ebb696
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 18 deletions.
24 changes: 8 additions & 16 deletions errortypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package errors

import (
"errors"
stderror "errors"
"fmt"
"strings"
Expand Down Expand Up @@ -56,15 +57,6 @@ const (
NotYetAvailable = ConstError("not yet available")
)

// constSuppressor is a small type wrapper for ConstError to surpress the error
// value from returning an error value. This allows us to maintain backwards
// compatibility.
type constSuppressor ConstError

func (c constSuppressor) Error() string { return "" }

func (c constSuppressor) Unwrap() error { return ConstError(c) }

// errWithType is an Err bundled with its error type (a ConstError)
type errWithType struct {
error
Expand Down Expand Up @@ -96,7 +88,7 @@ func wrapErrorWithMsg(err error, msg string) error {

func makeWrappedConstError(err error, format string, args ...interface{}) error {
separator := " "
if err.Error() == "" {
if err.Error() == "" || errors.Is(err, &fmtNoop{}) {
separator = ""
}
return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...)
Expand Down Expand Up @@ -196,7 +188,7 @@ func IsUserNotFound(err error) bool {
// the Locationer interface.
func Unauthorizedf(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(Unauthorized), format, args...),
makeWrappedConstError(Hide(Unauthorized), format, args...),
1,
)
}
Expand Down Expand Up @@ -364,7 +356,7 @@ func IsNotAssigned(err error) bool {
// Locationer interface.
func BadRequestf(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(BadRequest), format, args...),
makeWrappedConstError(Hide(BadRequest), format, args...),
1,
)
}
Expand All @@ -388,7 +380,7 @@ func IsBadRequest(err error) bool {
// and the Locationer interface.
func MethodNotAllowedf(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(MethodNotAllowed), format, args...),
makeWrappedConstError(Hide(MethodNotAllowed), format, args...),
1,
)
}
Expand All @@ -412,7 +404,7 @@ func IsMethodNotAllowed(err error) bool {
// Locationer interface.
func Forbiddenf(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(Forbidden), format, args...),
makeWrappedConstError(Hide(Forbidden), format, args...),
1,
)
}
Expand All @@ -436,7 +428,7 @@ func IsForbidden(err error) bool {
// Is(err, QuotaLimitExceeded) and the Locationer interface.
func QuotaLimitExceededf(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(QuotaLimitExceeded), format, args...),
makeWrappedConstError(Hide(QuotaLimitExceeded), format, args...),
1,
)
}
Expand All @@ -460,7 +452,7 @@ func IsQuotaLimitExceeded(err error) bool {
// and the Locationer interface.
func NotYetAvailablef(format string, args ...interface{}) error {
return newLocationError(
makeWrappedConstError(constSuppressor(NotYetAvailable), format, args...),
makeWrappedConstError(Hide(NotYetAvailable), format, args...),
1,
)
}
Expand Down
48 changes: 48 additions & 0 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func getLocation(callDepth int) (string, int) {
// }
//
func Trace(other error) error {
//return SetLocation(other, 2)
if other == nil {
return nil
}
Expand Down Expand Up @@ -350,6 +351,20 @@ func Is(err, target error) bool {
return stderrors.Is(err, target)
}

// IsType is a convenience method for ascertaining if an error contains the
// target error type within its chain. This is aimed at ease of development
// where a more complicated error type wants to be to checked for existence but
// pointer var of that type is too much overhead.
func IsType[t error](err error) bool {
for err != nil {
if _, is := err.(t); is {
return true
}
err = stderrors.Unwrap(err)
}
return false
}

// As is a proxy for the As function in Go's standard `errors` library
// (pkg.go.dev/errors).
func As(err error, target interface{}) bool {
Expand All @@ -367,3 +382,36 @@ func SetLocation(err error, callDepth int) error {

return newLocationError(err, callDepth)
}

// fmtNoop provides an internal type for wrapping errors so they won't be
// printed in fmt type commands. As this type is used by the Hide function it's
// expected that error not be nil.
type fmtNoop struct {
error
}

// Format implements the fmt.Formatter interface so that the error wrapped by
// fmtNoop will not be printed.
func (*fmtNoop) Format(_ fmt.State, r rune) {}

// Is implements errors.Is. It useful for us to be able to check if an error
// chain has fmtNoop for formatting purposes.
func (f *fmtNoop) Is(err error) bool {
_, is := err.(*fmtNoop)
return is
}

// Unwrap implements the errors.Unwrap method returning the error wrapped by
// fmtNoop.
func (f *fmtNoop) Unwrap() error {
return f.error
}

// Hide takes an error and silences it's error string from appearing in fmt
// like
func Hide(err error) error {
if err == nil {
return nil
}
return &fmtNoop{err}
}
88 changes: 87 additions & 1 deletion functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package errors_test

import (
stderrors "errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -45,7 +46,7 @@ func (*functionSuite) TestTrace(c *gc.C) {
loc := errorLocationValue(c)

c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Is(err, first), gc.Equals, true)
c.Assert(errors.Details(err), Contains, loc)

c.Assert(errors.Trace(nil), gc.IsNil)
Expand Down Expand Up @@ -387,3 +388,88 @@ func (*functionSuite) TestSetLocation(c *gc.C) {

c.Check(errors.ErrorStack(err), gc.Equals, stack)
}

func (*functionSuite) TestHideErrorStillReturnsErrorString(c *gc.C) {
err := stderrors.New("This is a simple error")
err = errors.Hide(err)

c.Assert(err.Error(), gc.Equals, "This is a simple error")
}

func (*functionSuite) TestQuietWrappedErrorStillSatisfied(c *gc.C) {
simpleTestError := errors.ConstError("I am a teapot")
err := fmt.Errorf("fill me up%w", errors.Hide(simpleTestError))
c.Assert(err.Error(), gc.Equals, "fill me up")
c.Assert(errors.Is(err, simpleTestError), gc.Equals, true)
}

type FooError struct {
}

func (*FooError) Error() string {
return "I am here boss"
}

type complexError struct {
Message string
}

func (c *complexError) Error() string {
return c.Message
}

type complexErrorOther struct {
Message string
}

func (c *complexErrorOther) Error() string {
return c.Message
}

func (*functionSuite) TestIsType(c *gc.C) {
complexErr := &complexError{Message: "complex error message"}
wrapped1 := fmt.Errorf("wrapping1: %w", complexErr)
wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1)

c.Assert(errors.IsType[*complexError](complexErr), gc.Equals, true)
c.Assert(errors.IsType[*complexError](wrapped1), gc.Equals, true)
c.Assert(errors.IsType[*complexError](wrapped2), gc.Equals, true)
c.Assert(errors.IsType[*complexErrorOther](complexErr), gc.Equals, false)
c.Assert(errors.IsType[*complexErrorOther](wrapped1), gc.Equals, false)
c.Assert(errors.IsType[*complexErrorOther](wrapped2), gc.Equals, false)

err := errors.New("test")
c.Assert(errors.IsType[*complexErrorOther](err), gc.Equals, false)

c.Assert(errors.IsType[*complexErrorOther](nil), gc.Equals, false)
}

func ExampleHide() {
myConstError := errors.ConstError("I don't want to be fmt printed")
err := fmt.Errorf("don't show this error%w", errors.Hide(myConstError))

fmt.Println(err)
fmt.Println(stderrors.Is(err, myConstError))

// Output:
// don't show this error
// true
}

type MyError struct {
Message string
}

func (m *MyError) Error() string {
return m.Message
}

func ExampleIsType() {
myErr := &MyError{Message: "these are not the droids you're looking for"}
err := fmt.Errorf("wrapped: %w", myErr)
is := errors.IsType[*MyError](err)
fmt.Println(is)

// Output:
// true
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/juju/errors

go 1.17
go 1.18

require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c

Expand Down

0 comments on commit 0ebb696

Please sign in to comment.