Skip to content

Commit 565063a

Browse files
committed
refactors validation errors
1 parent 7b27eb5 commit 565063a

File tree

4 files changed

+165
-50
lines changed

4 files changed

+165
-50
lines changed

api.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ func errorAsJSON(err Error) []byte {
7171
return b
7272
}
7373

74+
func flattenComposite(errs *CompositeError) *CompositeError {
75+
var res []error
76+
for _, er := range errs.Errors {
77+
switch e := er.(type) {
78+
case *CompositeError:
79+
flat := flattenComposite(e)
80+
res = append(res, flat.Errors...)
81+
default:
82+
res = append(res, e)
83+
}
84+
}
85+
return CompositeValidationError(res...)
86+
}
87+
7488
// MethodNotAllowed creates a new method not allowed error
7589
func MethodNotAllowed(requested string, allow []string) Error {
7690
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
@@ -79,18 +93,26 @@ func MethodNotAllowed(requested string, allow []string) Error {
7993

8094
// ServeError the error handler interface implemenation
8195
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
82-
switch err.(type) {
96+
switch e := err.(type) {
97+
case *CompositeError:
98+
er := flattenComposite(e)
99+
ServeError(rw, r, er.Errors[0])
83100
case *MethodNotAllowedError:
84-
e := err.(*MethodNotAllowedError)
85101
rw.Header().Add("Allow", strings.Join(err.(*MethodNotAllowedError).Allowed, ","))
86102
rw.WriteHeader(int(e.Code()))
87-
rw.Write(errorAsJSON(e))
103+
if r == nil || r.Method != "HEAD" {
104+
rw.Write(errorAsJSON(e))
105+
}
88106
case Error:
89-
rw.WriteHeader(int(err.(Error).Code()))
90-
rw.Write(errorAsJSON(err.(Error)))
107+
rw.WriteHeader(int(e.Code()))
108+
if r == nil || r.Method != "HEAD" {
109+
rw.Write(errorAsJSON(e))
110+
}
91111
default:
92112
rw.WriteHeader(http.StatusInternalServerError)
93-
rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
113+
if r == nil || r.Method != "HEAD" {
114+
rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
115+
}
94116
}
95117

96118
}

headers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"net/http"
66
)
77

8-
// Validation represents a failure of a precondidition
8+
// Validation represents a failure of a precondition
99
type Validation struct {
1010
code int32
1111
Name string

schema.go

Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,147 @@
11
package errors
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
"strings"
6+
)
47

58
const (
6-
invalidType = `%s is an invalid type name`
7-
typeFail = `%s in %s must be of type %s`
8-
typeFailWithData = `%s in %s must be of type %s: %q`
9-
typeFailWithError = `%s in %s must be of type %s, because: %s`
10-
requiredFail = `%s in %s is required`
11-
tooLongMessage = `%s in %s should be at most %d chars long`
12-
tooShortMessage = `%s in %s should be at least %d chars long`
13-
patternFail = `%s in %s should match '%s'`
14-
enumFail = `%s in %s should be one of %v`
15-
mulitpleOfFail = `%s in %s should be a multiple of %v`
16-
maxIncFail = `%s in %s should be less than or equal to %v`
17-
maxExcFail = `%s in %s should be less than %v`
18-
minIncFail = `%s in %s should be greater than or equal to %v`
19-
minExcFail = `%s in %s should be greater than %v`
20-
uniqueFail = `%s in %s shouldn't contain duplicates`
21-
maxItemsFail = `%s in %s should have at most %d items`
22-
minItemsFail = `%s in %s should have at least %d items`
23-
typeFailNoIn = `%s must be of type %s`
24-
typeFailWithDataNoIn = `%s must be of type %s: %q`
25-
typeFailWithErrorNoIn = `%s must be of type %s, because: %s`
26-
requiredFailNoIn = `%s is required`
27-
tooLongMessageNoIn = `%s should be at most %d chars long`
28-
tooShortMessageNoIn = `%s should be at least %d chars long`
29-
patternFailNoIn = `%s should match '%s'`
30-
enumFailNoIn = `%s should be one of %v`
31-
mulitpleOfFailNoIn = `%s should be a multiple of %v`
32-
maxIncFailNoIn = `%s should be less than or equal to %v`
33-
maxExcFailNoIn = `%s should be less than %v`
34-
minIncFailNoIn = `%s should be greater than or equal to %v`
35-
minExcFailNoIn = `%s should be greater than %v`
36-
uniqueFailNoIn = `%s shouldn't contain duplicates`
37-
maxItemsFailNoIn = `%s should have at most %d items`
38-
minItemsFailNoIn = `%s should have at least %d items`
39-
noAdditionalItems = "%s in %s can't have additional items"
40-
noAdditionalItemsNoIn = "%s can't have additional items"
9+
invalidType = "%s is an invalid type name"
10+
typeFail = "%s in %s must be of type %s"
11+
typeFailWithData = "%s in %s must be of type %s: %q"
12+
typeFailWithError = "%s in %s must be of type %s, because: %s"
13+
requiredFail = "%s in %s is required"
14+
tooLongMessage = "%s in %s should be at most %d chars long"
15+
tooShortMessage = "%s in %s should be at least %d chars long"
16+
patternFail = "%s in %s should match '%s'"
17+
enumFail = "%s in %s should be one of %v"
18+
mulitpleOfFail = "%s in %s should be a multiple of %v"
19+
maxIncFail = "%s in %s should be less than or equal to %v"
20+
maxExcFail = "%s in %s should be less than %v"
21+
minIncFail = "%s in %s should be greater than or equal to %v"
22+
minExcFail = "%s in %s should be greater than %v"
23+
uniqueFail = "%s in %s shouldn't contain duplicates"
24+
maxItemsFail = "%s in %s should have at most %d items"
25+
minItemsFail = "%s in %s should have at least %d items"
26+
typeFailNoIn = "%s must be of type %s"
27+
typeFailWithDataNoIn = "%s must be of type %s: %q"
28+
typeFailWithErrorNoIn = "%s must be of type %s, because: %s"
29+
requiredFailNoIn = "%s is required"
30+
tooLongMessageNoIn = "%s should be at most %d chars long"
31+
tooShortMessageNoIn = "%s should be at least %d chars long"
32+
patternFailNoIn = "%s should match '%s'"
33+
enumFailNoIn = "%s should be one of %v"
34+
mulitpleOfFailNoIn = "%s should be a multiple of %v"
35+
maxIncFailNoIn = "%s should be less than or equal to %v"
36+
maxExcFailNoIn = "%s should be less than %v"
37+
minIncFailNoIn = "%s should be greater than or equal to %v"
38+
minExcFailNoIn = "%s should be greater than %v"
39+
uniqueFailNoIn = "%s shouldn't contain duplicates"
40+
maxItemsFailNoIn = "%s should have at most %d items"
41+
minItemsFailNoIn = "%s should have at least %d items"
42+
noAdditionalItems = "%s in %s can't have additional items"
43+
noAdditionalItemsNoIn = "%s can't have additional items"
44+
tooFewProperties = "%s in %s should have at least %d properties"
45+
tooFewPropertiesNoIn = "%s should have at least %d properties"
46+
tooManyProperties = "%s in %s should have at most %d properties"
47+
tooManyPropertiesNoIn = "%s should have at most %d properties"
48+
unallowedProperty = "%s.%s in %s is a forbidden property"
49+
unallowedPropertyNoIn = "%s.%s is a forbidden property"
50+
failedAllPatternProps = "%s.%s in %s failed all pattern properties"
51+
failedAllPatternPropsNoIn = "%s.%s failed all pattern properties"
4152
)
4253

54+
// CompositeError is an error that groups several errors together
55+
type CompositeError struct {
56+
Errors []error
57+
code int32
58+
message string
59+
}
60+
61+
func (c *CompositeError) Code() int32 {
62+
return c.code
63+
}
64+
65+
func (c *CompositeError) Error() string {
66+
if len(c.Errors) > 0 {
67+
msgs := []string{c.message + ":"}
68+
for _, e := range c.Errors {
69+
msgs = append(msgs, e.Error())
70+
}
71+
return strings.Join(msgs, "\n")
72+
}
73+
return c.message
74+
}
75+
4376
// CompositeValidationError an error to wrap a bunch of other errors
44-
func CompositeValidationError(errors ...Error) *Validation {
45-
return &Validation{
77+
func CompositeValidationError(errors ...error) *CompositeError {
78+
return &CompositeError{
4679
code: 422,
47-
Value: append([]Error{}, errors...),
80+
Errors: append([]error{}, errors...),
4881
message: "validation failure list",
4982
}
5083
}
5184

85+
// FailedAllPatternProperties an error for when the property doesn't match a pattern
86+
func FailedAllPatternProperties(name, in, key string) *Validation {
87+
msg := fmt.Sprintf(failedAllPatternProps, name, key, in)
88+
if in == "" {
89+
msg = fmt.Sprintf(failedAllPatternPropsNoIn, name, key)
90+
}
91+
return &Validation{
92+
code: 422,
93+
Name: name,
94+
In: in,
95+
Value: key,
96+
message: msg,
97+
}
98+
}
99+
100+
// PropertyNotAllowed an error for when the property doesn't match a pattern
101+
func PropertyNotAllowed(name, in, key string) *Validation {
102+
msg := fmt.Sprintf(unallowedProperty, name, key, in)
103+
if in == "" {
104+
msg = fmt.Sprintf(unallowedPropertyNoIn, name, key)
105+
}
106+
return &Validation{
107+
code: 422,
108+
Name: name,
109+
In: in,
110+
Value: key,
111+
message: msg,
112+
}
113+
}
114+
115+
// TooFewProperties an error for an object with too few properties
116+
func TooFewProperties(name, in string, n int64) *Validation {
117+
msg := fmt.Sprintf(tooFewProperties, name, in, n)
118+
if in == "" {
119+
msg = fmt.Sprintf(tooFewPropertiesNoIn, name, n)
120+
}
121+
return &Validation{
122+
code: 422,
123+
Name: name,
124+
In: in,
125+
Value: n,
126+
message: msg,
127+
}
128+
}
129+
130+
// TooManyProperties an error for an object with too many properties
131+
func TooManyProperties(name, in string, n int64) *Validation {
132+
msg := fmt.Sprintf(tooManyProperties, name, in, n)
133+
if in == "" {
134+
msg = fmt.Sprintf(tooManyPropertiesNoIn, name, n)
135+
}
136+
return &Validation{
137+
code: 422,
138+
Name: name,
139+
In: in,
140+
Value: n,
141+
message: msg,
142+
}
143+
}
144+
52145
// AdditionalItemsNotAllowed an error for invalid additional items
53146
func AdditionalItemsNotAllowed(name, in string) *Validation {
54147
msg := fmt.Sprintf(noAdditionalItems, name, in)

schema_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ func TestSchemaErrors(t *testing.T) {
183183
assert.Equal(t, 422, err.Code())
184184
assert.Equal(t, "the collection format \"yada\" is not supported for the query param \"something\"", err.Error())
185185

186-
err = CompositeValidationError()
187-
assert.Error(t, err)
188-
assert.Equal(t, 422, err.Code())
189-
assert.Equal(t, "validation failure list", err.Error())
186+
err2 := CompositeValidationError()
187+
assert.Error(t, err2)
188+
assert.Equal(t, 422, err2.Code())
189+
assert.Equal(t, "validation failure list", err2.Error())
190190
}

0 commit comments

Comments
 (0)