Repository navigation
Expand file tree
/
Copy patherror.go
More file actions
240 lines (210 loc) · 6.35 KB
/
Copy patherror.go
File metadata and controls
240 lines (210 loc) · 6.35 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
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package validation
import (
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
)
type (
// Error interface represents an validation error
Error interface {
Error() string
Code() string
Message() string
SetMessage(string) Error
Params() map[string]interface{}
SetParams(map[string]interface{}) Error
}
// ErrorObject is the default validation error
// that implements the Error interface.
ErrorObject struct {
code string
message string
params map[string]interface{}
}
// Errors represents the validation errors that are indexed by struct field names, map or slice keys.
// values are Error or Errors (for map, slice and array error value is Errors).
Errors map[string]error
// InternalError represents an error that should NOT be treated as a validation error.
InternalError interface {
error
InternalError() error
}
internalError struct {
error
}
)
// NewInternalError wraps a given error into an InternalError.
func NewInternalError(err error) InternalError {
return internalError{error: err}
}
// InternalError returns the actual error that it wraps around.
func (e internalError) InternalError() error {
return e.error
}
// Unwrap returns the wrapped error, so that errors.Is and errors.As reach it.
func (e internalError) Unwrap() error {
return e.error
}
// SetCode set the error's translation code.
func (e ErrorObject) SetCode(code string) Error {
e.code = code
return e
}
// Code get the error's translation code.
func (e ErrorObject) Code() string {
return e.code
}
// SetParams set the error's params.
func (e ErrorObject) SetParams(params map[string]interface{}) Error {
e.params = params
return e
}
// AddParam add parameter to the error's parameters.
func (e ErrorObject) AddParam(name string, value interface{}) Error {
if e.params == nil {
e.params = make(map[string]interface{})
}
e.params[name] = value
return e
}
// Params returns the error's params.
func (e ErrorObject) Params() map[string]interface{} {
return e.params
}
// SetMessage set the error's message.
func (e ErrorObject) SetMessage(message string) Error {
e.message = message
return e
}
// Message return the error's message.
func (e ErrorObject) Message() string {
return e.message
}
// Is reports whether target is a validation Error with the same non-empty code. It makes
// errors.Is(err, ErrRequired) and the like work: an ErrorObject holds a params map, so it
// cannot be compared with ==, and a rule with a custom message (Required.Error("...")) keeps
// the code of the error it was built from. Params are not compared.
//
// An error with an empty code never matches, not even itself, so give the sentinel errors
// of your own rules a code: NewError("my_code", "...").
func (e ErrorObject) Is(target error) bool {
if e.code == "" {
return false
}
if p, ok := target.(*ErrorObject); ok && p == nil {
// Code() on a nil *ErrorObject would panic.
return false
}
t, ok := target.(Error)
return ok && e.code == t.Code()
}
// Error returns the error string of Errors.
func (es Errors) Error() string {
if len(es) == 0 {
return ""
}
keys := es.sortedKeys()
var s strings.Builder
for _, key := range keys {
if es[key] == nil {
continue
}
// Decide the separator by what was written, not by the key index: skipped nil entries
// must not leave a leading "; ".
if s.Len() > 0 {
s.WriteString("; ")
}
if errs, ok := es[key].(Errors); ok {
_, _ = fmt.Fprintf(&s, "%v: (%v)", key, errs)
} else {
_, _ = fmt.Fprintf(&s, "%v: %v", key, es[key].Error())
}
}
if s.Len() == 0 {
// Only nil entries: report the same as an empty Errors.
return ""
}
s.WriteString(".")
return s.String()
}
// Is implements the interface relied upon by the standard library's errors.Is.
// It reports whether any entry of es matches target according to errors.Is,
// including entries of the Errors nested for maps, slices and nested structs.
// What an entry matches is up to the entry: an error returned by a By rule
// matches its sentinel, and an ErrorObject matches a validation Error with the
// same code (see ErrorObject.Is), so errors.Is(err, ErrRequired) reports whether
// any field failed Required.
//
// Nil field errors are skipped. An Errors is not comparable, so errors.Is(es, es) is false.
// Errors that contain themselves are not supported, as in Error().
func (es Errors) Is(target error) bool {
if target == nil {
return false
}
// A plain range: the result does not depend on the order, and sorting the keys
// costs an allocation per call.
for _, err := range es {
if err != nil && errors.Is(err, target) {
return true
}
}
return false
}
// Errors deliberately has no As method (and no Unwrap() []error). Code that tells
// a single-value error from a field map relies on errors.As(err, &validation.Error)
// reporting false for an Errors; an As that looks inside the map would make it
// return the first field's error and lose the field names.
// sortedKeys returns the keys of es sorted in increasing order. It gives Error a
// deterministic iteration order over the fields.
func (es Errors) sortedKeys() []string {
keys := make([]string, 0, len(es))
for key := range es {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// MarshalJSON converts the Errors into a valid JSON.
func (es Errors) MarshalJSON() ([]byte, error) {
errs := map[string]interface{}{}
for key, err := range es {
if ms, ok := err.(json.Marshaler); ok {
errs[key] = ms
} else {
errs[key] = err.Error()
}
}
return json.Marshal(errs)
}
// Filter removes all nils from Errors and returns back the updated Errors as an error.
// If the length of Errors becomes 0, it will return nil.
func (es Errors) Filter() error {
for key, value := range es {
if value == nil {
delete(es, key)
}
}
if len(es) == 0 {
return nil
}
return es
}
// NewError create new validation error.
func NewError(code, message string) Error {
return ErrorObject{
code: code,
message: message,
}
}
// Assert that our ErrorObject implements the Error interface and the hook used by errors.Is.
var (
_ Error = ErrorObject{}
_ interface{ Is(error) bool } = ErrorObject{}
)
// Assert that Errors implements the hook interface used by errors.Is.
var _ interface{ Is(error) bool } = Errors{}