diff --git a/libs/errors/errors.go b/libs/errors/errors.go index 6125fbf9d08..9c953701f5c 100644 --- a/libs/errors/errors.go +++ b/libs/errors/errors.go @@ -15,7 +15,15 @@ package errors -import "strings" +import ( + "fmt" + "strings" +) + +var ( + errSelfReference = fmt.Errorf("") + errParentReference = fmt.Errorf("") +) type CumulatedErrors struct { Errors []error @@ -26,9 +34,41 @@ func NewCumulatedErrors() *CumulatedErrors { } func (e *CumulatedErrors) Add(err error) { + // prevent adding this instance of cumulatedErrors to itself. + if err == e { + err = errSelfReference + } else if cerr, ok := err.(*CumulatedErrors); ok { + // nothing to add. + if !cerr.HasAny() { + return + } + // create a copy of the error we're adding + cpy := &CumulatedErrors{ + Errors: append([]error{}, cerr.Errors...), + } + // remove any references to the parent from the error we're adding. + err = cpy.checkRef(e, errParentReference) + } e.Errors = append(e.Errors, err) } +// check recursively if a cumulated errors object contains a certain reference, and if so, replace with a placehold, simple error. +// returns either itself (with the replaced references), or a replacement error. +func (e *CumulatedErrors) checkRef(ref *CumulatedErrors, repl error) error { + if e == ref { + return repl + } + // recursively remove a given reference. + for i, subE := range e.Errors { + if subE == ref { + e.Errors[i] = repl + } else if cErr, ok := subE.(*CumulatedErrors); ok { + e.Errors[i] = cErr.checkRef(ref, repl) + } + } + return e +} + func (e *CumulatedErrors) HasAny() bool { return len(e.Errors) > 0 } diff --git a/libs/errors/errors_test.go b/libs/errors/errors_test.go new file mode 100644 index 00000000000..f2513ae5e4e --- /dev/null +++ b/libs/errors/errors_test.go @@ -0,0 +1,69 @@ +// Copyright (C) 2023 Gobalsky Labs Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package errors_test + +import ( + "fmt" + "testing" + + "code.vegaprotocol.io/vega/libs/errors" + + "github.com/stretchr/testify/require" +) + +func TestCircularreferences(t *testing.T) { + parent := errors.NewCumulatedErrors() + child := errors.NewCumulatedErrors() + nested := errors.NewCumulatedErrors() + errs := []error{ + fmt.Errorf("simple error 1"), + fmt.Errorf("simple error 2"), + fmt.Errorf("simple error 3"), + } + t.Run("try to add parent to itself", func(t *testing.T) { + parent.Add(parent) + expect := "" + require.True(t, parent.HasAny()) + require.Equal(t, expect, parent.Error()) + }) + t.Run("try nesting without circular references", func(t *testing.T) { + child.Add(errs[0]) + parent.Add(child) + expect := fmt.Sprintf(", also %s", errs[0].Error()) + require.Equal(t, expect, parent.Error()) + }) + t.Run("try adding empty cumulated error", func(t *testing.T) { + parent.Add(nested) + // still the same expected value + expect := fmt.Sprintf(", also %s", errs[0].Error()) + require.False(t, nested.HasAny()) + require.Equal(t, expect, parent.Error()) + // adding to the nested error should not affect the parent. + nested.Add(errs[1]) + require.True(t, nested.HasAny()) + require.Equal(t, expect, parent.Error()) + }) + t.Run("try nesting both parent and child, and adding to both", func(t *testing.T) { + nested.Add(errs[2]) + nested.Add(child) + nested.Add(parent) + child.Add(nested) + parent.Add(nested) + // self reference>, also simple error 1, also simple error 2, also simple error 3, also simple error 1, also , also simple error 1 + expect := fmt.Sprintf(", also %s, also %s, also %s, also %s, also , also %s", errs[0].Error(), errs[1].Error(), errs[2].Error(), errs[0].Error(), errs[0].Error()) + require.Equal(t, expect, parent.Error()) + }) +}