/
type_conversions.go
371 lines (352 loc) · 10.2 KB
/
type_conversions.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
package data
import (
"encoding/base64"
"fmt"
"math"
"strconv"
"strings"
"time"
)
const (
// MaxConvFloat64 is the largest float64 that can be converted to int64.
MaxConvFloat64 = float64(math.MaxInt64)
// MinConvFloat64 is the smallest float64 that can be converted to int64
MinConvFloat64 = float64(math.MinInt64)
errNilConversionFormat = "cannot convert nil to %v"
)
// AsBool returns a bool value only when the type of Value is TypeBool,
// otherwise it returns error.
func AsBool(v Value) (bool, error) {
if v == nil {
return false, fmt.Errorf(errNilConversionFormat, TypeBool)
}
return v.asBool()
}
// AsInt returns an integer value only when the type of Value is TypeInt,
// otherwise it returns error.
func AsInt(v Value) (int64, error) {
if v == nil {
return 0, fmt.Errorf(errNilConversionFormat, TypeInt)
}
return v.asInt()
}
// AsFloat returns a float value only when the type of Value is TypeFloat,
// otherwise it returns error.
func AsFloat(v Value) (float64, error) {
if v == nil {
return 0, fmt.Errorf(errNilConversionFormat, TypeFloat)
}
return v.asFloat()
}
// AsString returns a string only when the type of Value is TypeString,
// otherwise it returns error.
func AsString(v Value) (string, error) {
if v == nil {
return "", fmt.Errorf(errNilConversionFormat, TypeString)
}
return v.asString()
}
// AsBlob returns an array of bytes only when the type of Value is TypeBlob,
// otherwise it returns error.
func AsBlob(v Value) ([]byte, error) {
if v == nil {
return nil, fmt.Errorf(errNilConversionFormat, TypeBlob)
}
return v.asBlob()
}
// AsTimestamp returns a time.Time only when the type of Value is TypeTime,
// otherwise it returns error.
func AsTimestamp(v Value) (time.Time, error) {
if v == nil {
return time.Time{}, fmt.Errorf(errNilConversionFormat, TypeTimestamp)
}
return v.asTimestamp()
}
// AsArray returns an array of Values only when the type of Value is TypeArray,
// otherwise it returns error.
func AsArray(v Value) (Array, error) {
if v == nil {
return nil, fmt.Errorf(errNilConversionFormat, TypeArray)
}
return v.asArray()
}
// AsMap returns a map of string keys and Values only when the type of Value is
// TypeMap, otherwise it returns error.
func AsMap(v Value) (Map, error) {
if v == nil {
return nil, fmt.Errorf(errNilConversionFormat, TypeMap)
}
return v.asMap()
}
// ToBool converts a given Value to a bool, if possible. The conversion
// rules are similar to those in Python:
//
// * Null: false
// * Bool: actual boolean value
// * Int: true if non-zero
// * Float: true if non-zero and not NaN
// * String: true if non-empty
// * Blob: true if non-empty
// * Timestamp: true if IsZero() is false
// * Array: true if non-empty
// * Map: true if non-empty
func ToBool(v Value) (bool, error) {
defaultValue := false
switch v.Type() {
case TypeNull:
return defaultValue, nil
case TypeBool:
return v.asBool()
case TypeInt:
val, _ := v.asInt()
return val != 0, nil
case TypeFloat:
val, _ := v.asFloat()
return val != 0.0 && !math.IsNaN(val), nil
case TypeString:
val, _ := v.asString()
val = strings.TrimSpace(val) // keep this for error reporting
switch strings.ToLower(val) {
case "t", "true", "y", "yes", "on", "1":
return true, nil
case "f", "false", "n", "no", "off", "0":
return false, nil
}
return false, fmt.Errorf("invalid string as a bool literal: %v", val)
case TypeBlob:
val, _ := v.asBlob()
return len(val) > 0, nil
case TypeTimestamp:
val, _ := v.asTimestamp()
return !val.IsZero(), nil
case TypeArray:
val, _ := v.asArray()
return len(val) > 0, nil
case TypeMap:
val, _ := v.asMap()
return len(val) > 0, nil
default:
return defaultValue,
fmt.Errorf("cannot convert %T to bool", v)
}
}
// ToInt converts a given Value to an int64, if possible. The conversion
// rules are as follows:
//
// * Null: 0
// * Bool: 0 if false, 1 if true
// * Int: actual value
// * Float: conversion as done by int64(value)
// (values outside of valid int64 bounds will lead to an error)
// * String: parsed integer with base 0 as per strconv.ParseInt
// (values outside of valid int64 bounds will lead to an error)
// * Blob: (error)
// * Timestamp: the number of second elapsed since January 1, 1970 UTC.
// * Array: (error)
// * Map: (error)
func ToInt(v Value) (int64, error) {
defaultValue := int64(0)
switch v.Type() {
case TypeNull:
return defaultValue, nil
case TypeBool:
val, _ := v.asBool()
if val {
return 1, nil
}
return 0, nil
case TypeInt:
return v.asInt()
case TypeFloat:
val, _ := v.asFloat()
if val >= MinConvFloat64 && val <= MaxConvFloat64 {
return int64(val), nil
}
return defaultValue,
fmt.Errorf("%v is out of bounds for int64 conversion", val)
case TypeString:
val, _ := v.asString()
return strconv.ParseInt(val, 0, 64)
case TypeTimestamp:
val, _ := v.asTimestamp()
// return only second part
seconds := time.Duration(val.Unix())
return int64(seconds), nil
default:
return defaultValue,
fmt.Errorf("cannot convert %T to int64", v)
}
}
// ToFloat converts a given Value to a float64, if possible. The conversion
// rules are as follows:
//
// * Null: 0.0
// * Bool: 0.0 if false, 1.0 if true
// * Int: conversion as done by float64(value)
// * Float: actual value
// * String: parsed float as per strconv.ParseFloat
// (values outside of valid float64 bounds will lead to an error)
// * Blob: (error)
// * Timestamp: the number of seconds (not microseconds!) elapsed since
// January 1, 1970 UTC, with a decimal part
// * Array: (error)
// * Map: (error)
func ToFloat(v Value) (float64, error) {
defaultValue := float64(0)
switch v.Type() {
case TypeNull:
return defaultValue, nil
case TypeBool:
val, _ := v.asBool()
if val {
return 1.0, nil
}
return 0.0, nil
case TypeInt:
val, _ := v.asInt()
return float64(val), nil
case TypeFloat:
return v.asFloat()
case TypeString:
val, _ := v.asString()
return strconv.ParseFloat(val, 64)
case TypeTimestamp:
val, _ := v.asTimestamp()
// We want to compute `val.UnixNano()/1e9`, but sometimes `UnixNano()`
// is not defined, so we switch to `val.Unix() + val.Nanosecond()/1e9`.
// Note that due to numerical issues, this sometimes yields different
// results within the range of machine precision.
return float64(val.Unix()) + float64(val.Nanosecond())/1e9, nil
default:
return defaultValue,
fmt.Errorf("cannot convert %T to float64", v)
}
}
// ToString converts a given Value to a string. The conversion
// rules are as follows:
//
// * Null: ""
// * String: the actual string
// * Blob: base64-encoded string
// * Timestamp: ISO 8601 representation, see time.RFC3339
// * other: Go's "%#v" representation
func ToString(v Value) (string, error) {
switch v.Type() {
case TypeNull:
return "", nil
case TypeString:
// if we used "%#v", we will get a quoted string; if
// we used "%v", we will get the result of String()
// (which is JSON, i.e., also quoted)
return v.asString()
case TypeBlob:
val, _ := v.asBlob()
return base64.StdEncoding.EncodeToString(val), nil
case TypeTimestamp:
val, _ := v.asTimestamp()
return val.Format(time.RFC3339Nano), nil
case TypeArray, TypeMap:
return v.String(), nil
default:
return fmt.Sprintf("%#v", v), nil
}
}
// ToBlob converts a given Value to []byte, if possible.
// The conversion rules are as follows:
//
// * Null: nil
// * String: []byte just copied from string
// * Blob: actual value
// * other: (error)
func ToBlob(v Value) ([]byte, error) {
switch v.Type() {
case TypeNull:
return nil, nil
case TypeString:
val, _ := v.asString()
return base64.StdEncoding.DecodeString(val)
case TypeBlob:
return v.asBlob()
case TypeArray:
a, _ := v.asArray()
b := make([]byte, len(a))
for i, e := range a {
v, err := e.asInt()
if err != nil {
return nil, fmt.Errorf("cannot convert %v to Blob value", e.Type())
} else if !(0 <= v && v <= 255) {
return nil, fmt.Errorf("cannot convert int to Blob value: %v", v)
}
b[i] = byte(v)
}
return b, nil
default:
return nil, fmt.Errorf("cannot convert %T to Blob", v)
}
}
// ToTimestamp converts a given Value to a time.Time struct, if possible.
// The conversion rules are as follows:
//
// * Null: zero time (this is *not* the time with Unix time 0!)
// * Int: Time with the given Unix time in seconds
// * Float: Time with the given Unix time in seconds, where the decimal
// part will be considered as a part of a second
// (values outside of valid int64 bounds will lead to an error)
// * String: Time with the given RFC3339/ISO8601 representation
// * Timestamp: actual time
// * other: (error)
func ToTimestamp(v Value) (time.Time, error) {
defaultValue := time.Time{}
switch v.Type() {
case TypeNull:
return defaultValue, nil
case TypeInt:
val, _ := v.asInt()
return time.Unix(val, 0), nil
case TypeFloat:
val, _ := v.asFloat()
if val >= MinConvFloat64 && val <= MaxConvFloat64 {
// say val is 3.7 or -4.6
integralPart := int64(val) // 3 or -4
decimalPart := val - float64(integralPart) // 0.7 or -0.6
ns := int64(1e9 * decimalPart) // nanosecond part
return time.Unix(integralPart, ns), nil
}
return defaultValue,
fmt.Errorf("%v is out of bounds for int64 conversion", val)
case TypeString:
val, _ := v.asString()
return time.Parse(time.RFC3339Nano, val)
case TypeTimestamp:
return v.asTimestamp()
default:
return defaultValue,
fmt.Errorf("cannot convert %T to Time", v)
}
}
// ToDuration converts a Value to time.Duration, if possible.
// The conversion rules are as follows:
//
// * Null: 0
// * Int: Converted to seconds (e.g. 3 is equal to 3 seconds)
// * Float: Converted to seconds (e.g. 3.141592 equals 3s + 141ms + 592us)
// * String: time.ParseDuration will be called
// * other: (error)
func ToDuration(v Value) (time.Duration, error) {
switch v.Type() {
case TypeNull:
var defaultValue time.Duration
return defaultValue, nil
case TypeInt:
i, _ := v.asInt()
return time.Duration(i) * time.Second, nil
case TypeFloat:
f, _ := v.asFloat()
return time.Duration(f * float64(time.Second)), nil
case TypeString:
s, _ := v.asString()
return time.ParseDuration(s)
default:
return 0, fmt.Errorf("cannot convert %T to Duration", v)
}
}