internal/errors: initial commit

This adds package errors, which provides error handling constructs that are
specific to the protobuf repo. In particular, it provides a NonFatal type
which is useful for capturing non-fatal errors like invalid UTF-8 and required
fields not set.

High-level API:
	type NonFatalErrors []error
		func (NonFatalErrors) Error() string
	type NonFatal struct{ ... }
		func (*NonFatal) Merge(err error) (ok bool)
		func (*NonFatal) AppendInvalidUTF8(field string)
		func (*NonFatal) AppendRequiredNotSet(field string)
	func New(string, ...interface{}) error

Change-Id: I9448c586008240e8987573fe79e0ffb024e7629d
Reviewed-on: https://go-review.googlesource.com/127338
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/internal/errors/errors.go b/internal/errors/errors.go
new file mode 100644
index 0000000..4fddf39
--- /dev/null
+++ b/internal/errors/errors.go
@@ -0,0 +1,128 @@
+// 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 errors implements functions to manipulate errors.
+package errors
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// TODO: This package currently only provides functionality for constructing
+// non-fatal errors. However, it does not currently provide functionality
+// to test for a specific kind of non-fatal error, which is necessary
+// for the end user.
+//
+// When that functionality is added, we need to think carefully about whether
+// a user only cares that some kind of non-fatal error was present or whether
+// all of the errors are of the same kind of non-fatal error.
+
+// NonFatalErrors is a list of non-fatal errors where each error
+// must either be a RequiredNotSet error or InvalidUTF8 error.
+// The list must not be empty.
+type NonFatalErrors []error
+
+func (es NonFatalErrors) Error() string {
+	ms := map[string]struct{}{}
+	for _, e := range es {
+		ms[e.Error()] = struct{}{}
+	}
+	var ss []string
+	for s := range ms {
+		ss = append(ss, s)
+	}
+	sort.Strings(ss)
+	return "proto: " + strings.Join(ss, "; ")
+}
+
+// NonFatal contains non-fatal errors, which are errors that permit execution
+// to continue, but should return with a non-nil error. As such, NonFatal is
+// a data structure useful for swallowing non-fatal errors, but being able to
+// reproduce them at the end of the function.
+// An error is non-fatal if it is collection of non-fatal errors, or is
+// an individual error where IsRequiredNotSet or IsInvalidUTF8 reports true.
+//
+// Typical usage pattern:
+//	var nerr errors.NonFatal
+//	...
+//	if err := MyFunction(); !nerr.Merge(err) {
+//		return nil, err // immediately return if err is fatal
+//	}
+//	...
+//	return out, nerr.E
+type NonFatal struct{ E error }
+
+// Merge merges err into nf and reports whether it was successful.
+// Otherwise it returns false for any fatal non-nil errors.
+func (nf *NonFatal) Merge(err error) (ok bool) {
+	if err == nil {
+		return true // not an error
+	}
+	if es, ok := err.(NonFatalErrors); ok {
+		nf.append(es...)
+		return true // merged a list of non-fatal errors
+	}
+	if e, ok := err.(interface{ RequiredNotSet() bool }); ok && e.RequiredNotSet() {
+		nf.append(err)
+		return true // non-fatal RequiredNotSet error
+	}
+	if e, ok := err.(interface{ InvalidUTF8() bool }); ok && e.InvalidUTF8() {
+		nf.append(err)
+		return true // non-fatal InvalidUTF8 error
+	}
+	return false // fatal error
+}
+
+// AppendRequiredNotSet appends a RequiredNotSet error.
+func (nf *NonFatal) AppendRequiredNotSet(field string) {
+	nf.append(requiredNotSetError(field))
+}
+
+// AppendInvalidUTF8 appends an InvalidUTF8 error.
+func (nf *NonFatal) AppendInvalidUTF8(field string) {
+	nf.append(invalidUTF8Error(field))
+}
+
+func (nf *NonFatal) append(errs ...error) {
+	es, _ := nf.E.(NonFatalErrors)
+	es = append(es, errs...)
+	nf.E = es
+}
+
+type requiredNotSetError string
+
+func (e requiredNotSetError) Error() string {
+	if e == "" {
+		return "required field not set"
+	}
+	return string("required field " + e + " not set")
+}
+func (requiredNotSetError) RequiredNotSet() bool { return true }
+
+type invalidUTF8Error string
+
+func (e invalidUTF8Error) Error() string {
+	if e == "" {
+		return "invalid UTF-8 detected"
+	}
+	return string("field " + e + " contains invalid UTF-8")
+}
+func (invalidUTF8Error) InvalidUTF8() bool { return true }
+
+// 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...)}
+}
+
+type prefixError struct{ s string }
+
+func (e *prefixError) Error() string { return "proto: " + e.s }
diff --git a/internal/errors/errors_test.go b/internal/errors/errors_test.go
new file mode 100644
index 0000000..8dd635e
--- /dev/null
+++ b/internal/errors/errors_test.go
@@ -0,0 +1,93 @@
+// 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 errors
+
+import (
+	"errors"
+	"reflect"
+	"testing"
+)
+
+func TestNonFatal(t *testing.T) {
+	type (
+		method = interface{} // merge | appendRequiredNotSet | appendInvalidUTF8
+		merge  struct {
+			inErr  error
+			wantOk bool
+		}
+		appendRequiredNotSet struct{ inField string }
+		appendInvalidUTF8    struct{ inField string }
+	)
+
+	tests := []struct {
+		label   string
+		methods []method
+		wantErr error
+	}{{
+		label: "IgnoreNil",
+		methods: []method{
+			merge{inErr: nil, wantOk: true},
+		},
+	}, {
+		label: "IgnoreFatal",
+		methods: []method{
+			merge{inErr: errors.New("fatal error")},
+		},
+	}, {
+		label: "MergeNonFatal",
+		methods: []method{
+			appendRequiredNotSet{"foo"},
+			merge{inErr: customRequiredNotSetError{}, wantOk: true},
+			appendInvalidUTF8{"bar"},
+			merge{inErr: customInvalidUTF8Error{}, wantOk: true},
+			merge{inErr: NonFatalErrors{
+				requiredNotSetError("fizz"),
+				invalidUTF8Error("buzz"),
+			}, wantOk: true},
+			merge{inErr: errors.New("fatal error")}, // not stored
+		},
+		wantErr: NonFatalErrors{
+			requiredNotSetError("foo"),
+			customRequiredNotSetError{},
+			invalidUTF8Error("bar"),
+			customInvalidUTF8Error{},
+			requiredNotSetError("fizz"),
+			invalidUTF8Error("buzz"),
+		},
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.label, func(t *testing.T) {
+			var nerr NonFatal
+			for _, m := range tt.methods {
+				switch m := m.(type) {
+				case merge:
+					if gotOk := nerr.Merge(m.inErr); gotOk != m.wantOk {
+						t.Errorf("Merge() = %v, want %v", gotOk, m.wantOk)
+					}
+				case appendRequiredNotSet:
+					nerr.AppendRequiredNotSet(m.inField)
+				case appendInvalidUTF8:
+					nerr.AppendInvalidUTF8(m.inField)
+				default:
+					t.Fatalf("invalid method: %T", m)
+				}
+			}
+			if !reflect.DeepEqual(nerr.E, tt.wantErr) {
+				t.Errorf("NonFatal.E = %v, want %v", nerr.E, tt.wantErr)
+			}
+		})
+	}
+}
+
+type customInvalidUTF8Error struct{}
+
+func (customInvalidUTF8Error) Error() string     { return "invalid UTF-8 detected" }
+func (customInvalidUTF8Error) InvalidUTF8() bool { return true }
+
+type customRequiredNotSetError struct{}
+
+func (customRequiredNotSetError) Error() string        { return "required field not set" }
+func (customRequiredNotSetError) RequiredNotSet() bool { return true }