forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkind.go
578 lines (533 loc) · 20.2 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
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
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.
// - Key Binary Encoding: the encoding which should be used when encoding the field
// as a key in binary messages. Some encodings specify a terminal and non-terminal form
// depending on whether or not the field is the last field in the key.
// - Value Binary Encoding: the encoding which should be used when encoding the field
// as a value in binary messages.
//
// 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.
//
// Binary encodings were chosen based on what is likely to be the most convenient default binary encoding
// for state management implementations. This encoding allows for sorted keys whenever it is possible for a kind
// and is deterministic.
// Modules that use the specified encoding natively will have a trivial decoder implementation because the
// encoding is already in the correct format after any initial prefix bytes are stripped.
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
// Key Binary Encoding:
// non-terminal: UTF-8 string with no null characters suffixed with a null character
// terminal: UTF-8 string with no null characters
// Value Binary Encoding: the same value binary encoding as BytesKind.
StringKind
// BytesKind represents a byte array.
// 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.
// Key Binary Encoding:
// non-terminal: length prefixed bytes where the width of the length prefix is 1, 2, 3 or 4 bytes depending on
// the field's MaxLength (defaulting to 4 bytes).
// Length prefixes should be big-endian encoded.
// Values larger than 2^32 bytes are not supported (likely key-value stores impose a lower limit).
// terminal: raw bytes with no length prefix
// Value Binary Encoding: two 32-bit unsigned little-endian integers, the first one representing the offset of the
// value in the buffer and the second one representing the length of the value.
BytesKind
// Int8Kind represents an 8-bit signed integer.
// Go Encoding: int8
// JSON Encoding: number
// Key Binary Encoding: 1-byte two's complement encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 1-byte two's complement encoding.
Int8Kind
// Uint8Kind represents an 8-bit unsigned integer.
// Go Encoding: uint8
// JSON Encoding: number
// Key Binary Encoding: 1-byte unsigned encoding.
// Value Binary Encoding: 1-byte unsigned encoding.
Uint8Kind
// Int16Kind represents a 16-bit signed integer.
// Go Encoding: int16
// JSON Encoding: number
// Key Binary Encoding: 2-byte two's complement big-endian encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 2 byte two's complement little-endian encoding.
Int16Kind
// Uint16Kind represents a 16-bit unsigned integer.
// Go Encoding: uint16
// JSON Encoding: number
// Key Binary Encoding: 2-byte unsigned big-endian encoding.
// Value Binary Encoding: 2-byte unsigned little-endian encoding.
Uint16Kind
// Int32Kind represents a 32-bit signed integer.
// Go Encoding: int32
// JSON Encoding: number
// Key Binary Encoding: 4-byte two's complement big-endian encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 4-byte two's complement little-endian encoding.
Int32Kind
// Uint32Kind represents a 32-bit unsigned integer.
// Go Encoding: uint32
// JSON Encoding: number
// Key Binary Encoding: 4-byte unsigned big-endian encoding.
// Value Binary Encoding: 4-byte unsigned little-endian encoding.
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.
// Key Binary Encoding: 8-byte two's complement big-endian encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 8-byte two's complement little-endian encoding.
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.
// Key Binary Encoding: 8-byte unsigned big-endian encoding.
// Value Binary Encoding: 8-byte unsigned little-endian encoding.
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
// Key Binary Encoding: 1-byte encoding where 0 is false and 1 is true.
// Value Binary Encoding: 1-byte encoding where 0 is false and 1 is true.
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.
// Key Binary Encoding: 8-byte two's complement big-endian encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 8-byte two's complement little-endian encoding.
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.
// Key Binary Encoding: 8-byte two's complement big-endian encoding, with the first bit inverted for sorting.
// Value Binary Encoding: 8-byte two's complement little-endian encoding.
DurationKind
// Float32Kind represents an IEEE-754 32-bit floating point number.
// Go Encoding: float32
// JSON Encoding: number
// Key Binary Encoding: 4-byte IEEE-754 encoding.
// Value Binary Encoding: 4-byte IEEE-754 encoding.
Float32Kind
// Float64Kind represents an IEEE-754 64-bit floating point number.
// Go Encoding: float64
// JSON Encoding: number
// Key Binary Encoding: 8-byte IEEE-754 encoding.
// Value Binary Encoding: 8-byte IEEE-754 encoding.
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. Addresses have a maximum
// supported length of 63 bytes.
// Go Encoding: []byte
// JSON Encoding: addresses should be encoded as strings using the human-readable address renderer
// provided to the JSON encoder.
// Key Binary Encoding:
// non-terminal: bytes prefixed with 1-byte length prefix
// terminal: raw bytes with no length prefix
// Value Binary Encoding: bytes prefixed with 1-byte length prefix.
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
// Key Binary Encoding: the same binary encoding as the EnumType's numeric kind.
// Value Binary Encoding: the same binary encoding as the EnumType's numeric kind.
EnumKind
// JSONKind represents arbitrary JSON data.
// Go Encoding: json.RawMessage
// JSON Encoding: any valid JSON value
// Key Binary Encoding: string encoding
// Value Binary Encoding: string encoding
JSONKind
// UIntNKind represents a signed integer type with a width in bits specified by the Size field in the
// field definition.
// Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added.
// N must be a multiple of 8, and it is invalid for N to equal 8, 16, 32, 64 as there are more specific
// types for these widths.
// Go Encoding: []byte where len([]byte) == Size / 8, little-endian encoded.
// JSON Encoding: base10 integer string matching the IntegerFormat regex, canonically with no leading zeros.
// Key Binary Encoding: N / 8 bytes big-endian encoded
// Value Binary Encoding: N / 8 bytes little-endian encoded
UIntNKind
// IntNKind represents an unsigned integer type with a width in bits specified by the Size field in the
// field definition. N must be a multiple of 8.
// Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added.
// N must be a multiple of 8, and it is invalid for N to equal 8, 16, 32, 64 as there are more specific
// types for these widths.
// Go Encoding: []byte where len([]byte) == Size / 8, two's complement little-endian encoded.
// JSON Encoding: base10 integer string matching the IntegerFormat regex, canonically with no leading zeros.
// Key Binary Encoding: N / 8 bytes big-endian two's complement encoded with the first bit inverted for sorting.
// Value Binary Encoding: N / 8 bytes little-endian two's complement encoded.
IntNKind
// StructKind represents a struct object.
// Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added.
// Go Encoding: an array of type []interface{} where each element is of the respective field's kind type.
// JSON Encoding: an object where each key is the field name and the value is the field value.
// Canonically, keys are in alphabetical order with no extra whitespace.
// Key Binary Encoding: not valid as a key field.
// Value Binary Encoding: 32-bit unsigned little-endian length prefix,
// followed by the value binary encoding of each field in order.
StructKind
// OneOfKind represents a field that can be one of a set of types.
// Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added.
// Go Encoding: the anonymous struct { Case string; Value interface{} }, aliased as OneOfValue.
// JSON Encoding: same as the case's struct encoding with "@type" set to the case name.
// Key Binary Encoding: not valid as a key field.
// Value Binary Encoding: the oneof's discriminant numeric value encoded as its discriminant kind
// followed by the encoded value.
OneOfKind
// ListKind represents a list of elements.
// Support for this is currently UNIMPLEMENTED, this notice will be removed when it is added.
// Go Encoding: an array of type []interface{} where each element is of the respective field's kind type.
// JSON Encoding: an array of values where each element is the field value.
// Canonically, there is no extra whitespace.
// Key Binary Encoding: not valid as a key field.
// Value Binary Encoding: 32-bit unsigned little-endian size prefix indicating the size of the encoded data in bytes,
// followed by a 32-bit unsigned little-endian count of the number of elements in the list,
// followed by each element encoded with value binary encoding.
ListKind
)
// 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
}
}