Prechádzať zdrojové kódy

Destructure Wrap{,f} into WithStack(WithMessage(err, msg))

Introduces WithMessage as well as errors.fundamental, errors.withMessage
and errors.withStack internal types.

Adjust tests for the new wrapped format when combining fundamental and
wrapped errors.
Dave Cheney 10 rokov pred
rodič
commit
777ed74de5
3 zmenil súbory, kde vykonal 135 pridanie a 73 odobranie
  1. 89 42
      errors.go
  2. 40 25
      format_test.go
  3. 6 6
      stack_test.go

+ 89 - 42
errors.go

@@ -87,71 +87,76 @@
 package errors
 package errors
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 )
 )
 
 
 // New returns an error with the supplied message.
 // New returns an error with the supplied message.
+// New also records the stack trace at the point it was called.
 func New(message string) error {
 func New(message string) error {
-	err := errors.New(message)
-	return &withStack{
-		err,
-		callers(),
+	return &fundamental{
+		msg:   message,
+		stack: callers(),
 	}
 	}
 }
 }
 
 
 // Errorf formats according to a format specifier and returns the string
 // Errorf formats according to a format specifier and returns the string
 // as a value that satisfies error.
 // as a value that satisfies error.
+// Errorf also records the stack trace at the point it was called.
 func Errorf(format string, args ...interface{}) error {
 func Errorf(format string, args ...interface{}) error {
-	err := fmt.Errorf(format, args...)
-	return &withStack{
-		err,
-		callers(),
+	return &fundamental{
+		msg:   fmt.Sprintf(format, args...),
+		stack: callers(),
 	}
 	}
 }
 }
 
 
-type withStack struct {
-	error
+// fundamental is an error that has a message and a stack, but no caller.
+type fundamental struct {
+	msg string
 	*stack
 	*stack
 }
 }
 
 
-func (w *withStack) Format(s fmt.State, verb rune) {
+func (f *fundamental) Error() string { return f.msg }
+
+func (f *fundamental) Format(s fmt.State, verb rune) {
 	switch verb {
 	switch verb {
 	case 'v':
 	case 'v':
 		if s.Flag('+') {
 		if s.Flag('+') {
-			io.WriteString(s, w.Error())
-			w.stack.Format(s, verb)
+			io.WriteString(s, f.msg)
+			f.stack.Format(s, verb)
 			return
 			return
 		}
 		}
 		fallthrough
 		fallthrough
-	case 's':
-		io.WriteString(s, w.Error())
+	case 's', 'q':
+		io.WriteString(s, f.msg)
 	}
 	}
 }
 }
 
 
-type cause struct {
-	cause error
-	msg   string
+// WithStack annotates err with a stack trace at the point WithStack was called.
+// If err is nil, WithStack returns nil.
+func WithStack(err error) error {
+	if err == nil {
+		return nil
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
 }
 }
 
 
-func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) }
-func (c cause) Cause() error  { return c.cause }
-
-// wrapper is an error implementation returned by Wrap and Wrapf
-// that implements its own fmt.Formatter.
-type wrapper struct {
-	cause
+type withStack struct {
+	error
 	*stack
 	*stack
 }
 }
 
 
-func (w wrapper) Format(s fmt.State, verb rune) {
+func (w *withStack) Cause() error { return w.error }
+
+func (w *withStack) Format(s fmt.State, verb rune) {
 	switch verb {
 	switch verb {
 	case 'v':
 	case 'v':
 		if s.Flag('+') {
 		if s.Flag('+') {
-			fmt.Fprintf(s, "%+v\n", w.Cause())
-			io.WriteString(s, w.msg)
-			fmt.Fprintf(s, "%+v", w.StackTrace())
+			fmt.Fprintf(s, "%+v", w.Cause())
+			w.stack.Format(s, verb)
 			return
 			return
 		}
 		}
 		fallthrough
 		fallthrough
@@ -164,31 +169,73 @@ func (w wrapper) Format(s fmt.State, verb rune) {
 
 
 // Wrap returns an error annotating err with message.
 // Wrap returns an error annotating err with message.
 // If err is nil, Wrap returns nil.
 // If err is nil, Wrap returns nil.
+// Wrap is conceptually the same as calling
+//
+//     errors.WithStack(errors.WithMessage(err, msg))
 func Wrap(err error, message string) error {
 func Wrap(err error, message string) error {
 	if err == nil {
 	if err == nil {
 		return nil
 		return nil
 	}
 	}
-	return wrapper{
-		cause: cause{
-			cause: err,
-			msg:   message,
-		},
-		stack: callers(),
+	err = &withMessage{
+		cause: err,
+		msg:   message,
+	}
+	return &withStack{
+		err,
+		callers(),
 	}
 	}
 }
 }
 
 
 // Wrapf returns an error annotating err with the format specifier.
 // Wrapf returns an error annotating err with the format specifier.
 // If err is nil, Wrapf returns nil.
 // If err is nil, Wrapf returns nil.
+// Wrapf is conceptually the same as calling
+//
+//     errors.WithStack(errors.WithMessage(err, format, args...))
 func Wrapf(err error, format string, args ...interface{}) error {
 func Wrapf(err error, format string, args ...interface{}) error {
 	if err == nil {
 	if err == nil {
 		return nil
 		return nil
 	}
 	}
-	return wrapper{
-		cause: cause{
-			cause: err,
-			msg:   fmt.Sprintf(format, args...),
-		},
-		stack: callers(),
+	err = &withMessage{
+		cause: err,
+		msg:   fmt.Sprintf(format, args...),
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+// WithMessage annotates err with a new message.
+// If err is nil, WithStack returns nil.
+func WithMessage(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+	return &withMessage{
+		cause: err,
+		msg:   message,
+	}
+}
+
+type withMessage struct {
+	cause error
+	msg   string
+}
+
+func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
+func (w *withMessage) Cause() error  { return w.cause }
+
+func (w *withMessage) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v\n", w.Cause())
+			io.WriteString(s, w.msg)
+			return
+		}
+		fallthrough
+	case 's', 'q':
+		io.WriteString(s, w.Error())
 	}
 	}
 }
 }
 
 

+ 40 - 25
format_test.go

@@ -29,8 +29,8 @@ func TestFormatNew(t *testing.T) {
 			"\t.+/github.com/pkg/errors/format_test.go:25",
 			"\t.+/github.com/pkg/errors/format_test.go:25",
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
 	}
 	}
 }
 }
 
 
@@ -55,8 +55,8 @@ func TestFormatErrorf(t *testing.T) {
 			"\t.+/github.com/pkg/errors/format_test.go:51",
 			"\t.+/github.com/pkg/errors/format_test.go:51",
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
 	}
 	}
 }
 }
 
 
@@ -83,14 +83,32 @@ func TestFormatWrap(t *testing.T) {
 		Wrap(io.EOF, "error"),
 		Wrap(io.EOF, "error"),
 		"%s",
 		"%s",
 		"error: EOF",
 		"error: EOF",
+	}, {
+		Wrap(io.EOF, "error"),
+		"%v",
+		"error: EOF",
+	}, {
+		Wrap(io.EOF, "error"),
+		"%+v",
+		"EOF\n" +
+			"error\n" +
+			"github.com/pkg/errors.TestFormatWrap\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:91",
+	}, {
+		Wrap(Wrap(io.EOF, "error1"), "error2"),
+		"%+v",
+		"EOF\n" +
+			"error1\n" +
+			"github.com/pkg/errors.TestFormatWrap\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:98\n",
 	}, {
 	}, {
 		Wrap(New("error with space"), "context"),
 		Wrap(New("error with space"), "context"),
 		"%q",
 		"%q",
 		`"context: error with space"`,
 		`"context: error with space"`,
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
 	}
 	}
 }
 }
 
 
@@ -100,20 +118,24 @@ func TestFormatWrapf(t *testing.T) {
 		format string
 		format string
 		want   string
 		want   string
 	}{{
 	}{{
-		Wrapf(New("error"), "error%d", 2),
+		Wrapf(io.EOF, "error%d", 2),
 		"%s",
 		"%s",
-		"error2: error",
+		"error2: EOF",
 	}, {
 	}, {
-		Wrap(io.EOF, "error"),
+		Wrapf(io.EOF, "error%d", 2),
 		"%v",
 		"%v",
-		"error: EOF",
+		"error2: EOF",
 	}, {
 	}, {
-		Wrap(io.EOF, "error"),
+		Wrapf(io.EOF, "error%d", 2),
 		"%+v",
 		"%+v",
 		"EOF\n" +
 		"EOF\n" +
-			"error\n" +
+			"error2\n" +
 			"github.com/pkg/errors.TestFormatWrapf\n" +
 			"github.com/pkg/errors.TestFormatWrapf\n" +
-			"\t.+/github.com/pkg/errors/format_test.go:111",
+			"\t.+/github.com/pkg/errors/format_test.go:129",
+	}, {
+		Wrapf(New("error"), "error%d", 2),
+		"%s",
+		"error2: error",
 	}, {
 	}, {
 		Wrapf(New("error"), "error%d", 2),
 		Wrapf(New("error"), "error%d", 2),
 		"%v",
 		"%v",
@@ -123,22 +145,15 @@ func TestFormatWrapf(t *testing.T) {
 		"%+v",
 		"%+v",
 		"error\n" +
 		"error\n" +
 			"github.com/pkg/errors.TestFormatWrapf\n" +
 			"github.com/pkg/errors.TestFormatWrapf\n" +
-			"\t.+/github.com/pkg/errors/format_test.go:122",
-	}, {
-		Wrap(Wrap(io.EOF, "error1"), "error2"),
-		"%+v",
-		"EOF\n" +
-			"error1\n" +
-			"github.com/pkg/errors.TestFormatWrapf\n" +
-			"\t.+/github.com/pkg/errors/format_test.go:128\n",
+			"\t.+/github.com/pkg/errors/format_test.go:144",
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.error, tt.format, tt.want)
 	}
 	}
 }
 }
 
 
-func testFormatRegexp(t *testing.T, arg interface{}, format, want string) {
+func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
 	got := fmt.Sprintf(format, arg)
 	got := fmt.Sprintf(format, arg)
 	lines := strings.SplitN(got, "\n", -1)
 	lines := strings.SplitN(got, "\n", -1)
 	for i, w := range strings.SplitN(want, "\n", -1) {
 	for i, w := range strings.SplitN(want, "\n", -1) {
@@ -147,7 +162,7 @@ func testFormatRegexp(t *testing.T, arg interface{}, format, want string) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 		if !match {
 		if !match {
-			t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", format, got, want)
+			t.Errorf("test %d: line %d: fmt.Sprintf(%q, err): got: %q, want: %q", n+1, i+1, format, got, want)
 		}
 		}
 	}
 	}
 }
 }

+ 6 - 6
stack_test.go

@@ -120,8 +120,8 @@ func TestFrameFormat(t *testing.T) {
 		"unknown:0",
 		"unknown:0",
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.Frame, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
 	}
 	}
 }
 }
 
 
@@ -204,7 +204,7 @@ func TestStackTrace(t *testing.T) {
 				"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
 				"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
 		},
 		},
 	}}
 	}}
-	for _, tt := range tests {
+	for i, tt := range tests {
 		x, ok := tt.err.(interface {
 		x, ok := tt.err.(interface {
 			StackTrace() StackTrace
 			StackTrace() StackTrace
 		})
 		})
@@ -214,7 +214,7 @@ func TestStackTrace(t *testing.T) {
 		}
 		}
 		st := x.StackTrace()
 		st := x.StackTrace()
 		for j, want := range tt.want {
 		for j, want := range tt.want {
-			testFormatRegexp(t, st[j], "%+v", want)
+			testFormatRegexp(t, i, st[j], "%+v", want)
 		}
 		}
 	}
 	}
 }
 }
@@ -286,7 +286,7 @@ func TestStackTraceFormat(t *testing.T) {
 		`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
 		`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
 	}}
 	}}
 
 
-	for _, tt := range tests {
-		testFormatRegexp(t, tt.StackTrace, tt.format, tt.want)
+	for i, tt := range tests {
+		testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
 	}
 	}
 }
 }