-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtype.go
More file actions
233 lines (216 loc) · 8.38 KB
/
Copy pathtype.go
File metadata and controls
233 lines (216 loc) · 8.38 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
// 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"
"math"
"reflect"
)
// typeKind enumerates the categories asserted by the type rules.
type typeKind int
const (
typeString typeKind = iota
typeInteger
typeFloat
typeBool
typeArray
typeMap
)
var (
// ErrTypeString is the error returned when a value is not a string.
ErrTypeString = NewError("validation_type_string", "must be a string")
// ErrTypeInteger is the error returned when a value is not an integer.
ErrTypeInteger = NewError("validation_type_integer", "must be an integer")
// ErrTypeFloat is the error returned when a value is not a number.
ErrTypeFloat = NewError("validation_type_float", "must be a number")
// ErrTypeBool is the error returned when a value is not a boolean.
ErrTypeBool = NewError("validation_type_bool", "must be a boolean")
// ErrTypeArray is the error returned when a value is not an array.
ErrTypeArray = NewError("validation_type_array", "must be an array")
// ErrTypeMap is the error returned when a value is not an object. The JSON
// term is used in the message because these rules are meant for JSON decoded
// data, where a Go map is the object.
ErrTypeMap = NewError("validation_type_map", "must be an object")
)
// IsString, IsInteger, IsFloat, and IsBoolean assert the underlying type of a
// value. They are intended for values whose static type is dynamic (any) — for
// example data decoded from JSON into an interface or a map[string]any — where
// the Go compiler can no longer guarantee the type. When the static type is
// already concrete there is nothing for these rules to check.
//
// Unlike the value-oriented rules (Length, Match, Min, ...) which dereference
// to an empty value and treat it as valid, the type rules only skip a nil
// pointer/interface. A present zero value such as 0, "", or false carries a
// type, so the rule still asserts it: IsString rejects a present 0, while
// IsInteger accepts a present 0. Use Required to additionally demand presence.
//
// Numbers are matched by value, not by Go kind, so they behave the same whether
// JSON was decoded with the default float64 numbers or with json.Decoder's
// UseNumber:
//
// - IsFloat accepts any numeric value (integers included — every integer is a
// valid number).
// - IsInteger accepts any integer-valued number, including a whole-valued
// float such as 5.0 (but not 5.5). This is required because the default
// json.Unmarshal turns every number into a float64, so 5 and 5.0 are
// indistinguishable; IsInteger asserts the value, not how it was spelled.
//
// A json.Number is classified by its textual content for IsInteger/IsFloat. It
// can only originate from an unquoted JSON number token, never from a quoted
// string, so it is never a string (nor a boolean): IsString and IsBoolean
// always reject a json.Number.
//
// IsArray and IsMap assert the two structural JSON types. A JSON array decodes
// to a slice (the default []any) and a JSON object decodes to a map (the
// default map[string]any), so those are what these rules accept. IsArray
// deliberately does NOT accept a []byte: the library treats a byte slice as
// string content everywhere else (see IsString), so a []byte is a string here,
// not an array. Like the other type rules they only skip a true nil, which for
// these includes a nil slice/map, a present but empty []any{} or
// map[string]any{} still carries its type and so is accepted.
//
// These rules now live in the is package too, where they read more naturally at
// the call site: is.String instead of validation.IsString. The is package has
// its own full copy of this logic, the package level rules below are kept so we
// dont break anyone but theyre deprecated, prefer the is package versions.
// IsFloat is the odd one out and stays un-deprecated for now because the is
// package already has an is.Float that checks string contents, so theres no
// clean name for the type rule over there yet.
var (
// Deprecated: Use is.String instead.
IsString = TypeRule{kind: typeString, err: ErrTypeString}
// Deprecated: Use is.Integer instead.
IsInteger = TypeRule{kind: typeInteger, err: ErrTypeInteger}
IsFloat = TypeRule{kind: typeFloat, err: ErrTypeFloat}
// Deprecated: Use is.Boolean instead.
IsBoolean = TypeRule{kind: typeBool, err: ErrTypeBool}
// Deprecated: Use is.Array instead.
IsArray = TypeRule{kind: typeArray, err: ErrTypeArray}
// Deprecated: Use is.Map instead.
IsMap = TypeRule{kind: typeMap, err: ErrTypeMap}
)
// TypeRule is a validation rule that asserts the underlying type of a value.
// Use the package-level IsString, IsInteger, IsFloat, and IsBoolean rules
// rather than constructing one directly.
type TypeRule struct {
kind typeKind
err Error
}
// Error sets the error message for the rule.
func (r TypeRule) Error(message string) TypeRule {
r.err = r.err.SetMessage(message)
return r
}
// ErrorObject sets the error struct for the rule.
func (r TypeRule) ErrorObject(err Error) TypeRule {
r.err = err
return r
}
// Validate checks that the value's underlying type matches the asserted type.
func (r TypeRule) Validate(value any) error {
value, isNil, err := Indirect(value)
if err != nil {
return err
}
// Only a nil pointer/interface is skipped; a present zero value still
// carries a type and is therefore asserted. See the IsString doc comment.
if isNil {
return nil
}
if r.matches(value) {
return nil
}
return r.err
}
// matches reports whether value satisfies the rule's asserted type.
func (r TypeRule) matches(value any) bool {
// json.Number's underlying kind is string, so it must be classified by its
// textual content before any kind-based check below would misread it.
if n, ok := value.(json.Number); ok {
switch r.kind {
case typeInteger:
return isWholeJSONNumber(n)
case typeFloat:
_, err := n.Float64()
return err == nil
default:
// A json.Number is provably not a string or boolean: it can only be
// produced from an unquoted JSON number token.
return false
}
}
switch r.kind {
case typeString:
// EnsureString also accepts a []byte, matching how StringRule treats
// string content throughout the library. A json.Number (kind string) is
// already handled above and never reaches here.
_, err := EnsureString(value)
return err == nil
case typeInteger:
return isIntegerValue(value)
case typeFloat:
return isNumericValue(value)
case typeBool:
return reflect.ValueOf(value).Kind() == reflect.Bool
case typeArray:
return isArrayValue(value)
case typeMap:
return reflect.ValueOf(value).Kind() == reflect.Map
}
return false
}
// isArrayValue reports whether value is a JSON style array: a slice or an array.
// A []byte is excluded on purpose. EnsureString treats a byte slice as string
// content, so IsString already claims it, and we dont want the same value to be
// both a string and an array. A byte array like [4]byte is not what EnsureString
// matches (it only matches the []byte slice type) so it stays an array here.
func isArrayValue(value any) bool {
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice:
return rv.Type() != bytesType
case reflect.Array:
return true
}
return false
}
// isIntegerValue reports whether value is an integer-valued number: any signed
// or unsigned integer type, or a float with no fractional part (so 5.0 counts,
// 5.5 does not).
func isIntegerValue(value any) bool {
if _, err := ToInt(value); err == nil {
return true
}
if _, err := ToUint(value); err == nil {
return true
}
if f, err := ToFloat(value); err == nil {
return !math.IsInf(f, 0) && f == math.Trunc(f)
}
return false
}
// isNumericValue reports whether value is any numeric type.
func isNumericValue(value any) bool {
if _, err := ToInt(value); err == nil {
return true
}
if _, err := ToUint(value); err == nil {
return true
}
_, err := ToFloat(value)
return err == nil
}
// isWholeJSONNumber reports whether a json.Number represents an integer value.
// A textual integer ("5") is accepted via Int64; a whole-valued decimal ("5.0")
// is accepted via Float64 so that it counts as an integer just like a
// float64-decoded 5.0 does.
func isWholeJSONNumber(n json.Number) bool {
if _, err := n.Int64(); err == nil {
return true
}
if f, err := n.Float64(); err == nil {
return !math.IsInf(f, 0) && f == math.Trunc(f)
}
return false
}