github.com/Laisky/zap@v1.27.0/zapcore/error_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zapcore_test
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	"go.uber.org/multierr"
    32  
    33  	//revive:disable:dot-imports
    34  	. "github.com/Laisky/zap/zapcore"
    35  )
    36  
    37  type errTooManyUsers int
    38  
    39  func (e errTooManyUsers) Error() string {
    40  	return fmt.Sprintf("%d too many users", int(e))
    41  }
    42  
    43  func (e errTooManyUsers) Format(s fmt.State, verb rune) {
    44  	// Implement fmt.Formatter, but don't add any information beyond the basic
    45  	// Error method.
    46  	if verb == 'v' && s.Flag('+') {
    47  		_, _ = io.WriteString(s, e.Error())
    48  	}
    49  }
    50  
    51  type errTooFewUsers int
    52  
    53  func (e errTooFewUsers) Error() string {
    54  	return fmt.Sprintf("%d too few users", int(e))
    55  }
    56  
    57  func (e errTooFewUsers) Format(s fmt.State, verb rune) {
    58  	if verb == 'v' && s.Flag('+') {
    59  		_, _ = io.WriteString(s, "verbose: ")
    60  		_, _ = io.WriteString(s, e.Error())
    61  	}
    62  }
    63  
    64  type customMultierr struct{}
    65  
    66  func (e customMultierr) Error() string {
    67  	return "great sadness"
    68  }
    69  
    70  func (e customMultierr) Errors() []error {
    71  	return []error{
    72  		errors.New("foo"),
    73  		nil,
    74  		multierr.Append(
    75  			errors.New("bar"),
    76  			errors.New("baz"),
    77  		),
    78  	}
    79  }
    80  
    81  func TestErrorEncoding(t *testing.T) {
    82  	tests := []struct {
    83  		key   string
    84  		iface any
    85  		want  map[string]any
    86  	}{
    87  		{
    88  			key:   "k",
    89  			iface: errTooManyUsers(2),
    90  			want: map[string]any{
    91  				"k": "2 too many users",
    92  			},
    93  		},
    94  		{
    95  			key:   "k",
    96  			iface: errTooFewUsers(2),
    97  			want: map[string]any{
    98  				"k":        "2 too few users",
    99  				"kVerbose": "verbose: 2 too few users",
   100  			},
   101  		},
   102  		{
   103  			key: "err",
   104  			iface: multierr.Combine(
   105  				errors.New("foo"),
   106  				errors.New("bar"),
   107  				errors.New("baz"),
   108  			),
   109  			want: map[string]any{
   110  				"err": "foo; bar; baz",
   111  				"errCauses": []any{
   112  					map[string]any{"error": "foo"},
   113  					map[string]any{"error": "bar"},
   114  					map[string]any{"error": "baz"},
   115  				},
   116  			},
   117  		},
   118  		{
   119  			key:   "e",
   120  			iface: customMultierr{},
   121  			want: map[string]any{
   122  				"e": "great sadness",
   123  				"eCauses": []any{
   124  					map[string]any{"error": "foo"},
   125  					map[string]any{
   126  						"error": "bar; baz",
   127  						"errorCauses": []any{
   128  							map[string]any{"error": "bar"},
   129  							map[string]any{"error": "baz"},
   130  						},
   131  					},
   132  				},
   133  			},
   134  		},
   135  		{
   136  			key:   "k",
   137  			iface: fmt.Errorf("failed: %w", errors.New("egad")),
   138  			want: map[string]any{
   139  				"k": "failed: egad",
   140  			},
   141  		},
   142  		{
   143  			key: "error",
   144  			iface: multierr.Combine(
   145  				fmt.Errorf("hello: %w",
   146  					multierr.Combine(errors.New("foo"), errors.New("bar")),
   147  				),
   148  				errors.New("baz"),
   149  				fmt.Errorf("world: %w", errors.New("qux")),
   150  			),
   151  			want: map[string]any{
   152  				"error": "hello: foo; bar; baz; world: qux",
   153  				"errorCauses": []any{
   154  					map[string]any{
   155  						"error": "hello: foo; bar",
   156  					},
   157  					map[string]any{"error": "baz"},
   158  					map[string]any{"error": "world: qux"},
   159  				},
   160  			},
   161  		},
   162  	}
   163  
   164  	for _, tt := range tests {
   165  		enc := NewMapObjectEncoder()
   166  		f := Field{Key: tt.key, Type: ErrorType, Interface: tt.iface}
   167  		f.AddTo(enc)
   168  		assert.Equal(t, tt.want, enc.Fields, "Unexpected output from field %+v.", f)
   169  	}
   170  }
   171  
   172  func TestRichErrorSupport(t *testing.T) {
   173  	f := Field{
   174  		Type:      ErrorType,
   175  		Interface: fmt.Errorf("failed: %w", errors.New("egad")),
   176  		Key:       "k",
   177  	}
   178  	enc := NewMapObjectEncoder()
   179  	f.AddTo(enc)
   180  	assert.Equal(t, "failed: egad", enc.Fields["k"], "Unexpected basic error message.")
   181  }
   182  
   183  func TestErrArrayBrokenEncoder(t *testing.T) {
   184  	t.Parallel()
   185  
   186  	f := Field{
   187  		Key:  "foo",
   188  		Type: ErrorType,
   189  		Interface: multierr.Combine(
   190  			errors.New("foo"),
   191  			errors.New("bar"),
   192  		),
   193  	}
   194  
   195  	failWith := errors.New("great sadness")
   196  	enc := NewMapObjectEncoder()
   197  	f.AddTo(brokenArrayObjectEncoder{
   198  		Err:           failWith,
   199  		ObjectEncoder: enc,
   200  	})
   201  
   202  	// Failure to add the field to the encoder
   203  	// causes the error to be added as a string field.
   204  	assert.Equal(t, "great sadness", enc.Fields["fooError"],
   205  		"Unexpected error message.")
   206  }
   207  
   208  // brokenArrayObjectEncoder is an ObjectEncoder
   209  // that builds a broken ArrayEncoder.
   210  type brokenArrayObjectEncoder struct {
   211  	ObjectEncoder
   212  	ArrayEncoder
   213  
   214  	Err error // error to return
   215  }
   216  
   217  func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler ArrayMarshaler) error {
   218  	return enc.ObjectEncoder.AddArray(key,
   219  		ArrayMarshalerFunc(func(ae ArrayEncoder) error {
   220  			enc.ArrayEncoder = ae
   221  			return marshaler.MarshalLogArray(enc)
   222  		}))
   223  }
   224  
   225  func (enc brokenArrayObjectEncoder) AppendObject(ObjectMarshaler) error {
   226  	return enc.Err
   227  }