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)
 }