Skip to content

Commit

Permalink
errors: add errors.Mark to support error marks for errors.Is and '%v'…
Browse files Browse the repository at this point in the history
… in fmt, without any side effect otherwise
  • Loading branch information
haixinchen committed Jan 25, 2022
1 parent 16d6a52 commit 5ee6c42
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/errors/mark.go
Original file line number Diff line number Diff line change
@@ -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
}
114 changes: 114 additions & 0 deletions src/errors/mark_test.go
Original file line number Diff line number Diff line change
@@ -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:
// <nil>
// -----
// 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: <nil>
// +v: <nil>
// -----
// 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
// -----

}

0 comments on commit 5ee6c42

Please sign in to comment.