Repository navigation
Expand file tree
/
Copy patherrors.go
More file actions
361 lines (332 loc) · 12.5 KB
/
Copy patherrors.go
File metadata and controls
361 lines (332 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package validator
import (
"errors"
"fmt"
"reflect"
"slices"
"strings"
)
// Configuration errors. New wraps them with the offending option, signature
// or rule named, so match them with errors.Is.
var (
// ErrInvalidOption reports a nil Option or an invalid option argument: an
// empty tag name, a nil function or map, a nil rule or filter, or a
// non-positive WithParallel threshold.
ErrInvalidOption = errors.New("validator: invalid option")
// ErrInvalidSignature reports a rule or filter signature that is empty,
// reserved ("dive") or not made of letters, digits and underscores
// starting with a non-digit.
ErrInvalidSignature = errors.New("validator: invalid signature")
// ErrDuplicateRule reports a Rule or FallibleRule signature registered
// twice, including a custom rule that shadows a built-in one.
ErrDuplicateRule = errors.New("validator: duplicate rule")
// ErrDuplicateFilter reports a Filter signature registered twice.
ErrDuplicateFilter = errors.New("validator: duplicate filter")
)
// Input, rule and runtime errors returned by the constructors, Validate,
// ValidateAs and Valid. They are wrapped, so match them with errors.Is.
var (
// ErrNilContext reports a nil context passed to Validate, ValidateAs or
// Valid. The call does not count as a validation run.
ErrNilContext = errors.New("validator: nil context")
// ErrInvalidInput reports input a constructor cannot use: a non-struct
// value for Struct or Valid, or JSON that is malformed, has trailing data
// or is not an object.
ErrInvalidInput = errors.New("validator: invalid input")
// ErrInvalidRules reports a rule expression that does not parse, names an
// unknown rule, or has bad static arguments. It is wrapped by RulesError,
// which names the field.
ErrInvalidRules = errors.New("validator: invalid rules")
// ErrRuleEvaluation reports a rule that could not complete: a FallibleRule
// returned an error or a rule panicked. It is wrapped by RuleError and is
// never recorded as a field failure.
ErrRuleEvaluation = errors.New("validator: rule evaluation failed")
// ErrRulePanic reports a panic recovered from a rule, a filter's caller or
// reflection over the input; RuleError wraps it together with
// ErrRuleEvaluation.
ErrRulePanic = errors.New("validator: rule panicked")
// ErrValidated reports a configuration change (AddRules, AddFilters,
// AddMessages and their Remove/Clear forms) attempted after Validate ran.
ErrValidated = errors.New("validator: validation already ran")
)
// Binding errors returned by Bind and ValidateAs.
var (
// ErrBindTarget reports a Bind or ValidateAs destination that is nil or
// whose type parameter is not a non-pointer struct.
ErrBindTarget = errors.New("validator: bind requires a non-nil pointer to a non-pointer struct")
// ErrBindConversion reports a value that cannot be converted to its
// destination field: integer overflow, an unparsable string, nil into a
// non-nilable kind, or an incompatible shape. BindError wraps it and
// names the field.
ErrBindConversion = errors.New("validator: bind conversion failed")
// ErrValidationFailed marks a ValidateAs error caused by field failures,
// as opposed to a rule execution or binding error; the joined error also
// carries the *Errors collection for AsErrors.
ErrValidationFailed = errors.New("validator: validation failed")
)
var (
_ error = (*Errors)(nil)
_ error = (*RuleError)(nil)
_ error = (*RulesError)(nil)
_ error = (*BindError)(nil)
)
// Errors is the read-only collection of field failures produced by a
// Validation. Messages are resolved on every read from the Validator's
// messages, translations and attributes plus the Validation's AddMessages
// overrides, so the raw templates are kept and the same collection can be
// rendered under another configuration. It implements error: Validate and
// Err return it when fields failed, possibly joined with a *RuleError, and
// AsErrors recovers it from a wrapped error.
type Errors struct {
items []FieldError
bases []string
override map[string]string
v *Validator
}
// One returns the first recorded message, or "" when nothing failed. Use it
// when a single line, such as an HTTP 400 body, is enough.
func (e *Errors) One() string {
if len(e.items) > 0 {
return e.message(0)
}
return ""
}
// OneFor returns the first message recorded for field, or "" when the field
// passed. Dive elements are separate fields named "field[i]" or "field[key]".
func (e *Errors) OneFor(field string) string {
for i := range e.items {
if e.items[i].Field == field {
return e.message(i)
}
}
return ""
}
// Messages returns the message of each rule that failed on field, keyed by
// rule signature ("!" for a negation, "||" for an alternation with no passing
// branch, "" for a failed filter). When a rule failed more than once the first
// message is kept. The map is freshly built and safe to modify.
func (e *Errors) Messages(field string) map[string]string {
inner := make(map[string]string)
for i := range e.items {
if e.items[i].Field != field {
continue
}
if _, exists := inner[e.items[i].Rule]; !exists {
inner[e.items[i].Rule] = e.message(i)
}
}
return inner
}
// All returns every failure as field -> rule -> message, keeping the first
// message when a rule failed more than once on a field. The map is freshly
// built and safe to modify.
func (e *Errors) All() map[string]map[string]string { return e.build() }
// Items returns copies of the recorded failures in the order they were
// recorded (filter failures first, then fields by name, each container before
// its dive elements), with Message resolved and Params cloned. The slice is
// never nil.
func (e *Errors) Items() []FieldError {
out := make([]FieldError, len(e.items))
for i := range e.items {
it := e.items[i]
it.Message = e.message(i)
it.Params = slices.Clone(it.Params)
out[i] = it
}
return out
}
// Has reports whether any rule or filter failed on field.
func (e *Errors) Has(field string) bool {
for _, it := range e.items {
if it.Field == field {
return true
}
}
return false
}
// String returns the same summary as Error, for %s and %v formatting.
func (e *Errors) String() string { return e.Error() }
// Error joins the first message of each failed field as "field: message",
// sorted by field name and separated by "; ". It is "" when nothing failed,
// which only happens on an Errors obtained before Validate.
func (e *Errors) Error() string {
if len(e.items) == 0 {
return ""
}
first := make(map[string]string, len(e.items))
fields := make([]string, 0, len(e.items))
for i := range e.items {
f := e.items[i].Field
if _, ok := first[f]; !ok {
first[f] = e.message(i)
fields = append(fields, f)
}
}
slices.Sort(fields)
var b strings.Builder
for i, f := range fields {
if i > 0 {
b.WriteString("; ")
}
b.WriteString(f)
b.WriteString(": ")
b.WriteString(first[f])
}
return b.String()
}
// AsErrors extracts the *Errors collection from an error returned by
// Validate, Err or ValidateAs, looking through errors.Join and fmt.Errorf
// wrappers. ok is false for errors that carry no field failures: rule
// execution errors, binding errors, a nil context or cancellation.
func AsErrors(err error) (*Errors, bool) {
return errors.AsType[*Errors](err)
}
func (e *Errors) add(fe FieldError, base string) {
e.items = appendGrown(e.items, fe)
e.bases = appendGrown(e.bases, base)
}
func (e *Errors) message(i int) string {
if e.v == nil {
return e.items[i].Message
}
return e.v.resolveMessage(e.items[i], e.bases[i], e.override)
}
func (e *Errors) build() map[string]map[string]string {
m := make(map[string]map[string]string, len(e.items))
for i := range e.items {
inner, ok := m[e.items[i].Field]
if !ok {
inner = make(map[string]string)
m[e.items[i].Field] = inner
}
// Keep the first message per field and rule for stable output.
if _, exists := inner[e.items[i].Rule]; !exists {
inner[e.items[i].Rule] = e.message(i)
}
}
return m
}
// RuleError identifies a rule whose evaluation could not be completed: a
// FallibleRule returned a non-nil error, or a rule or the reflection reading
// the input panicked. It is an operational failure, not a verdict on the
// data, so it is never added to Errors: Validate and Valid return it (joined
// with any field failures), and ValidateAs returns it without
// ErrValidationFailed. The failing field's remaining rules are skipped;
// other fields are still evaluated.
//
// Unwrap yields ErrRuleEvaluation joined with Err, so errors.Is matches the
// sentinel as well as the cause (ErrRulePanic for a recovered panic, or the
// FallibleRule's own error).
type RuleError struct {
// Field is the validation name of the field being evaluated, or "" when
// the failure cannot be attributed to one field.
Field string
// Rule is the signature of the FallibleRule that returned the error; ""
// for a recovered panic.
Rule string
// Err is the cause: the FallibleRule's error, or ErrRulePanic wrapped with
// the panic value. It is nil only in a zero RuleError.
Err error
}
// Error renders the field, rule and cause, omitting the parts that are unset;
// a nil receiver or a nil Err reports rule evaluation failed.
func (e *RuleError) Error() string {
if e == nil {
return ErrRuleEvaluation.Error()
}
if e.Err == nil {
switch {
case e.Field != "" && e.Rule != "":
return fmt.Sprintf("validator: field %q rule %q: rule evaluation failed", e.Field, e.Rule)
case e.Field != "":
return fmt.Sprintf("validator: field %q: rule evaluation failed", e.Field)
default:
return ErrRuleEvaluation.Error()
}
}
switch {
case e.Field != "" && e.Rule != "":
return fmt.Sprintf(
"validator: field %q rule %q: %v",
e.Field,
e.Rule,
e.Err,
)
case e.Field != "":
return fmt.Sprintf("validator: field %q: %v", e.Field, e.Err)
default:
return "validator: " + e.Err.Error()
}
}
// Unwrap returns ErrRuleEvaluation joined with Err, or ErrRuleEvaluation alone
// when Err is nil, so errors.Is sees both.
func (e *RuleError) Unwrap() error {
if e == nil || e.Err == nil {
return ErrRuleEvaluation
}
return errors.Join(ErrRuleEvaluation, e.Err)
}
// RulesError identifies a field whose rule expression is invalid: a parse
// error, an unknown rule, bad static arguments (a regex that does not compile,
// a missing sibling name, an unparsable file size), or "sometimes" placed
// under ||, ! or dive. The constructors, Check and CheckType return one
// RulesError per bad field, joined with errors.Join; nothing is evaluated.
//
// Unwrap yields ErrInvalidRules joined with Err, so errors.Is matches both.
type RulesError struct {
// Field is the validation name whose expression failed to compile.
Field string
// Err is the cause, typically a parse error carrying the position.
Err error
}
// Error renders the field and cause; a nil receiver or a nil Err reports
// invalid rules.
func (e *RulesError) Error() string {
switch {
case e == nil:
return ErrInvalidRules.Error()
case e.Err == nil && e.Field == "":
return ErrInvalidRules.Error()
case e.Err == nil:
return fmt.Sprintf("validator: field %q: invalid rules", e.Field)
case e.Field == "":
return "validator: invalid rules: " + e.Err.Error()
default:
return fmt.Sprintf("validator: field %q: %v", e.Field, e.Err)
}
}
// Unwrap returns ErrInvalidRules joined with Err, or ErrInvalidRules alone
// when Err is nil, so errors.Is sees both.
func (e *RulesError) Unwrap() error {
if e == nil || e.Err == nil {
return ErrInvalidRules
}
return errors.Join(ErrInvalidRules, e.Err)
}
// BindError identifies the first field that Bind or ValidateAs could not
// convert into the destination struct. Binding is atomic, so the destination
// is unchanged when this error is returned. Unwrap yields Err.
type BindError struct {
// Field is the validation name of the destination field.
Field string
// Target is the Go type the value should have been converted to.
Target reflect.Type
// Value is the input value that failed to convert.
Value any
// Err is the cause, ErrBindConversion.
Err error
}
// Error renders the field, target type and cause.
func (e *BindError) Error() string {
return "validator: bind field " + e.Field + " to " + e.Target.String() + ": " + e.Err.Error()
}
// Unwrap returns the cause so errors.Is(err, ErrBindConversion) holds.
func (e *BindError) Unwrap() error { return e.Err }
// appendGrown appends with an initial capacity of 4, so the first failures of
// a field share one allocation instead of growing 1, 2, 4.
func appendGrown[T any](dst []T, v T) []T {
if cap(dst) == 0 {
dst = make([]T, 0, 4)
}
return append(dst, v)
}