Skip to content

Commit 0ebb696

Browse files
authored
Merge pull request #59 from tlm/shush-errors
[JUJU-1339] Adds a shush method to the library and IsType.
2 parents b38fca4 + 54eecbc commit 0ebb696

4 files changed

Lines changed: 144 additions & 18 deletions

File tree

errortypes.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package errors
55

66
import (
7+
"errors"
78
stderror "errors"
89
"fmt"
910
"strings"
@@ -56,15 +57,6 @@ const (
5657
NotYetAvailable = ConstError("not yet available")
5758
)
5859

59-
// constSuppressor is a small type wrapper for ConstError to surpress the error
60-
// value from returning an error value. This allows us to maintain backwards
61-
// compatibility.
62-
type constSuppressor ConstError
63-
64-
func (c constSuppressor) Error() string { return "" }
65-
66-
func (c constSuppressor) Unwrap() error { return ConstError(c) }
67-
6860
// errWithType is an Err bundled with its error type (a ConstError)
6961
type errWithType struct {
7062
error
@@ -96,7 +88,7 @@ func wrapErrorWithMsg(err error, msg string) error {
9688

9789
func makeWrappedConstError(err error, format string, args ...interface{}) error {
9890
separator := " "
99-
if err.Error() == "" {
91+
if err.Error() == "" || errors.Is(err, &fmtNoop{}) {
10092
separator = ""
10193
}
10294
return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...)
@@ -196,7 +188,7 @@ func IsUserNotFound(err error) bool {
196188
// the Locationer interface.
197189
func Unauthorizedf(format string, args ...interface{}) error {
198190
return newLocationError(
199-
makeWrappedConstError(constSuppressor(Unauthorized), format, args...),
191+
makeWrappedConstError(Hide(Unauthorized), format, args...),
200192
1,
201193
)
202194
}
@@ -364,7 +356,7 @@ func IsNotAssigned(err error) bool {
364356
// Locationer interface.
365357
func BadRequestf(format string, args ...interface{}) error {
366358
return newLocationError(
367-
makeWrappedConstError(constSuppressor(BadRequest), format, args...),
359+
makeWrappedConstError(Hide(BadRequest), format, args...),
368360
1,
369361
)
370362
}
@@ -388,7 +380,7 @@ func IsBadRequest(err error) bool {
388380
// and the Locationer interface.
389381
func MethodNotAllowedf(format string, args ...interface{}) error {
390382
return newLocationError(
391-
makeWrappedConstError(constSuppressor(MethodNotAllowed), format, args...),
383+
makeWrappedConstError(Hide(MethodNotAllowed), format, args...),
392384
1,
393385
)
394386
}
@@ -412,7 +404,7 @@ func IsMethodNotAllowed(err error) bool {
412404
// Locationer interface.
413405
func Forbiddenf(format string, args ...interface{}) error {
414406
return newLocationError(
415-
makeWrappedConstError(constSuppressor(Forbidden), format, args...),
407+
makeWrappedConstError(Hide(Forbidden), format, args...),
416408
1,
417409
)
418410
}
@@ -436,7 +428,7 @@ func IsForbidden(err error) bool {
436428
// Is(err, QuotaLimitExceeded) and the Locationer interface.
437429
func QuotaLimitExceededf(format string, args ...interface{}) error {
438430
return newLocationError(
439-
makeWrappedConstError(constSuppressor(QuotaLimitExceeded), format, args...),
431+
makeWrappedConstError(Hide(QuotaLimitExceeded), format, args...),
440432
1,
441433
)
442434
}
@@ -460,7 +452,7 @@ func IsQuotaLimitExceeded(err error) bool {
460452
// and the Locationer interface.
461453
func NotYetAvailablef(format string, args ...interface{}) error {
462454
return newLocationError(
463-
makeWrappedConstError(constSuppressor(NotYetAvailable), format, args...),
455+
makeWrappedConstError(Hide(NotYetAvailable), format, args...),
464456
1,
465457
)
466458
}

functions.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func getLocation(callDepth int) (string, int) {
5656
// }
5757
//
5858
func Trace(other error) error {
59+
//return SetLocation(other, 2)
5960
if other == nil {
6061
return nil
6162
}
@@ -350,6 +351,20 @@ func Is(err, target error) bool {
350351
return stderrors.Is(err, target)
351352
}
352353

354+
// IsType is a convenience method for ascertaining if an error contains the
355+
// target error type within its chain. This is aimed at ease of development
356+
// where a more complicated error type wants to be to checked for existence but
357+
// pointer var of that type is too much overhead.
358+
func IsType[t error](err error) bool {
359+
for err != nil {
360+
if _, is := err.(t); is {
361+
return true
362+
}
363+
err = stderrors.Unwrap(err)
364+
}
365+
return false
366+
}
367+
353368
// As is a proxy for the As function in Go's standard `errors` library
354369
// (pkg.go.dev/errors).
355370
func As(err error, target interface{}) bool {
@@ -367,3 +382,36 @@ func SetLocation(err error, callDepth int) error {
367382

368383
return newLocationError(err, callDepth)
369384
}
385+
386+
// fmtNoop provides an internal type for wrapping errors so they won't be
387+
// printed in fmt type commands. As this type is used by the Hide function it's
388+
// expected that error not be nil.
389+
type fmtNoop struct {
390+
error
391+
}
392+
393+
// Format implements the fmt.Formatter interface so that the error wrapped by
394+
// fmtNoop will not be printed.
395+
func (*fmtNoop) Format(_ fmt.State, r rune) {}
396+
397+
// Is implements errors.Is. It useful for us to be able to check if an error
398+
// chain has fmtNoop for formatting purposes.
399+
func (f *fmtNoop) Is(err error) bool {
400+
_, is := err.(*fmtNoop)
401+
return is
402+
}
403+
404+
// Unwrap implements the errors.Unwrap method returning the error wrapped by
405+
// fmtNoop.
406+
func (f *fmtNoop) Unwrap() error {
407+
return f.error
408+
}
409+
410+
// Hide takes an error and silences it's error string from appearing in fmt
411+
// like
412+
func Hide(err error) error {
413+
if err == nil {
414+
return nil
415+
}
416+
return &fmtNoop{err}
417+
}

functions_test.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package errors_test
55

66
import (
7+
stderrors "errors"
78
"fmt"
89
"io"
910
"os"
@@ -45,7 +46,7 @@ func (*functionSuite) TestTrace(c *gc.C) {
4546
loc := errorLocationValue(c)
4647

4748
c.Assert(err.Error(), gc.Equals, "first")
48-
c.Assert(errors.Cause(err), gc.Equals, first)
49+
c.Assert(errors.Is(err, first), gc.Equals, true)
4950
c.Assert(errors.Details(err), Contains, loc)
5051

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

388389
c.Check(errors.ErrorStack(err), gc.Equals, stack)
389390
}
391+
392+
func (*functionSuite) TestHideErrorStillReturnsErrorString(c *gc.C) {
393+
err := stderrors.New("This is a simple error")
394+
err = errors.Hide(err)
395+
396+
c.Assert(err.Error(), gc.Equals, "This is a simple error")
397+
}
398+
399+
func (*functionSuite) TestQuietWrappedErrorStillSatisfied(c *gc.C) {
400+
simpleTestError := errors.ConstError("I am a teapot")
401+
err := fmt.Errorf("fill me up%w", errors.Hide(simpleTestError))
402+
c.Assert(err.Error(), gc.Equals, "fill me up")
403+
c.Assert(errors.Is(err, simpleTestError), gc.Equals, true)
404+
}
405+
406+
type FooError struct {
407+
}
408+
409+
func (*FooError) Error() string {
410+
return "I am here boss"
411+
}
412+
413+
type complexError struct {
414+
Message string
415+
}
416+
417+
func (c *complexError) Error() string {
418+
return c.Message
419+
}
420+
421+
type complexErrorOther struct {
422+
Message string
423+
}
424+
425+
func (c *complexErrorOther) Error() string {
426+
return c.Message
427+
}
428+
429+
func (*functionSuite) TestIsType(c *gc.C) {
430+
complexErr := &complexError{Message: "complex error message"}
431+
wrapped1 := fmt.Errorf("wrapping1: %w", complexErr)
432+
wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1)
433+
434+
c.Assert(errors.IsType[*complexError](complexErr), gc.Equals, true)
435+
c.Assert(errors.IsType[*complexError](wrapped1), gc.Equals, true)
436+
c.Assert(errors.IsType[*complexError](wrapped2), gc.Equals, true)
437+
c.Assert(errors.IsType[*complexErrorOther](complexErr), gc.Equals, false)
438+
c.Assert(errors.IsType[*complexErrorOther](wrapped1), gc.Equals, false)
439+
c.Assert(errors.IsType[*complexErrorOther](wrapped2), gc.Equals, false)
440+
441+
err := errors.New("test")
442+
c.Assert(errors.IsType[*complexErrorOther](err), gc.Equals, false)
443+
444+
c.Assert(errors.IsType[*complexErrorOther](nil), gc.Equals, false)
445+
}
446+
447+
func ExampleHide() {
448+
myConstError := errors.ConstError("I don't want to be fmt printed")
449+
err := fmt.Errorf("don't show this error%w", errors.Hide(myConstError))
450+
451+
fmt.Println(err)
452+
fmt.Println(stderrors.Is(err, myConstError))
453+
454+
// Output:
455+
// don't show this error
456+
// true
457+
}
458+
459+
type MyError struct {
460+
Message string
461+
}
462+
463+
func (m *MyError) Error() string {
464+
return m.Message
465+
}
466+
467+
func ExampleIsType() {
468+
myErr := &MyError{Message: "these are not the droids you're looking for"}
469+
err := fmt.Errorf("wrapped: %w", myErr)
470+
is := errors.IsType[*MyError](err)
471+
fmt.Println(is)
472+
473+
// Output:
474+
// true
475+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/juju/errors
22

3-
go 1.17
3+
go 1.18
44

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

0 commit comments

Comments
 (0)