From e8e21bca4bde8d642a7bd6b15bd90788e72962a3 Mon Sep 17 00:00:00 2001 From: searKing <471030698@qq.com> Date: Tue, 25 Jan 2022 20:09:39 +0800 Subject: [PATCH] errors: add errors.Mark to support error marks for errors.Is and '%v' in fmt, without any side effect otherwise --- src/errors/mark.go | 102 +++++++++++++++++++++++++++++++++++ src/errors/mark_test.go | 114 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 src/errors/mark.go create mode 100644 src/errors/mark_test.go diff --git a/src/errors/mark.go b/src/errors/mark.go new file mode 100644 index 0000000000000..7a8eb81f4e4f0 --- /dev/null +++ b/src/errors/mark.go @@ -0,0 +1,102 @@ +// Copyright 2022 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 ( + "fmt" + "io" +) + +var _ error = markError{} // verify that Error implements error + +// Mark returns an error with the supplied errors as marks. +// If err is nil, return nil. +// marks take effects only when Is and '%v' in fmt. +// Is returns true if err or any marks match the target. +func Mark(err error, marks ...error) error { + if err == nil { + return nil + } + if len(marks) == 0 { + return err + } + me := markError{ + err: err, + marks: marks, + } + return me +} + +type markError struct { + err error // visual error + marks []error // hidden errors as marks, take effects only when Is and '%v' in fmt. +} + +func (e markError) Error() string { + if e.err == nil { + return "" + } + return e.err.Error() +} + +func (e markError) Format(s fmt.State, verb rune) { + if e.err == nil { + return + } + switch verb { + case 'v': + if s.Flag('+') { + me := e.clean() + if len(me.marks) == 0 { + _, _ = fmt.Fprintf(s, "%+v", me.err) + return + } + _, _ = io.WriteString(s, "Marked errors occurred:\n") + + _, _ = fmt.Fprintf(s, "|\t%+v", me.err) + for _, mark := range me.marks { + _, _ = fmt.Fprintf(s, "\nM\t%+v", mark) + } + return + } + fallthrough + case 's', 'q': + _, _ = io.WriteString(s, e.Error()) + } +} + +// clean removes all none nil elem in all the marks +func (e markError) clean() markError { + var marks []error + for _, err := range e.marks { + if err != nil { + marks = append(marks, err) + } + } + return markError{ + err: e.err, + marks: marks, + } +} + +// Is reports whether any error in markError or it's mark errors matches target. +func (e markError) Is(target error) bool { + if Is(e.err, target) { + return true + } + for _, err := range e.marks { + if Is(err, target) { + return true + } + } + return false +} + +// Unwrap returns the error in e, if there is exactly one. If there is more than one +// error, Unwrap returns nil, since there is no way to determine which should be +// returned. +func (e markError) Unwrap() error { + return e.err +} diff --git a/src/errors/mark_test.go b/src/errors/mark_test.go new file mode 100644 index 0000000000000..6cd4209645a1d --- /dev/null +++ b/src/errors/mark_test.go @@ -0,0 +1,114 @@ +// Copyright 2022 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_test + +import ( + "errors" + "fmt" +) + +func ExampleMark() { + err := errors.Mark(nil, nil) + fmt.Println(err) + fmt.Println("-----") + err = errors.Mark(fmt.Errorf("whoops"), nil) + fmt.Println(err) + fmt.Println("-----") + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo")) + fmt.Println(err) + fmt.Println("-----") + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar")) + fmt.Println(err) + fmt.Println("-----") + + // Output: + // + // ----- + // whoops + // ----- + // whoops + // ----- + // whoops + // ----- +} + +func ExampleMark_format() { + err := errors.Mark(nil) + fmt.Printf("v: %v\n", err) + fmt.Printf("+v: %+v\n", err) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), nil) + fmt.Printf("v: %v\n", err) + fmt.Printf("+v: %+v\n", err) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo")) + fmt.Printf("v: %v\n", err) + fmt.Printf("+v: %+v\n", err) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar")) + fmt.Printf("v: %v\n", err) + fmt.Printf("+v: %+v\n", err) + fmt.Println("-----") + + // Output: + // v: + // +v: + // ----- + // v: whoops + // +v: whoops + // ----- + // v: whoops + // +v: Marked errors occurred: + // | whoops + // M foo + // ----- + // v: whoops + // +v: Marked errors occurred: + // | whoops + // M foo + // M bar + // ----- +} + +func ExampleMark_is() { + var mark = errors.New("mark") + err := errors.Mark(nil, mark) + fmt.Printf("%v\n", errors.Is(err, nil)) + fmt.Printf("%v\n", errors.Is(err, mark)) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), nil, mark) + fmt.Printf("%v\n", errors.Is(err, nil)) + fmt.Printf("%v\n", errors.Is(err, mark)) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), mark) + fmt.Printf("%v\n", errors.Is(err, nil)) + fmt.Printf("%v\n", errors.Is(err, mark)) + fmt.Println("-----") + + err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar"), mark) + fmt.Printf("%v\n", errors.Is(err, nil)) + fmt.Printf("%v\n", errors.Is(err, mark)) + fmt.Println("-----") + + // Output: + // true + // false + // ----- + // false + // true + // ----- + // false + // true + // ----- + // false + // true + // ----- + +}