Skip to content

Commit

Permalink
Add AppendInto function (#31)
Browse files Browse the repository at this point in the history
This adds an AppendInto function that behaves similarly to Append
except, it operates on a `*error` on the left side and it reports
whether the right side error was non-nil.

    func AppendInto(*error, error) (errored bool)

Making the left side a pointer aligns with the fast path of `Append`.

Returning whether the right error was non-nil aligns with the standard
`if err := ...; err != nil` pattern.

```diff
-if err := thing(); err != nil {
+if multierr.AppendInto(&err, thing()) {
   continue
 }
```

Resolves #21
  • Loading branch information
abhinav authored Nov 4, 2019
1 parent c3fc3d0 commit 60a318a
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 2 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Releases
========

v1.4.0 (unreleased)
===================

- Add `AppendInto` function to more ergonomically build errors inside a
loop.


v1.3.0 (2019-10-29)
===================

Expand Down
52 changes: 51 additions & 1 deletion error.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2017 Uber Technologies, Inc.
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -397,3 +397,53 @@ func Append(left error, right error) error {
errors := [2]error{left, right}
return fromSlice(errors[0:])
}

// AppendInto appends an error into the destination of an error pointer and
// returns whether the error being appended was non-nil.
//
// var err error
// multierr.AppendInto(&err, r.Close())
// multierr.AppendInto(&err, w.Close())
//
// The above is equivalent to,
//
// err := multierr.Append(r.Close(), w.Close())
//
// As AppendInto reports whether the provided error was non-nil, it may be
// used to build a multierr error in a loop more ergonomically. For example:
//
// var err error
// for line := range lines {
// var item Item
// if multierr.AppendInto(&err, parse(line, &item)) {
// continue
// }
// items = append(items, item)
// }
//
// Compare this with a verison that relies solely on Append:
//
// var err error
// for line := range lines {
// var item Item
// if parseErr := parse(line, &item); parseErr != nil {
// err = multierr.Append(err, parseErr)
// continue
// }
// items = append(items, item)
// }
func AppendInto(into *error, err error) (errored bool) {
if into == nil {
// We panic if 'into' is nil. This is not documented above
// because suggesting that the pointer must be non-nil may
// confuse users into thinking that the error that it points
// to must be non-nil.
panic("misuse of multierr.AppendInto: into pointer must not be nil")
}

if err == nil {
return false
}
*into = Append(*into, err)
return true
}
73 changes: 72 additions & 1 deletion error_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2017 Uber Technologies, Inc.
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -509,3 +509,74 @@ func TestNilMultierror(t *testing.T) {
require.Empty(t, err.Error())
require.Empty(t, err.Errors())
}

func TestAppendInto(t *testing.T) {
tests := []struct {
desc string
into *error
give error
want error
}{
{
desc: "append into empty",
into: new(error),
give: errors.New("foo"),
want: errors.New("foo"),
},
{
desc: "append into non-empty, non-multierr",
into: errorPtr(errors.New("foo")),
give: errors.New("bar"),
want: Combine(
errors.New("foo"),
errors.New("bar"),
),
},
{
desc: "append into non-empty multierr",
into: errorPtr(Combine(
errors.New("foo"),
errors.New("bar"),
)),
give: errors.New("baz"),
want: Combine(
errors.New("foo"),
errors.New("bar"),
errors.New("baz"),
),
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
assert.True(t, AppendInto(tt.into, tt.give))
assert.Equal(t, tt.want, *tt.into)
})
}
}

func TestAppendIntoNil(t *testing.T) {
t.Run("nil pointer panics", func(t *testing.T) {
assert.Panics(t, func() {
AppendInto(nil, errors.New("foo"))
})
})

t.Run("nil error is no-op", func(t *testing.T) {
t.Run("empty left", func(t *testing.T) {
var err error
assert.False(t, AppendInto(&err, nil))
assert.Nil(t, err)
})

t.Run("non-empty left", func(t *testing.T) {
err := errors.New("foo")
assert.False(t, AppendInto(&err, nil))
assert.Equal(t, errors.New("foo"), err)
})
})
}

func errorPtr(err error) *error {
return &err
}
22 changes: 22 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,25 @@ func ExampleErrors() {
// call 3 failed
// call 5 failed
}

func ExampleAppendInto() {
var err error

if multierr.AppendInto(&err, errors.New("foo")) {
fmt.Println("call 1 failed")
}

if multierr.AppendInto(&err, nil) {
fmt.Println("call 2 failed")
}

if multierr.AppendInto(&err, errors.New("baz")) {
fmt.Println("call 3 failed")
}

fmt.Println(err)
// Output:
// call 1 failed
// call 3 failed
// foo; baz
}

0 comments on commit 60a318a

Please sign in to comment.