Jelajahi Sumber

Use fmt.Formatter throughout (#40)

* Use fmt.Formatter throughout

Replace Fprintf with fmt.Formatter logic on various types. This
effectively guts Fprint to be just a recursive call down the err.Cause
chain. The next step will be to move this recursive logic into
wrapper.Format.

This change necessitates adding types for the error impls returned from
New and Errorf, and Wrap and Wrapf, respectively. The name of the latter
type is acceptable, the former is not and alternative suggestions are
encouraged.

- Remove cause.Format
- Added fmt.Formatter tests. The location is temporary, once this PR is merged and Fprint removed
they will be merged into errors_test.go
Dave Cheney 9 tahun lalu
induk
melakukan
c0c662e216
3 mengubah file dengan 115 tambahan dan 42 penghapusan
  1. 42 39
      errors.go
  2. 3 3
      example_test.go
  3. 70 0
      format_test.go

+ 42 - 39
errors.go

@@ -54,18 +54,35 @@
 package errors
 
 import (
-	"errors"
 	"fmt"
 	"io"
 )
 
+// _error is an error implementation returned by New and Errorf
+// that implements its own fmt.Formatter.
+type _error struct {
+	msg string
+	*stack
+}
+
+func (e _error) Error() string { return e.msg }
+
+func (e _error) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v: ", e.Stacktrace()[0])
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, e.msg)
+	}
+}
+
 // New returns an error that formats as the given text.
 func New(text string) error {
-	return struct {
-		error
-		*stack
-	}{
-		errors.New(text),
+	return _error{
+		text,
 		callers(),
 	}
 }
@@ -73,11 +90,8 @@ func New(text string) error {
 // Errorf formats according to a format specifier and returns the string
 // as a value that satisfies error.
 func Errorf(format string, args ...interface{}) error {
-	return struct {
-		error
-		*stack
-	}{
-		fmt.Errorf(format, args...),
+	return _error{
+		fmt.Sprintf(format, args...),
 		callers(),
 	}
 }
@@ -87,19 +101,26 @@ type cause struct {
 	msg   string
 }
 
-func (c cause) Error() string { return fmt.Sprintf("%v", c) }
+func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) }
 func (c cause) Cause() error  { return c.cause }
 
-func (c cause) Format(s fmt.State, verb rune) {
+// wrapper is an error implementation returned by Wrap and Wrapf
+// that implements its own fmt.Formatter.
+type wrapper struct {
+	cause
+	*stack
+}
+
+func (w wrapper) Format(s fmt.State, verb rune) {
 	switch verb {
 	case 'v':
 		if s.Flag('+') {
-			io.WriteString(s, c.msg)
+			fmt.Fprintf(s, "%+v: %s", w.Stacktrace()[0], w.cause.msg)
 			return
 		}
 		fallthrough
 	case 's':
-		fmt.Fprintf(s, "%s: %v", c.msg, c.Cause())
+		io.WriteString(s, w.Error())
 	}
 }
 
@@ -109,15 +130,12 @@ func Wrap(err error, message string) error {
 	if err == nil {
 		return nil
 	}
-	return struct {
-		cause
-		*stack
-	}{
-		cause{
+	return wrapper{
+		cause: cause{
 			cause: err,
 			msg:   message,
 		},
-		callers(),
+		stack: callers(),
 	}
 }
 
@@ -127,15 +145,12 @@ func Wrapf(err error, format string, args ...interface{}) error {
 	if err == nil {
 		return nil
 	}
-	return struct {
-		cause
-		*stack
-	}{
-		cause{
+	return wrapper{
+		cause: cause{
 			cause: err,
 			msg:   fmt.Sprintf(format, args...),
 		},
-		callers(),
+		stack: callers(),
 	}
 }
 
@@ -179,20 +194,8 @@ func Cause(err error) error {
 //
 // Deprecated: Fprint will be removed in version 0.7.
 func Fprint(w io.Writer, err error) {
-	type stacktrace interface {
-		Stacktrace() []Frame
-	}
-
 	for err != nil {
-		switch err := err.(type) {
-		case stacktrace:
-			frame := err.Stacktrace()[0]
-			fmt.Fprintf(w, "%+v: ", frame)
-		default:
-			// de nada
-		}
 		fmt.Fprintf(w, "%+v\n", err)
-
 		cause, ok := err.(causer)
 		if !ok {
 			break

+ 3 - 3
example_test.go

@@ -14,9 +14,9 @@ func ExampleNew() {
 	// Output: whoops
 }
 
-func ExampleNew_fprint() {
+func ExampleNew_printf() {
 	err := errors.New("whoops")
-	errors.Fprint(os.Stdout, err)
+	fmt.Printf("%+v", err)
 
 	// Output: github.com/pkg/errors/example_test.go:18: whoops
 }
@@ -65,7 +65,7 @@ func ExampleWrapf() {
 
 func ExampleErrorf() {
 	err := errors.Errorf("whoops: %s", "foo")
-	errors.Fprint(os.Stdout, err)
+	fmt.Printf("%+v", err)
 
 	// Output: github.com/pkg/errors/example_test.go:67: whoops: foo
 }

+ 70 - 0
format_test.go

@@ -0,0 +1,70 @@
+package errors
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestFormat(t *testing.T) {
+	tests := []struct {
+		error
+		format string
+		want   string
+	}{{
+
+		New("error"),
+		"%s",
+		"error",
+	}, {
+		New("error"),
+		"%v",
+		"error",
+	}, {
+		New("error"),
+		"%+v",
+		"github.com/pkg/errors/format_test.go:23: error",
+	}, {
+		Errorf("%s", "error"),
+		"%s",
+		"error",
+	}, {
+		Errorf("%s", "error"),
+		"%v",
+		"error",
+	}, {
+		Errorf("%s", "error"),
+		"%+v",
+		"github.com/pkg/errors/format_test.go:35: error",
+	}, {
+		Wrap(New("error"), "error2"),
+		"%s",
+		"error2: error",
+	}, {
+		Wrap(New("error"), "error2"),
+		"%v",
+		"error2: error",
+	}, {
+		Wrap(New("error"), "error2"),
+		"%+v",
+		"github.com/pkg/errors/format_test.go:47: error2",
+	}, {
+		Wrapf(New("error"), "error%d", 2),
+		"%s",
+		"error2: error",
+	}, {
+		Wrapf(New("error"), "error%d", 2),
+		"%v",
+		"error2: error",
+	}, {
+		Wrapf(New("error"), "error%d", 2),
+		"%+v",
+		"github.com/pkg/errors/format_test.go:59: error2",
+	}}
+
+	for _, tt := range tests {
+		got := fmt.Sprintf(tt.format, tt.error)
+		if got != tt.want {
+			t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", tt.format, got, tt.want)
+		}
+	}
+}