Skip to content

Commit d4cab14

Browse files
authored
Merge pull request #83 from Scalingo/fix/gohandlers/15
Better handling of ValidationErrors
2 parents 807a346 + ba1380a commit d4cab14

File tree

4 files changed

+174
-58
lines changed

4 files changed

+174
-58
lines changed

errors/cause.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package errors
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/pkg/errors"
7+
)
8+
9+
// IsRootCause return true if the cause of the given error is the same type as
10+
// mytype.
11+
// This function takes the cause of an error if the errors stack has been
12+
// wrapped with errors.Wrapf or errgo.Notef or errgo.NoteMask or errgo.Mask.
13+
//
14+
// Example:
15+
// errors.IsRootCause(err, &ValidationErrors{})
16+
func IsRootCause(err error, mytype interface{}) bool {
17+
t := reflect.TypeOf(mytype)
18+
errCause := errors.Cause(err)
19+
errRoot := ErrgoRoot(err)
20+
return reflect.TypeOf(errCause) == t || reflect.TypeOf(errRoot) == t
21+
}
22+
23+
// RootCause returns the cause of an errors stack, whatever the method they used
24+
// to be stacked: either errgo.Notef or errors.Wrapf.
25+
func RootCause(err error) error {
26+
errCause := errors.Cause(err)
27+
if errCause == nil {
28+
errCause = ErrgoRoot(err)
29+
}
30+
return errCause
31+
}

errors/cause_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package errors
2+
3+
import (
4+
"testing"
5+
6+
"github.com/pkg/errors"
7+
"github.com/stretchr/testify/assert"
8+
errgo "gopkg.in/errgo.v1"
9+
)
10+
11+
func Test_IsRootCause(t *testing.T) {
12+
t.Run("given an error stack with errgo.Notef", func(t *testing.T) {
13+
var err error
14+
err = (&ValidationErrors{})
15+
err = errgo.Notef(err, "biniou")
16+
17+
assert.True(t, IsRootCause(err, &ValidationErrors{}))
18+
assert.False(t, IsRootCause(err, ValidationErrors{}))
19+
})
20+
21+
t.Run("given an error stack with errors.Wrapf", func(t *testing.T) {
22+
var err error
23+
err = (&ValidationErrors{})
24+
err = errors.Wrapf(err, "biniou")
25+
26+
assert.True(t, IsRootCause(err, &ValidationErrors{}))
27+
assert.False(t, IsRootCause(err, ValidationErrors{}))
28+
})
29+
}
30+
31+
func Test_RootCause(t *testing.T) {
32+
t.Run("given an error stack with errgo.Mask", func(t *testing.T) {
33+
var err error
34+
err = (&ValidationErrors{
35+
Errors: map[string][]string{
36+
"test": []string{"biniou"},
37+
},
38+
})
39+
err = errgo.Mask(err, errgo.Any)
40+
41+
assert.Equal(t, "test=biniou", RootCause(err).Error())
42+
})
43+
44+
t.Run("given an error stack with errgo.Notef", func(t *testing.T) {
45+
var err error
46+
err = (&ValidationErrors{
47+
Errors: map[string][]string{
48+
"test": []string{"biniou"},
49+
},
50+
})
51+
err = errgo.Notef(err, "pouet")
52+
53+
assert.Equal(t, "test=biniou", RootCause(err).Error())
54+
})
55+
56+
t.Run("given an error stack with errors.Wrap", func(t *testing.T) {
57+
var err error
58+
err = (&ValidationErrors{
59+
Errors: map[string][]string{
60+
"test": []string{"biniou"},
61+
},
62+
})
63+
err = errors.Wrap(err, "pouet")
64+
65+
assert.Equal(t, "test=biniou", RootCause(err).Error())
66+
})
67+
}

errors/validation_errors.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package errors
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
)
7+
8+
// ValidationErrors store each errors associated to every fields of a model
9+
type ValidationErrors struct {
10+
Errors map[string][]string `json:"errors"`
11+
}
12+
13+
func (v *ValidationErrors) Error() string {
14+
var buffer bytes.Buffer
15+
16+
for field, errors := range v.Errors {
17+
buffer.WriteString(field)
18+
buffer.WriteString("=")
19+
buffer.WriteString(strings.Join(errors, ","))
20+
}
21+
return buffer.String()
22+
}
23+
24+
// ValidationErrorsBuilder is used to provide a simple way to create a ValidationErrors struct. The typical usecase is:
25+
// func (m *MyModel) Validate(ctx context.Context) *ValidationErrors {
26+
// validations := document.NewValidationErrorsBuilder()
27+
//
28+
// if m.Name == "" {
29+
// validations.Set("name", "should not be empty")
30+
// }
31+
//
32+
// if m.Email == "" {
33+
// validations.Set("email", "should not be empty")
34+
// }
35+
//
36+
// return validations.Build()
37+
// }
38+
type ValidationErrorsBuilder struct {
39+
errors map[string][]string
40+
}
41+
42+
// NewValidationErrors return an empty ValidationErrors struct
43+
func NewValidationErrorsBuilder() *ValidationErrorsBuilder {
44+
return &ValidationErrorsBuilder{
45+
errors: make(map[string][]string),
46+
}
47+
}
48+
49+
// Set will add an error on a specific field, if the field already contains an error, it will just add it to the current errors list
50+
func (v *ValidationErrorsBuilder) Set(field, err string) {
51+
v.errors[field] = append(v.errors[field], err)
52+
}
53+
54+
// Get will return all errors set for a specific field
55+
func (v *ValidationErrorsBuilder) Get(field string) []string {
56+
return v.errors[field]
57+
}
58+
59+
// Build will send a ValidationErrors struct if there is some errors or nil if no errors has been defined
60+
func (v *ValidationErrorsBuilder) Build() *ValidationErrors {
61+
if len(v.errors) == 0 {
62+
return nil
63+
}
64+
65+
return &ValidationErrors{
66+
Errors: v.errors,
67+
}
68+
}

mongo/document/validation_error.go

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,18 @@
11
package document
22

33
import (
4-
"bytes"
5-
"strings"
4+
"github.com/Scalingo/go-utils/errors"
65
)
76

8-
// ValidationErrors store each errors associated to every fields of a model
9-
type ValidationErrors struct {
10-
Errors map[string][]string `json:"errors"`
11-
}
12-
13-
func (v *ValidationErrors) Error() string {
14-
var buffer bytes.Buffer
15-
16-
for field, errors := range v.Errors {
17-
buffer.WriteString(field)
18-
buffer.WriteString("=")
19-
buffer.WriteString(strings.Join(errors, ","))
20-
}
21-
return buffer.String()
22-
}
7+
// ValidationErrors is a type alias of errors.ValidationErrors. It is defined to
8+
// keep retro-compatibility
9+
type ValidationErrors = errors.ValidationErrors
2310

24-
// ValidationErrorsBuilder is used to provide a simple way to create a ValidationErrors struct. The typical usecase is:
25-
// func (m *MyModel) Validate(ctx context.Context) *ValidationErrors {
26-
// validations := document.NewValidationErrorsBuilder()
27-
//
28-
// if m.Name == "" {
29-
// validations.Set("name", "should not be empty")
30-
// }
31-
//
32-
// if m.Email == "" {
33-
// validations.Set("email", "should not be empty")
34-
// }
35-
//
36-
// return validations.Build()
37-
// }
38-
type ValidationErrorsBuilder struct {
39-
errors map[string][]string
40-
}
11+
// ValidationErrorsBuilder is a type alias of errors.ValidationErrorsBuilder.
12+
// It is defined to keep retro-compatibility
13+
type ValidationErrorsBuilder = errors.ValidationErrorsBuilder
4114

4215
// NewValidationErrors return an empty ValidationErrors struct
4316
func NewValidationErrorsBuilder() *ValidationErrorsBuilder {
44-
return &ValidationErrorsBuilder{
45-
errors: make(map[string][]string),
46-
}
47-
}
48-
49-
// Set will add an error on a specific field, if the field already contains an error, it will just add it to the current errors list
50-
func (v *ValidationErrorsBuilder) Set(field, err string) {
51-
v.errors[field] = append(v.errors[field], err)
52-
}
53-
54-
// Get will return all errors set for a specific field
55-
func (v *ValidationErrorsBuilder) Get(field string) []string {
56-
return v.errors[field]
57-
}
58-
59-
// Build will send a ValidationErrors struct if there is some errors or nil if no errors has been defined
60-
func (v *ValidationErrorsBuilder) Build() *ValidationErrors {
61-
if len(v.errors) == 0 {
62-
return nil
63-
}
64-
65-
return &ValidationErrors{
66-
Errors: v.errors,
67-
}
17+
return errors.NewValidationErrorsBuilder()
6818
}

0 commit comments

Comments
 (0)