Skip to content

Commit d3609cf

Browse files
authored
Utilize tabs for nested error string representation (#36)
- Fixed a bug as a result of which it was impossible to match embedded errors' key-value pairs with their messages - Modified error formatting so that every embedded message is indented and placed on a new line Example: ``` Error #1 Error #2 [key2.1=value2.1, key2.2=value2.2] Error #3 [key3=value3] Resource not found ```
1 parent f999c61 commit d3609cf

File tree

8 files changed

+265
-412
lines changed

8 files changed

+265
-412
lines changed

build/install_tools.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ $(go env GOPATH)/bin/golangci-lint version | grep ${GOLNT_VERSION} || curl -sSfL
1515

1616
if [[ "${OSTYPE}" == "linux"* ]]; then
1717
/tmp/typos --version | grep ${TYPOS_VERSION} || wget -qO- https://github.com/crate-ci/typos/releases/download/v${TYPOS_VERSION}/typos-v${TYPOS_VERSION}-x86_64-unknown-linux-musl.tar.gz | tar -zxf - -C /tmp/ ./typos
18+
sudo snap install mdl
1819
elif [[ "${OSTYPE}" == "darwin"* ]]; then
1920
/tmp/typos --version | grep ${TYPOS_VERSION} || wget -qO- https://github.com/crate-ci/typos/releases/download/v${TYPOS_VERSION}/typos-v${TYPOS_VERSION}-x86_64-apple-darwin.tar.gz | tar -zxf - -C /tmp/ ./typos
2021
fi
2122

2223
pip3 install addlicense mdv yamllint
23-
sudo snap install mdl

docs/DOCUMENTATION.md

Lines changed: 133 additions & 374 deletions
Large diffs are not rendered by default.

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ require (
2525
github.com/jinzhu/now v1.1.5 // indirect
2626
github.com/pmezard/go-difflib v1.0.0 // indirect
2727
golang.org/x/crypto v0.5.0 // indirect
28-
golang.org/x/sys v0.4.0 // indirect
29-
golang.org/x/text v0.6.0 // indirect
28+
golang.org/x/sys v0.6.0 // indirect
29+
golang.org/x/text v0.8.0 // indirect
3030
gopkg.in/yaml.v3 v3.0.1 // indirect
3131
)
3232

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
7878
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7979
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8080
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
81-
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
82-
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
81+
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
82+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8383
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
8484
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
8585
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -89,8 +89,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8989
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
9090
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
9191
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
92-
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
93-
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
92+
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
93+
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
9494
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
9595
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
9696
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

pkg/datastore/example_multiinstance_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ func ExampleDataStore_multiInstance() {
9999
// [Dev/P1337] Bob: 31 <nil>
100100
// [Prod/P1337] John: 36 <nil>
101101
// [Dev/P3] Pat: 39 <nil>
102-
// Unable to locate record: record not found[ record=[/P3] : 0,]
102+
// Unable to locate record [record=[/P3] : 0]
103+
// record not found
103104
// 1 <nil>
104105
// 1 <nil>
105106
// 0 <nil>

pkg/datastore/example_multitenancy_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ func ExampleDataStore_multiTenancy() {
9797
// [Coke/P1337] Bob: 31 <nil>
9898
// [Pepsi/P1337] John: 36 <nil>
9999
// [Coke/P3] Pat: 39 <nil>
100-
// Unable to locate record: record not found[ record=[/P3] : 0,]
100+
// Unable to locate record [record=[/P3] : 0]
101+
// record not found
101102
// 1 <nil>
102103
// 1 <nil>
103104
// 0 <nil>

pkg/errors/error_codes.go

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"fmt"
2424
"reflect"
25+
"strings"
2526
"unsafe"
2627
)
2728

@@ -43,33 +44,38 @@ const (
4344
DB_ROLE = ErrorContextKey("DbRole")
4445
)
4546

46-
func contextToString(ctx interface{}) string {
47-
ctxStr := ""
48-
contextValues := reflect.ValueOf(ctx).Elem()
49-
contextKeys := reflect.TypeOf(ctx).Elem()
50-
51-
if contextKeys.Kind() == reflect.Struct {
52-
for i := 0; i < contextValues.NumField(); i++ {
53-
reflectValue := contextValues.Field(i)
54-
reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem()
55-
56-
reflectField := contextKeys.Field(i)
57-
58-
if reflectField.Name == "Context" {
59-
ctxStr += contextToString(reflectValue.Interface())
60-
} else {
61-
switch reflectField.Name {
62-
case "key":
63-
ctxStr += fmt.Sprintf(" %+v=", reflectValue.Interface())
64-
case "val":
65-
ctxStr += fmt.Sprintf("%+v,", reflectValue.Interface())
66-
default:
67-
ctxStr += fmt.Sprintf("%+v=%+v,", reflectField.Name, reflectValue.Interface())
68-
}
47+
func contextToString(ctx context.Context) string {
48+
contextType := reflect.TypeOf(ctx).Elem()
49+
if contextType.Kind() != reflect.Struct {
50+
return ""
51+
}
52+
53+
contextValue := reflect.ValueOf(ctx).Elem()
54+
numFields := contextValue.NumField()
55+
ctxStrings := make([]string, 0, numFields)
56+
for i := 0; i < numFields; i++ {
57+
reflectValue := contextValue.Field(i)
58+
reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem()
59+
60+
reflectField := contextType.Field(i)
61+
switch reflectField.Name {
62+
case "Context":
63+
innerCtxStr := contextToString(reflectValue.Interface().(context.Context))
64+
if len(innerCtxStr) > 0 {
65+
ctxStrings = append(ctxStrings, innerCtxStr+", ")
6966
}
67+
case "key":
68+
keyStr := fmt.Sprintf("%+v=", reflectValue.Interface())
69+
ctxStrings = append(ctxStrings, keyStr)
70+
case "val":
71+
valueStr := fmt.Sprintf("%+v", reflectValue.Interface())
72+
ctxStrings = append(ctxStrings, valueStr)
73+
default:
74+
str := fmt.Sprintf("%+v=%+v,", reflectField.Name, reflectValue.Interface())
75+
ctxStrings = append(ctxStrings, str)
7076
}
7177
}
72-
return ctxStr
78+
return strings.Join(ctxStrings, "")
7379
}
7480

7581
type DbError struct {
@@ -79,14 +85,38 @@ type DbError struct {
7985
}
8086

8187
func (e *DbError) Error() string {
82-
str := e.msg
88+
var errStrBuilder strings.Builder
89+
90+
// Add error's own message
91+
errStrBuilder.WriteString(e.msg)
92+
93+
// Add error's key-value pairs
94+
if e.ctx != nil {
95+
errStrBuilder.WriteString(" [")
96+
errStrBuilder.WriteString(contextToString(e.ctx))
97+
errStrBuilder.WriteRune(']')
98+
}
99+
100+
// Add nested error on a new line
83101
if e.err != nil {
84-
str = str + ": " + e.err.Error()
102+
errStrBuilder.WriteRune('\n')
103+
errStrBuilder.WriteString(e.err.Error())
85104
}
86-
if e.ctx != nil {
87-
str = str + "[" + contextToString(e.ctx) + "]"
105+
106+
// Indent each nested error
107+
errStr := errStrBuilder.String()
108+
if split := strings.Split(errStr, "\n"); len(split) > 1 {
109+
errStrBuilder.Reset()
110+
for i := range split {
111+
split[i] = strings.TrimSpace(split[i])
112+
for tabsPrinted := 0; tabsPrinted < i; tabsPrinted++ {
113+
split[i] = "\t" + split[i]
114+
}
115+
}
116+
errStr = strings.Join(split, "\n")
88117
}
89-
return str
118+
119+
return errStr
90120
}
91121

92122
func (e *DbError) Is(target error) bool {

pkg/errors/error_codes_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package errors_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"regexp"
7+
"strings"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
. "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/errors"
12+
)
13+
14+
func TestErrorToStringConversion_ContextValuesPlacing(t *testing.T) {
15+
assert := assert.New(t)
16+
t.Parallel()
17+
18+
err4 := errors.New("Resource not found")
19+
err3 := ErrBaseDb.With("Error #3").WithValue("key3", "value3").Wrap(err4)
20+
err2 := ErrBaseDb.With("Error #2").WithValue("key2.1", "value2.1").
21+
WithValue("key2.2", "value2.2").Wrap(err3)
22+
err1 := ErrBaseDb.With("Error #1").Wrap(err2)
23+
24+
var actualErrStr string = err1.Error()
25+
matched, err := regexp.MatchString("\\] *\\[", actualErrStr)
26+
assert.NoError(err)
27+
assert.False(matched, "Expected error's string version not to place all context values at the very end")
28+
29+
const expectedErrStr = `Error #1
30+
Error #2 [key2.1=value2.1, key2.2=value2.2]
31+
Error #3 [key3=value3]
32+
Resource not found`
33+
assert.Equal(expectedErrStr, actualErrStr)
34+
}
35+
36+
func TestErrorToStringConversion_MultilineWithIndentation(t *testing.T) {
37+
assert := assert.New(t)
38+
t.Parallel()
39+
40+
var err error = ErrBaseDb.With("Error #6")
41+
const numEmbeddedErrors = 5
42+
for i := numEmbeddedErrors; i > 0; i-- {
43+
err = ErrBaseDb.With(fmt.Sprintf("Error #%d", i)).Wrap(err)
44+
}
45+
46+
actualErrStr := err.Error()
47+
assert.Equalf(numEmbeddedErrors, strings.Count(actualErrStr, "\n"), "Expected error to contain %d newlines", numEmbeddedErrors)
48+
49+
expectedNumTabs := 5
50+
for i := 0; i < numEmbeddedErrors; i++ {
51+
expectedNumTabs += i
52+
}
53+
assert.Equalf(expectedNumTabs, strings.Count(actualErrStr, "\t"), "Expected error to contain %d tabs", expectedNumTabs)
54+
55+
const expectedErrStr = `Error #1
56+
Error #2
57+
Error #3
58+
Error #4
59+
Error #5
60+
Error #6`
61+
assert.Equal(expectedErrStr, actualErrStr)
62+
}

0 commit comments

Comments
 (0)