proto, internal/errors: add Error sentinel, errors.Wrap
Add a sentinel proto.Error error which matches all errors returned by
packages in this module.
Document that protoregistry.NotFound is an exact sentinel value for
performance reasons.
Add a Wrap function to the internal/errors package and use it to wrap
errors from outside sources (resolvers). Wrapped errors match
proto.Error.
Fixes golang/protobuf#1021.
Change-Id: I45567df3fd6c8dc9a5caafdb55654827f6fb1941
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/215338
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/errors/errors.go b/internal/errors/errors.go
index bc495b4..20c17b3 100644
--- a/internal/errors/errors.go
+++ b/internal/errors/errors.go
@@ -6,20 +6,19 @@
package errors
import (
+ "errors"
"fmt"
"google.golang.org/protobuf/internal/detrand"
)
+// Error is a sentinel matching all errors produced by this package.
+var Error = errors.New("protobuf error")
+
// New formats a string according to the format specifier and arguments and
// returns an error that has a "proto" prefix.
func New(f string, x ...interface{}) error {
- for i := 0; i < len(x); i++ {
- if e, ok := x[i].(*prefixError); ok {
- x[i] = e.s // avoid "proto: " prefix when chaining
- }
- }
- return &prefixError{s: fmt.Sprintf(f, x...)}
+ return &prefixError{s: format(f, x...)}
}
type prefixError struct{ s string }
@@ -38,6 +37,49 @@
return prefix + e.s
}
+func (e *prefixError) Unwrap() error {
+ return Error
+}
+
+// Wrap returns an error that has a "proto" prefix, the formatted string described
+// by the format specifier and arguments, and a suffix of err. The error wraps err.
+func Wrap(err error, f string, x ...interface{}) error {
+ return &wrapError{
+ s: format(f, x...),
+ err: err,
+ }
+}
+
+type wrapError struct {
+ s string
+ err error
+}
+
+func (e *wrapError) Error() string {
+ return format("%v%v: %v", prefix, e.s, e.err)
+}
+
+func (e *wrapError) Unwrap() error {
+ return e.err
+}
+
+func (e *wrapError) Is(target error) bool {
+ return target == Error
+}
+
+func format(f string, x ...interface{}) string {
+ // avoid "proto: " prefix when chaining
+ for i := 0; i < len(x); i++ {
+ switch e := x[i].(type) {
+ case *prefixError:
+ x[i] = e.s
+ case *wrapError:
+ x[i] = format("%v: %v", e.s, e.err)
+ }
+ }
+ return fmt.Sprintf(f, x...)
+}
+
func InvalidUTF8(name string) error {
return New("field %v contains invalid UTF-8", name)
}