-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
kind.go
466 lines (426 loc) · 13 KB
/
kind.go
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
package schema
import (
"encoding/json"
"fmt"
"regexp"
"time"
"unicode/utf8"
)
// Kind represents the basic type of a field in an object.
// Each kind defines the following encodings:
// Go Encoding: the golang type which should be accepted by listeners and
// generated by decoders when providing entity updates.
// JSON Encoding: the JSON encoding which should be used when encoding the field to JSON.
// When there is some non-determinism in an encoding, kinds should specify what
// values they accept and also what is the canonical, deterministic encoding which
// should be preferably emitted by serializers.
type Kind int
const (
// InvalidKind indicates that an invalid type.
InvalidKind Kind = iota
// StringKind is a string type.
// Go Encoding: UTF-8 string with no null characters.
// JSON Encoding: string
StringKind
// BytesKind is a bytes type.
// Go Encoding: []byte
// JSON Encoding: base64 encoded string, canonical values should be encoded with standard encoding and padding.
// Either standard or URL encoding with or without padding should be accepted.
BytesKind
// Int8Kind represents an 8-bit signed integer.
// Go Encoding: int8
// JSON Encoding: number
Int8Kind
// Uint8Kind represents an 8-bit unsigned integer.
// Go Encoding: uint8
// JSON Encoding: number
Uint8Kind
// Int16Kind represents a 16-bit signed integer.
// Go Encoding: int16
// JSON Encoding: number
Int16Kind
// Uint16Kind represents a 16-bit unsigned integer.
// Go Encoding: uint16
// JSON Encoding: number
Uint16Kind
// Int32Kind represents a 32-bit signed integer.
// Go Encoding: int32
// JSON Encoding: number
Int32Kind
// Uint32Kind represents a 32-bit unsigned integer.
// Go Encoding: uint32
// JSON Encoding: number
Uint32Kind
// Int64Kind represents a 64-bit signed integer.
// Go Encoding: int64
// JSON Encoding: base10 integer string which matches the IntegerFormat regex
// The canonical encoding should include no leading zeros.
Int64Kind
// Uint64Kind represents a 64-bit unsigned integer.
// Go Encoding: uint64
// JSON Encoding: base10 integer string which matches the IntegerFormat regex
// Canonically encoded values should include no leading zeros.
Uint64Kind
// IntegerKind represents an arbitrary precision integer number.
// Support for expressing the maximum bit precision of values will be added in the future.
// Go Encoding: string which matches the IntegerFormat regex (unstable, subject to change).
// JSON Encoding: base10 integer string
// Canonically encoded values should include no leading zeros.
// Equality comparison with integers should be done using numerical equality rather
// than string equality.
IntegerKind
// DecimalKind represents an arbitrary precision decimal or integer number.
// Support for optionally limiting the precision may be added in the future.
// Go Encoding: string which matches the DecimalFormat regex
// JSON Encoding: base10 decimal string
// Canonically encoded values should include no leading zeros or trailing zeros,
// and exponential notation with a lowercase 'e' should be used for any numbers
// with an absolute value less than or equal to 1e-6 or greater than or equal to 1e6.
// Equality comparison with decimals should be done using numerical equality rather
// than string equality.
DecimalKind
// BoolKind represents a boolean true or false value.
// Go Encoding: bool
// JSON Encoding: boolean
BoolKind
// TimeKind represents a nanosecond precision UNIX time value (with zero representing January 1, 1970 UTC).
// Its valid range is +/- 2^63 (the range of a 64-bit signed integer).
// Go Encoding: time.Time
// JSON Encoding: Any value IS0 8601 time stamp should be accepted.
// Canonical values should be encoded with UTC time zone Z, nanoseconds should
// be encoded with no trailing zeros, and T time values should always be present
// even at 00:00:00.
TimeKind
// DurationKind represents the elapsed time between two nanosecond precision time values.
// Its valid range is +/- 2^63 (the range of a 64-bit signed integer).
// Go Encoding: time.Duration
// JSON Encoding: the number of seconds as a decimal string with no trailing zeros followed by
// a lowercase 's' character to represent seconds.
DurationKind
// Float32Kind represents an IEEE-754 32-bit floating point number.
// Go Encoding: float32
// JSON Encoding: number
Float32Kind
// Float64Kind represents an IEEE-754 64-bit floating point number.
// Go Encoding: float64
// JSON Encoding: number
Float64Kind
// AddressKind represents an account address which is represented by a variable length array of bytes.
// Addresses usually have a human-readable rendering, such as bech32, and tooling should provide
// a way for apps to define a string encoder for friendly user-facing display.
// Go Encoding: []byte
// JSON Encoding: addresses should be encoded as strings using the human-readable address renderer
// provided to the JSON encoder.
AddressKind
// EnumKind represents a value of an enum type.
// Fields of this type are expected to set the EnumType field in the field definition to the enum
// definition.
// Go Encoding: string
// JSON Encoding: string
EnumKind
// JSONKind represents arbitrary JSON data.
// Go Encoding: json.RawMessage
// JSON Encoding: any valid JSON value
JSONKind
)
// MAX_VALID_KIND is the maximum valid kind value.
const MAX_VALID_KIND = JSONKind
const (
// IntegerFormat is a regex that describes the format integer number strings must match. It specifies
// that integers may have at most 100 digits.
IntegerFormat = `^-?[0-9]{1,100}$`
// DecimalFormat is a regex that describes the format decimal number strings must match. It specifies
// that decimals may have at most 50 digits before and after the decimal point and may have an optional
// exponent of up to 2 digits. These restrictions ensure that the decimal can be accurately represented
// by a wide variety of implementations.
DecimalFormat = `^-?[0-9]{1,50}(\.[0-9]{1,50})?([eE][-+]?[0-9]{1,2})?$`
)
// Validate returns an errContains if the kind is invalid.
func (t Kind) Validate() error {
if t <= InvalidKind {
return fmt.Errorf("unknown type: %d", t)
}
if t > JSONKind {
return fmt.Errorf("invalid type: %d", t)
}
return nil
}
// String returns a string representation of the kind.
func (t Kind) String() string {
switch t {
case StringKind:
return "string"
case BytesKind:
return "bytes"
case Int8Kind:
return "int8"
case Uint8Kind:
return "uint8"
case Int16Kind:
return "int16"
case Uint16Kind:
return "uint16"
case Int32Kind:
return "int32"
case Uint32Kind:
return "uint32"
case Int64Kind:
return "int64"
case Uint64Kind:
return "uint64"
case DecimalKind:
return "decimal"
case IntegerKind:
return "integer"
case BoolKind:
return "bool"
case TimeKind:
return "time"
case DurationKind:
return "duration"
case Float32Kind:
return "float32"
case Float64Kind:
return "float64"
case AddressKind:
return "address"
case EnumKind:
return "enum"
case JSONKind:
return "json"
default:
return fmt.Sprintf("invalid(%d)", t)
}
}
// ValidateValueType returns an errContains if the value does not conform to the expected go type.
// Some fields may accept nil values, however, this method does not have any notion of
// nullability. This method only validates that the go type of the value is correct for the kind
// and does not validate string or json formats. Kind.ValidateValue does a more thorough validation
// of number and json string formatting.
func (t Kind) ValidateValueType(value interface{}) error {
switch t {
case StringKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case BytesKind:
_, ok := value.([]byte)
if !ok {
return fmt.Errorf("expected []byte, got %T", value)
}
case Int8Kind:
_, ok := value.(int8)
if !ok {
return fmt.Errorf("expected int8, got %T", value)
}
case Uint8Kind:
_, ok := value.(uint8)
if !ok {
return fmt.Errorf("expected uint8, got %T", value)
}
case Int16Kind:
_, ok := value.(int16)
if !ok {
return fmt.Errorf("expected int16, got %T", value)
}
case Uint16Kind:
_, ok := value.(uint16)
if !ok {
return fmt.Errorf("expected uint16, got %T", value)
}
case Int32Kind:
_, ok := value.(int32)
if !ok {
return fmt.Errorf("expected int32, got %T", value)
}
case Uint32Kind:
_, ok := value.(uint32)
if !ok {
return fmt.Errorf("expected uint32, got %T", value)
}
case Int64Kind:
_, ok := value.(int64)
if !ok {
return fmt.Errorf("expected int64, got %T", value)
}
case Uint64Kind:
_, ok := value.(uint64)
if !ok {
return fmt.Errorf("expected uint64, got %T", value)
}
case IntegerKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case DecimalKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case BoolKind:
_, ok := value.(bool)
if !ok {
return fmt.Errorf("expected bool, got %T", value)
}
case TimeKind:
_, ok := value.(time.Time)
if !ok {
return fmt.Errorf("expected time.Time, got %T", value)
}
case DurationKind:
_, ok := value.(time.Duration)
if !ok {
return fmt.Errorf("expected time.Duration, got %T", value)
}
case Float32Kind:
_, ok := value.(float32)
if !ok {
return fmt.Errorf("expected float32, got %T", value)
}
case Float64Kind:
_, ok := value.(float64)
if !ok {
return fmt.Errorf("expected float64, got %T", value)
}
case AddressKind:
_, ok := value.([]byte)
if !ok {
return fmt.Errorf("expected []byte, got %T", value)
}
case EnumKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case JSONKind:
_, ok := value.(json.RawMessage)
if !ok {
return fmt.Errorf("expected json.RawMessage, got %T", value)
}
default:
return fmt.Errorf("invalid type: %d", t)
}
return nil
}
// ValidateValue returns an errContains if the value does not conform to the expected go type and format.
// It is more thorough, but slower, than Kind.ValidateValueType and validates that Integer, Decimal and JSON
// values are formatted correctly. It cannot validate enum values because Kind's do not have enum schemas.
func (t Kind) ValidateValue(value interface{}) error {
err := t.ValidateValueType(value)
if err != nil {
return err
}
switch t {
case StringKind:
str := value.(string)
if !utf8.ValidString(str) {
return fmt.Errorf("expected valid utf-8 string, got %s", value)
}
// check for null characters
for _, r := range str {
if r == 0 {
return fmt.Errorf("expected string without null characters, got %s", value)
}
}
case IntegerKind:
if !integerRegex.Match([]byte(value.(string))) {
return fmt.Errorf("expected base10 integer, got %s", value)
}
case DecimalKind:
if !decimalRegex.Match([]byte(value.(string))) {
return fmt.Errorf("expected decimal number, got %s", value)
}
case JSONKind:
if !json.Valid(value.(json.RawMessage)) {
return fmt.Errorf("expected valid JSON, got %s", value)
}
default:
return nil
}
return nil
}
// ValidKeyKind returns true if the kind is a valid key kind.
// All kinds except Float32Kind, Float64Kind, and JSONKind are valid key kinds
// because they do not define a strict form of equality.
func (t Kind) ValidKeyKind() bool {
switch t {
case Float32Kind, Float64Kind, JSONKind:
return false
default:
return true
}
}
var (
integerRegex = regexp.MustCompile(IntegerFormat)
decimalRegex = regexp.MustCompile(DecimalFormat)
)
// KindForGoValue finds the simplest kind that can represent the given go value. It will not, however,
// return kinds such as IntegerKind, DecimalKind, AddressKind, or EnumKind which all can be
// represented as strings.
func KindForGoValue(value interface{}) Kind {
switch value.(type) {
case string:
return StringKind
case []byte:
return BytesKind
case int8:
return Int8Kind
case uint8:
return Uint8Kind
case int16:
return Int16Kind
case uint16:
return Uint16Kind
case int32:
return Int32Kind
case uint32:
return Uint32Kind
case int64:
return Int64Kind
case uint64:
return Uint64Kind
case float32:
return Float32Kind
case float64:
return Float64Kind
case bool:
return BoolKind
case time.Time:
return TimeKind
case time.Duration:
return DurationKind
case json.RawMessage:
return JSONKind
default:
return InvalidKind
}
}
// MarshalJSON marshals the kind to a JSON string and returns an error if the kind is invalid.
func (t Kind) MarshalJSON() ([]byte, error) {
if err := t.Validate(); err != nil {
return nil, err
}
return json.Marshal(t.String())
}
// UnmarshalJSON unmarshals the kind from a JSON string and returns an error if the kind is invalid.
func (t *Kind) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
k, ok := kindStrings[s]
if !ok {
return fmt.Errorf("invalid kind: %s", s)
}
*t = k
return nil
}
var kindStrings = map[string]Kind{}
func init() {
for i := InvalidKind + 1; i <= MAX_VALID_KIND; i++ {
kindStrings[i.String()] = i
}
}