Skip to content

Commit 00b42a8

Browse files
committed
feat: return multiple errors on validation call.
This commit introduces a new type: ValError and adjusts the signature of Validator.Validate to return a slice of ValError instead of a single error. This change comes with the expectation that all validators should return the full list of errors that a given validator detects. Error-free passes can return either nil or an empty []ValError slice. This adjustment starts with a basic "just supply the message", but ValError comes with extra fields that we can iterate upon to populate in the future. Having the full list of errors for a given instance makes this package far more useful, at the expense of forcing implementers to understand a few extra things. Meh. Worth it. closes #15
1 parent b6ed67c commit 00b42a8

11 files changed

+196
-113
lines changed

keywords.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,28 @@ func newTipe() Validator {
5858
}
5959

6060
// Validate checks to see if input data satisfies the type constraint
61-
func (t tipe) Validate(data interface{}) error {
61+
func (t tipe) Validate(data interface{}) (errs []ValError) {
6262
jt := DataType(data)
6363
for _, typestr := range t.vals {
6464
if jt == typestr || jt == "integer" && typestr == "number" {
6565
return nil
6666
}
6767
}
6868
if len(t.vals) == 1 {
69-
return fmt.Errorf(`expected "%v" to be of type %s`, data, t.vals[0])
69+
errs = append(errs, ValError{
70+
Message: fmt.Sprintf(`expected "%v" to be of type %s`, data, t.vals[0]),
71+
})
72+
return
7073
}
7174

7275
str := ""
7376
for _, ts := range t.vals {
7477
str += ts + ","
7578
}
76-
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
79+
errs = append(errs, ValError{
80+
Message: fmt.Sprintf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1]),
81+
})
82+
return
7783
}
7884

7985
// JSONProp implements JSON property name indexing for tipe
@@ -138,13 +144,15 @@ func (e enum) String() string {
138144
}
139145

140146
// Validate implements the Validator interface for enum
141-
func (e enum) Validate(data interface{}) error {
147+
func (e enum) Validate(data interface{}) []ValError {
142148
for _, v := range e {
143149
if err := v.Validate(data); err == nil {
144150
return nil
145151
}
146152
}
147-
return fmt.Errorf("expected %s to be one of %s", data)
153+
return []ValError{
154+
{Message: fmt.Sprintf("expected %s to be one of %s", data)},
155+
}
148156
}
149157

150158
// JSONProp implements JSON property name indexing for enum
@@ -178,14 +186,18 @@ func newKonst() Validator {
178186
}
179187

180188
// Validate implements the validate interface for konst
181-
func (c konst) Validate(data interface{}) error {
189+
func (c konst) Validate(data interface{}) []ValError {
182190
var con interface{}
183191
if err := json.Unmarshal(c, &con); err != nil {
184-
return err
192+
return []ValError{
193+
{Message: err.Error()},
194+
}
185195
}
186196

187197
if !reflect.DeepEqual(con, data) {
188-
return fmt.Errorf(`%s must equal %s`, string(c), data)
198+
return []ValError{
199+
{Message: fmt.Sprintf(`%s must equal %s`, string(c), data)},
200+
}
189201
}
190202
return nil
191203
}

keywords_arrays.go

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ func newItems() Validator {
2727
}
2828

2929
// Validate implements the Validator interface for items
30-
func (it items) Validate(data interface{}) error {
30+
func (it items) Validate(data interface{}) []ValError {
3131
if arr, ok := data.([]interface{}); ok {
3232
if it.single {
33-
for i, elem := range arr {
34-
if err := it.Schemas[0].Validate(elem); err != nil {
35-
return fmt.Errorf("element %d %s", i, err.Error())
33+
for _, elem := range arr {
34+
if ves := it.Schemas[0].Validate(elem); len(ves) > 0 {
35+
return ves
3636
}
3737
}
3838
} else {
3939
for i, vs := range it.Schemas {
4040
if i < len(arr) {
41-
if err := vs.Validate(arr[i]); err != nil {
42-
return fmt.Errorf("element %d %s", i, err.Error())
41+
if ves := vs.Validate(arr[i]); len(ves) > 0 {
42+
return ves
4343
}
4444
}
4545
}
@@ -108,20 +108,20 @@ func newAdditionalItems() Validator {
108108
}
109109

110110
// Validate implements the Validator interface for additionalItems
111-
func (a *additionalItems) Validate(data interface{}) error {
111+
func (a *additionalItems) Validate(data interface{}) (errs []ValError) {
112112
if a.startIndex >= 0 {
113113
if arr, ok := data.([]interface{}); ok {
114114
for i, elem := range arr {
115115
if i < a.startIndex {
116116
continue
117117
}
118-
if err := a.Schema.Validate(elem); err != nil {
119-
return fmt.Errorf("element %d: %s", i, err.Error())
118+
if ves := a.Schema.Validate(elem); len(ves) > 0 {
119+
errs = append(errs, ves...)
120120
}
121121
}
122122
}
123123
}
124-
return nil
124+
return
125125
}
126126

127127
// JSONProp implements JSON property name indexing for additionalItems
@@ -158,10 +158,12 @@ func newMaxItems() Validator {
158158
}
159159

160160
// Validate implements the Validator interface for maxItems
161-
func (m maxItems) Validate(data interface{}) error {
161+
func (m maxItems) Validate(data interface{}) []ValError {
162162
if arr, ok := data.([]interface{}); ok {
163163
if len(arr) > int(m) {
164-
return fmt.Errorf("%d array items exceeds %d max", len(arr), m)
164+
return []ValError{
165+
{Message: fmt.Sprintf("%d array items exceeds %d max", len(arr), m)},
166+
}
165167
}
166168
}
167169
return nil
@@ -177,10 +179,12 @@ func newMinItems() Validator {
177179
}
178180

179181
// Validate implements the Validator interface for minItems
180-
func (m minItems) Validate(data interface{}) error {
182+
func (m minItems) Validate(data interface{}) []ValError {
181183
if arr, ok := data.([]interface{}); ok {
182184
if len(arr) < int(m) {
183-
return fmt.Errorf("%d array items below %d minimum", len(arr), m)
185+
return []ValError{
186+
{Message: fmt.Sprintf("%d array items below %d minimum", len(arr), m)},
187+
}
184188
}
185189
}
186190
return nil
@@ -197,13 +201,15 @@ func newUniqueItems() Validator {
197201
}
198202

199203
// Validate implements the Validator interface for uniqueItems
200-
func (u *uniqueItems) Validate(data interface{}) error {
204+
func (u *uniqueItems) Validate(data interface{}) []ValError {
201205
if arr, ok := data.([]interface{}); ok {
202206
found := []interface{}{}
203207
for _, elem := range arr {
204208
for _, f := range found {
205209
if reflect.DeepEqual(f, elem) {
206-
return fmt.Errorf("arry must be unique: %v", arr)
210+
return []ValError{
211+
{Message: fmt.Sprintf("arry must be unique: %v", arr)},
212+
}
207213
}
208214
}
209215
found = append(found, elem)
@@ -221,15 +227,17 @@ func newContains() Validator {
221227
}
222228

223229
// Validate implements the Validator interface for contains
224-
func (c *contains) Validate(data interface{}) error {
230+
func (c *contains) Validate(data interface{}) []ValError {
225231
v := Schema(*c)
226232
if arr, ok := data.([]interface{}); ok {
227233
for _, elem := range arr {
228234
if err := v.Validate(elem); err == nil {
229235
return nil
230236
}
231237
}
232-
return fmt.Errorf("expected %v to contain at least one of: %s", data, c)
238+
return []ValError{
239+
{Message: fmt.Sprintf("expected %v to contain at least one of: %s", data, c)},
240+
}
233241
}
234242
return nil
235243
}

keywords_booleans.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ func newAllOf() Validator {
1515
}
1616

1717
// Validate implements the validator interface for allOf
18-
func (a allOf) Validate(data interface{}) error {
19-
for i, sch := range a {
20-
if err := sch.Validate(data); err != nil {
21-
return fmt.Errorf("allOf element %d error: %s", i, err.Error())
18+
func (a allOf) Validate(data interface{}) (errs []ValError) {
19+
for _, sch := range a {
20+
if ves := sch.Validate(data); len(ves) > 0 {
21+
errs = append(errs, ves...)
2222
}
2323
}
24-
return nil
24+
return
2525
}
2626

2727
// JSONProp implements JSON property name indexing for allOf
@@ -55,13 +55,15 @@ func newAnyOf() Validator {
5555
}
5656

5757
// Validate implements the validator interface for anyOf
58-
func (a anyOf) Validate(data interface{}) error {
58+
func (a anyOf) Validate(data interface{}) []ValError {
5959
for _, sch := range a {
6060
if err := sch.Validate(data); err == nil {
6161
return nil
6262
}
6363
}
64-
return fmt.Errorf("value did not match any specified anyOf schemas: %v", data)
64+
return []ValError{
65+
{Message: fmt.Sprintf("value did not match any specified anyOf schemas: %v", data)},
66+
}
6567
}
6668

6769
// JSONProp implements JSON property name indexing for anyOf
@@ -94,18 +96,22 @@ func newOneOf() Validator {
9496
}
9597

9698
// Validate implements the validator interface for oneOf
97-
func (o oneOf) Validate(data interface{}) error {
99+
func (o oneOf) Validate(data interface{}) []ValError {
98100
matched := false
99101
for _, sch := range o {
100102
if err := sch.Validate(data); err == nil {
101103
if matched {
102-
return fmt.Errorf("value matched more than one specified oneOf schemas")
104+
return []ValError{
105+
{Message: fmt.Sprintf("value matched more than one specified oneOf schemas")},
106+
}
103107
}
104108
matched = true
105109
}
106110
}
107111
if !matched {
108-
return fmt.Errorf("value did not match any of the specified oneOf schemas")
112+
return []ValError{
113+
{Message: fmt.Sprintf("value did not match any of the specified oneOf schemas")},
114+
}
109115
}
110116
return nil
111117
}
@@ -141,11 +147,13 @@ func newNot() Validator {
141147
}
142148

143149
// Validate implements the validator interface for not
144-
func (n *not) Validate(data interface{}) error {
150+
func (n *not) Validate(data interface{}) []ValError {
145151
sch := Schema(*n)
146152
if sch.Validate(data) == nil {
147153
// TODO - make this error actually make sense
148-
return fmt.Errorf("not clause")
154+
return []ValError{
155+
{Message: fmt.Sprintf("not clause")},
156+
}
149157
}
150158
return nil
151159
}

keywords_conditionals.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func newIif() Validator {
1919
}
2020

2121
// Validate implements the Validator interface for iif
22-
func (i *iif) Validate(data interface{}) error {
22+
func (i *iif) Validate(data interface{}) []ValError {
2323
if err := i.Schema.Validate(data); err == nil {
2424
if i.then != nil {
2525
s := Schema(*i.then)
@@ -71,7 +71,7 @@ func newThen() Validator {
7171
}
7272

7373
// Validate implements the Validator interface for then
74-
func (t *then) Validate(data interface{}) error {
74+
func (t *then) Validate(data interface{}) []ValError {
7575
return nil
7676
}
7777

@@ -110,7 +110,7 @@ func newEls() Validator {
110110
}
111111

112112
// Validate implements the Validator interface for els
113-
func (e *els) Validate(data interface{}) error {
113+
func (e *els) Validate(data interface{}) []ValError {
114114
return nil
115115
}
116116

keywords_numeric.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ func newMultipleOf() Validator {
1414
}
1515

1616
// Validate implements the Validator interface for multipleOf
17-
func (m multipleOf) Validate(data interface{}) error {
17+
func (m multipleOf) Validate(data interface{}) []ValError {
1818
if num, ok := data.(float64); ok {
1919
div := num / float64(m)
2020
if float64(int(div)) != div {
21-
return fmt.Errorf("%f must be a multiple of %f", num, m)
21+
return []ValError{
22+
{Message: fmt.Sprintf("%f must be a multiple of %f", num, m)},
23+
}
2224
}
2325
}
2426
return nil
@@ -34,10 +36,12 @@ func newMaximum() Validator {
3436
}
3537

3638
// Validate implements the Validator interface for maximum
37-
func (m maximum) Validate(data interface{}) error {
39+
func (m maximum) Validate(data interface{}) []ValError {
3840
if num, ok := data.(float64); ok {
3941
if num > float64(m) {
40-
return fmt.Errorf("%f must be less than or equal to %f", num, m)
42+
return []ValError{
43+
{Message: fmt.Sprintf("%f must be less than or equal to %f", num, m)},
44+
}
4145
}
4246
}
4347
return nil
@@ -53,10 +57,12 @@ func newExclusiveMaximum() Validator {
5357
}
5458

5559
// Validate implements the Validator interface for exclusiveMaximum
56-
func (m exclusiveMaximum) Validate(data interface{}) error {
60+
func (m exclusiveMaximum) Validate(data interface{}) []ValError {
5761
if num, ok := data.(float64); ok {
5862
if num >= float64(m) {
59-
return fmt.Errorf("%f must be less than %f", num, m)
63+
return []ValError{
64+
{Message: fmt.Sprintf("%f must be less than %f", num, m)},
65+
}
6066
}
6167
}
6268
return nil
@@ -71,10 +77,12 @@ func newMinimum() Validator {
7177
}
7278

7379
// Validate implements the Validator interface for minimum
74-
func (m minimum) Validate(data interface{}) error {
80+
func (m minimum) Validate(data interface{}) []ValError {
7581
if num, ok := data.(float64); ok {
7682
if num < float64(m) {
77-
return fmt.Errorf("%f must be greater than or equal to %f", num, m)
83+
return []ValError{
84+
{Message: fmt.Sprintf("%f must be greater than or equal to %f", num, m)},
85+
}
7886
}
7987
}
8088
return nil
@@ -89,10 +97,12 @@ func newExclusiveMinimum() Validator {
8997
}
9098

9199
// Validate implements the Validator interface for exclusiveMinimum
92-
func (m exclusiveMinimum) Validate(data interface{}) error {
100+
func (m exclusiveMinimum) Validate(data interface{}) []ValError {
93101
if num, ok := data.(float64); ok {
94102
if num <= float64(m) {
95-
return fmt.Errorf("%f must be greater than %f", num, m)
103+
return []ValError{
104+
{Message: fmt.Sprintf("%f must be greater than %f", num, m)},
105+
}
96106
}
97107
}
98108
return nil

0 commit comments

Comments
 (0)