Skip to content

Commit 35d054a

Browse files
Merge pull request #25 from go-viper/error
Replace internal joined error with errors.Join
2 parents 57a3d74 + 9e25c61 commit 35d054a

File tree

7 files changed

+137
-105
lines changed

7 files changed

+137
-105
lines changed

error.go

Lines changed: 0 additions & 50 deletions
This file was deleted.

internal/errors/errors.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package errors
2+
3+
import "errors"
4+
5+
func New(text string) error {
6+
return errors.New(text)
7+
}
8+
9+
func As(err error, target interface{}) bool {
10+
return errors.As(err, target)
11+
}

internal/errors/join.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build go1.20
2+
3+
package errors
4+
5+
import "errors"
6+
7+
func Join(errs ...error) error {
8+
return errors.Join(errs...)
9+
}

internal/errors/join_go1_19.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build !go1.20
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package errors
8+
9+
// Join returns an error that wraps the given errors.
10+
// Any nil error values are discarded.
11+
// Join returns nil if every value in errs is nil.
12+
// The error formats as the concatenation of the strings obtained
13+
// by calling the Error method of each element of errs, with a newline
14+
// between each string.
15+
//
16+
// A non-nil error returned by Join implements the Unwrap() []error method.
17+
func Join(errs ...error) error {
18+
n := 0
19+
for _, err := range errs {
20+
if err != nil {
21+
n++
22+
}
23+
}
24+
if n == 0 {
25+
return nil
26+
}
27+
e := &joinError{
28+
errs: make([]error, 0, n),
29+
}
30+
for _, err := range errs {
31+
if err != nil {
32+
e.errs = append(e.errs, err)
33+
}
34+
}
35+
return e
36+
}
37+
38+
type joinError struct {
39+
errs []error
40+
}
41+
42+
func (e *joinError) Error() string {
43+
// Since Join returns nil if every value in errs is nil,
44+
// e.errs cannot be empty.
45+
if len(e.errs) == 1 {
46+
return e.errs[0].Error()
47+
}
48+
49+
b := []byte(e.errs[0].Error())
50+
for _, err := range e.errs[1:] {
51+
b = append(b, '\n')
52+
b = append(b, err.Error()...)
53+
}
54+
// At this point, b has at least one byte '\n'.
55+
// return unsafe.String(&b[0], len(b))
56+
return string(b)
57+
}
58+
59+
func (e *joinError) Unwrap() []error {
60+
return e.errs
61+
}

mapstructure.go

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,13 @@ package mapstructure
160160

161161
import (
162162
"encoding/json"
163-
"errors"
164163
"fmt"
165164
"reflect"
166165
"sort"
167166
"strconv"
168167
"strings"
168+
169+
"github.com/go-viper/mapstructure/v2/internal/errors"
169170
)
170171

171172
// DecodeHookFunc is the callback function that can be used for
@@ -414,7 +415,15 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
414415
// Decode decodes the given raw interface to the target pointer specified
415416
// by the configuration.
416417
func (d *Decoder) Decode(input interface{}) error {
417-
return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
418+
err := d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
419+
420+
// Retain some of the original behavior when multiple errors ocurr
421+
var joinedErr interface{ Unwrap() []error }
422+
if errors.As(err, &joinedErr) {
423+
return fmt.Errorf("decoding failed due to the following error(s):\n\n%w", err)
424+
}
425+
426+
return err
418427
}
419428

420429
// Decodes an unknown data type into a specific reflection value.
@@ -881,7 +890,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
881890
valElemType := valType.Elem()
882891

883892
// Accumulate errors
884-
errors := make([]string, 0)
893+
var errs []error
885894

886895
// If the input data is empty, then we just match what the input data is.
887896
if dataVal.Len() == 0 {
@@ -903,15 +912,15 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
903912
// First decode the key into the proper type
904913
currentKey := reflect.Indirect(reflect.New(valKeyType))
905914
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
906-
errors = appendErrors(errors, err)
915+
errs = append(errs, err)
907916
continue
908917
}
909918

910919
// Next decode the data into the proper type
911920
v := dataVal.MapIndex(k).Interface()
912921
currentVal := reflect.Indirect(reflect.New(valElemType))
913922
if err := d.decode(fieldName, v, currentVal); err != nil {
914-
errors = appendErrors(errors, err)
923+
errs = append(errs, err)
915924
continue
916925
}
917926

@@ -921,12 +930,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
921930
// Set the built up map to the value
922931
val.Set(valMap)
923932

924-
// If we had errors, return those
925-
if len(errors) > 0 {
926-
return &joinedError{errors}
927-
}
928-
929-
return nil
933+
return errors.Join(errs...)
930934
}
931935

932936
func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
@@ -1164,7 +1168,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
11641168
}
11651169

11661170
// Accumulate any errors
1167-
errors := make([]string, 0)
1171+
var errs []error
11681172

11691173
for i := 0; i < dataVal.Len(); i++ {
11701174
currentData := dataVal.Index(i).Interface()
@@ -1175,19 +1179,14 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
11751179

11761180
fieldName := name + "[" + strconv.Itoa(i) + "]"
11771181
if err := d.decode(fieldName, currentData, currentField); err != nil {
1178-
errors = appendErrors(errors, err)
1182+
errs = append(errs, err)
11791183
}
11801184
}
11811185

11821186
// Finally, set the value to the slice we built up
11831187
val.Set(valSlice)
11841188

1185-
// If there were errors, we return those
1186-
if len(errors) > 0 {
1187-
return &joinedError{errors}
1188-
}
1189-
1190-
return nil
1189+
return errors.Join(errs...)
11911190
}
11921191

11931192
func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error {
@@ -1233,27 +1232,22 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value)
12331232
}
12341233

12351234
// Accumulate any errors
1236-
errors := make([]string, 0)
1235+
var errs []error
12371236

12381237
for i := 0; i < dataVal.Len(); i++ {
12391238
currentData := dataVal.Index(i).Interface()
12401239
currentField := valArray.Index(i)
12411240

12421241
fieldName := name + "[" + strconv.Itoa(i) + "]"
12431242
if err := d.decode(fieldName, currentData, currentField); err != nil {
1244-
errors = appendErrors(errors, err)
1243+
errs = append(errs, err)
12451244
}
12461245
}
12471246

12481247
// Finally, set the value to the array we built up
12491248
val.Set(valArray)
12501249

1251-
// If there were errors, we return those
1252-
if len(errors) > 0 {
1253-
return &joinedError{errors}
1254-
}
1255-
1256-
return nil
1250+
return errors.Join(errs...)
12571251
}
12581252

12591253
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
@@ -1315,7 +1309,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13151309
}
13161310

13171311
targetValKeysUnused := make(map[interface{}]struct{})
1318-
errors := make([]string, 0)
1312+
1313+
var errs []error
13191314

13201315
// This slice will keep track of all the structs we'll be decoding.
13211316
// There can be more than one struct if there are embedded structs
@@ -1369,8 +1364,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
13691364

13701365
if squash {
13711366
if fieldVal.Kind() != reflect.Struct {
1372-
errors = appendErrors(errors,
1373-
fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()))
1367+
errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()))
13741368
} else {
13751369
structs = append(structs, fieldVal)
13761370
}
@@ -1449,7 +1443,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14491443
}
14501444

14511445
if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
1452-
errors = appendErrors(errors, err)
1446+
errs = append(errs, err)
14531447
}
14541448
}
14551449

@@ -1464,7 +1458,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14641458

14651459
// Decode it as-if we were just decoding this map onto our map.
14661460
if err := d.decodeMap(name, remain, remainField.val); err != nil {
1467-
errors = appendErrors(errors, err)
1461+
errs = append(errs, err)
14681462
}
14691463

14701464
// Set the map to nil so we have none so that the next check will
@@ -1480,7 +1474,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14801474
sort.Strings(keys)
14811475

14821476
err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
1483-
errors = appendErrors(errors, err)
1477+
errs = append(errs, err)
14841478
}
14851479

14861480
if d.config.ErrorUnset && len(targetValKeysUnused) > 0 {
@@ -1491,11 +1485,11 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
14911485
sort.Strings(keys)
14921486

14931487
err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
1494-
errors = appendErrors(errors, err)
1488+
errs = append(errs, err)
14951489
}
14961490

1497-
if len(errors) > 0 {
1498-
return &joinedError{errors}
1491+
if err := errors.Join(errs...); err != nil {
1492+
return err
14991493
}
15001494

15011495
// Add the unused keys to the list of unused keys if we're tracking metadata

mapstructure_examples_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ func ExampleDecode_errors() {
6363

6464
fmt.Println(err.Error())
6565
// Output:
66-
// 5 error(s) decoding:
66+
// decoding failed due to the following error(s):
6767
//
68-
// * 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
69-
// * 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
70-
// * 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
71-
// * 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
72-
// * 'Name' expected type 'string', got unconvertible type 'int', value: '123'
68+
// 'Name' expected type 'string', got unconvertible type 'int', value: '123'
69+
// 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
70+
// 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
71+
// 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
72+
// 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
7373
}
7474

7575
func ExampleDecode_metadata() {

0 commit comments

Comments
 (0)