errors/fmt: add adaptor support
Embedded error in Formatter interface to allow
assigning an errors.Formatter to an error variable without a cast.
Change-Id: I6fff75b6128256877c31fa6d7e1cdcaeabfbdeeb
Reviewed-on: https://go-review.googlesource.com/c/152999
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/errors/errors.go b/errors/errors.go
index d7ab482..39d7c5e 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -28,7 +28,7 @@
return e.s
}
-func (e *errorString) Format(p Printer) (next error) {
+func (e *errorString) FormatError(p Printer) (next error) {
p.Print(e.s)
e.frame.Format(p)
return nil
diff --git a/errors/fmt/adaptor.go b/errors/fmt/adaptor.go
new file mode 100644
index 0000000..2da6a9d
--- /dev/null
+++ b/errors/fmt/adaptor.go
@@ -0,0 +1,31 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import "golang.org/x/exp/errors"
+
+// The functionality in this file is to provide adaptors only. It will not
+// be included in the standard library.
+
+// FormatError calls the FormatError method of err with a errors.Printer
+// configured according to s and verb and writes the result to s.
+func FormatError(s State, verb rune, err errors.Formatter) {
+ // Assuming this function is only called from the Format method, and given
+ // that FormatError takes precedence over Format, it cannot be called from
+ // any package that supports errors.Formatter. It is therefore safe to
+ // disregard that State may be a specific printer implementation and use one
+ // of our choice instead.
+ p := newPrinter()
+ if verb == 'v' {
+ if s.Flag('#') {
+ p.fmt.sharpV = true
+ }
+ if s.Flag('+') {
+ p.fmt.plusV = true
+ }
+ }
+ fmtError(p, verb, err)
+ s.Write(p.buf)
+}
diff --git a/errors/fmt/errors.go b/errors/fmt/errors.go
index 3150cc5..06fcffe 100644
--- a/errors/fmt/errors.go
+++ b/errors/fmt/errors.go
@@ -74,7 +74,7 @@
return Sprint(e)
}
-func (e *noWrapError) Format(p errors.Printer) (next error) {
+func (e *noWrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
@@ -90,7 +90,7 @@
return Sprint(e)
}
-func (e *wrapError) Format(p errors.Printer) (next error) {
+func (e *wrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
@@ -148,16 +148,8 @@
w.fmt.inDetail = false
switch v := err.(type) {
case errors.Formatter:
- err = v.Format((*errPP)(w))
- // TODO: This case is for supporting old error implementations.
- // It may eventually disappear.
- case interface{ FormatError(errors.Printer) error }:
err = v.FormatError((*errPP)(w))
case Formatter:
- // Discard verb, but keep the flags. Discarding the verb prevents
- // nested quoting and other unwanted behavior. Preserving flags
- // recursively signals a request for detail, if interpreted as %+v.
- w.fmt.fmtFlags = p.fmt.fmtFlags
if w.fmt.plusV {
v.Format((*errPPState)(w), 'v') // indent new lines
} else {
diff --git a/errors/fmt/errors_test.go b/errors/fmt/errors_test.go
index 6a0ce27..9feff95 100644
--- a/errors/fmt/errors_test.go
+++ b/errors/fmt/errors_test.go
@@ -88,9 +88,8 @@
"can't adumbrate elephant",
detailed{},
}
- transition = &wrapped2{"elephant still on strike", detailed{}}
- nonascii = &wrapped{"café", nil}
- newline = &wrapped{"msg with\nnewline",
+ nonascii = &wrapped{"café", nil}
+ newline = &wrapped{"msg with\nnewline",
&wrapped{"and another\none", nil}}
fallback = &wrapped{"fallback", os.ErrNotExist}
oldAndNew = &wrapped{"new style", formatError("old style")}
@@ -133,15 +132,6 @@
"\n and the 12 monkeys" +
"\n are laughing",
}, {
- err: transition,
- fmt: "%+v",
- want: "elephant still on strike:" +
- "\n somefile.go:123" +
- "\n - out of peanuts:" +
- "\n the elephant is on strike" +
- "\n and the 12 monkeys" +
- "\n are laughing",
- }, {
err: simple,
fmt: "%#v",
want: "&fmt_test.wrapped{msg:\"simple\", err:error(nil)}",
@@ -150,7 +140,7 @@
fmt: "%+v",
want: "something:" +
"\n golang.org/x/exp/errors/fmt_test.TestErrorFormatter" +
- "\n .+/golang.org/x/exp/errors/fmt/errors_test.go:98" +
+ "\n .+/golang.org/x/exp/errors/fmt/errors_test.go:97" +
"\n something more",
regexp: true,
}, {
@@ -311,21 +301,7 @@
func (e wrapped) Error() string { return "should call Format" }
-func (e wrapped) Format(p errors.Printer) (next error) {
- p.Print(e.msg)
- p.Detail()
- p.Print("somefile.go:123")
- return e.err
-}
-
-type wrapped2 struct {
- msg string
- err error
-}
-
-func (e wrapped2) Error() string { return "should call Format" }
-
-func (e wrapped2) FormatError(p errors.Printer) (next error) {
+func (e wrapped) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
p.Print("somefile.go:123")
@@ -338,7 +314,7 @@
func (e detailed) Error() string { return fmt.Sprint(e) }
-func (detailed) Format(p errors.Printer) (next error) {
+func (detailed) FormatError(p errors.Printer) (next error) {
p.Printf("out of %s", "peanuts")
p.Detail()
p.Print("the elephant is on strike\n")
@@ -352,7 +328,7 @@
func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
-func (e *withFrameAndMore) Format(p errors.Printer) (next error) {
+func (e *withFrameAndMore) FormatError(p errors.Printer) (next error) {
p.Print("something")
if p.Detail() {
e.frame.Format(p)
@@ -365,7 +341,7 @@
func (e spurious) Error() string { return fmt.Sprint(e) }
-func (e spurious) Format(p errors.Printer) (next error) {
+func (e spurious) FormatError(p errors.Printer) (next error) {
p.Print("spurious")
p.Detail() // Call detail even if we don't print anything
if e == "" {
@@ -415,7 +391,7 @@
func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
-func (e fmtTwiceErr) Format(p errors.Printer) (next error) {
+func (e fmtTwiceErr) FormatError(p errors.Printer) (next error) {
p.Printf(e.format, e.args...)
p.Print("/")
p.Printf(e.format, e.args...)
@@ -452,7 +428,7 @@
a = append(a, err.Error())
break
}
- err = f.Format(&p)
+ err = f.FormatError(&p)
a = append(a, cleanPath(p.str))
}
return a
diff --git a/errors/fmt/print.go b/errors/fmt/print.go
index 0374b0f..da3baf8 100644
--- a/errors/fmt/print.go
+++ b/errors/fmt/print.go
@@ -5,6 +5,7 @@
package fmt
import (
+ gofmt "fmt"
"io"
"os"
"reflect"
@@ -31,44 +32,31 @@
invReflectString = "<invalid reflect.Value>"
)
+// These interface need to be aliases so that implementations can be used
+// for different implementations.
+
// State represents the printer state passed to custom formatters.
// It provides access to the io.Writer interface plus information about
// the flags and options for the operand's format specifier.
-type State interface {
- // Write is the function to call to emit formatted output to be printed.
- Write(b []byte) (n int, err error)
- // Width returns the value of the width option and whether it has been set.
- Width() (wid int, ok bool)
- // Precision returns the value of the precision option and whether it has been set.
- Precision() (prec int, ok bool)
-
- // Flag reports whether the flag c, a character, has been set.
- Flag(c int) bool
-}
+type State = gofmt.State
// Formatter is the interface implemented by values with a custom formatter.
// The implementation of Format may call Sprint(f) or Fprint(f) etc.
// to generate its output.
-type Formatter interface {
- Format(f State, c rune)
-}
+type Formatter = gofmt.Formatter
// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
-type Stringer interface {
- String() string
-}
+type Stringer = gofmt.Stringer
// GoStringer is implemented by any value that has a GoString method,
// which defines the Go syntax for that value.
// The GoString method is used to print values passed as an operand
// to a %#v format.
-type GoStringer interface {
- GoString() string
-}
+type GoStringer = gofmt.GoStringer
// Use simple []byte instead of bytes.Buffer to avoid large dependency.
type buffer []byte
@@ -556,17 +544,17 @@
if p.erroring {
return
}
- // Is it a Formatter?
- if formatter, ok := p.arg.(Formatter); ok {
+ switch x := p.arg.(type) {
+ case Formatter:
handled = true
defer p.catchPanic(p.arg, verb)
- formatter.Format(p, verb)
+ x.Format(p, verb)
return
- }
- if err, ok := p.arg.(error); ok {
+
+ case error:
handled = true
defer p.catchPanic(p.arg, verb)
- return fmtError(p, verb, err)
+ return fmtError(p, verb, x)
}
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
diff --git a/errors/format.go b/errors/format.go
index aed5de3..12deed3 100644
--- a/errors/format.go
+++ b/errors/format.go
@@ -6,9 +6,11 @@
// A Formatter formats error messages.
type Formatter interface {
- // Format prints the receiver's first error and returns the next error in
+ error
+
+ // FormatError prints the receiver's first error and returns the next error in
// the error chain, if any.
- Format(p Printer) (next error)
+ FormatError(p Printer) (next error)
}
// A Printer formats error messages.
diff --git a/errors/wrap.go b/errors/wrap.go
index 00d1cb2..c936d97 100644
--- a/errors/wrap.go
+++ b/errors/wrap.go
@@ -25,9 +25,9 @@
error
}
-func (e noWrapper) Format(p Printer) (next error) {
+func (e noWrapper) FormatError(p Printer) (next error) {
if f, ok := e.error.(Formatter); ok {
- return f.Format(p)
+ return f.FormatError(p)
}
p.Print(e.error)
return nil
diff --git a/errors/wrap_test.go b/errors/wrap_test.go
index c4d6fc6..f72425d 100644
--- a/errors/wrap_test.go
+++ b/errors/wrap_test.go
@@ -189,7 +189,7 @@
func (errorD) Error() string { return "errorD" }
-func (errorD) Format(p errors.Printer) error {
+func (errorD) FormatError(p errors.Printer) error {
p.Print("errorD")
p.Detail()
p.Print("detail")