-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathconversion.go
289 lines (255 loc) · 8.25 KB
/
conversion.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
package safecast
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
// Convert attempts to convert any value to the desired type
// - If the conversion is possible, the converted value is returned.
// - If the conversion results in a value outside the range of the desired type, an [ErrRangeOverflow] error is wrapped in the returned error.
// - If the conversion exceeds the maximum value of the desired type, an [ErrExceedMaximumValue] error is wrapped in the returned error.
// - If the conversion exceeds the minimum value of the desired type, an [ErrExceedMinimumValue] error is wrapped in the returned error.
// - If the conversion is not possible for the desired type, an [ErrUnsupportedConversion] error is wrapped in the returned error.
// - If the conversion fails from string, an [ErrStringConversion] error is wrapped in the returned error.
// - If the conversion results in an error, an [ErrConversionIssue] error is wrapped in the returned error.
func Convert[NumOut Number](orig any) (converted NumOut, err error) {
switch v := orig.(type) {
case int:
return convertFromNumber[NumOut](v)
case uint:
return convertFromNumber[NumOut](v)
case int8:
return convertFromNumber[NumOut](v)
case uint8:
return convertFromNumber[NumOut](v)
case int16:
return convertFromNumber[NumOut](v)
case uint16:
return convertFromNumber[NumOut](v)
case int32:
return convertFromNumber[NumOut](v)
case uint32:
return convertFromNumber[NumOut](v)
case int64:
return convertFromNumber[NumOut](v)
case uint64:
return convertFromNumber[NumOut](v)
case float32:
return convertFromNumber[NumOut](v)
case float64:
return convertFromNumber[NumOut](v)
case bool:
o := 0
if v {
o = 1
}
return NumOut(o), nil
case fmt.Stringer:
return convertFromString[NumOut](v.String())
case error:
return convertFromString[NumOut](v.Error())
case string:
return convertFromString[NumOut](v)
}
return 0, errorHelper{
err: fmt.Errorf("%w from %T", ErrUnsupportedConversion, orig),
}
}
// MustConvert calls [Convert] to convert the value to the desired type, and panics if the conversion fails.
func MustConvert[NumOut Number](orig any) NumOut {
converted, err := Convert[NumOut](orig)
if err != nil {
panic(err)
}
return converted
}
func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOut, err error) {
converted = NumOut(orig)
// floats could be compared directly
switch any(converted).(type) {
case float64:
// float64 cannot overflow, so we don't have to worry about it
return converted, nil
case float32:
origFloat64, isFloat64 := any(orig).(float64)
if !isFloat64 {
// only float64 can overflow float32
// everything else can be safely converted
return converted, nil
}
// check boundary
if math.Abs(origFloat64) < math.MaxFloat32 {
// the value is within float32 range, there is no overflow
return converted, nil
}
// TODO: check for numbers close to math.MaxFloat32
boundary := getUpperBoundary(converted)
errBoundary := ErrExceedMaximumValue
if negative(orig) {
boundary = getLowerBoundary(converted)
errBoundary = ErrExceedMinimumValue
}
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
errBoundary := ErrExceedMaximumValue
boundary := getUpperBoundary(converted)
if negative(orig) {
errBoundary = ErrExceedMinimumValue
boundary = getLowerBoundary(converted)
}
if !sameSign(orig, converted) {
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
// convert back to the original type
cast := NumIn(converted)
// and compare
base := orig
switch f := any(orig).(type) {
case float64:
base = NumIn(math.Trunc(f))
case float32:
base = NumIn(math.Trunc(float64(f)))
}
// exact match
if cast == base {
return converted, nil
}
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
func convertFromString[NumOut Number](s string) (converted NumOut, err error) {
s = strings.TrimSpace(s)
if b, err := strconv.ParseBool(s); err == nil {
if b {
return NumOut(1), nil
}
return NumOut(0), nil
}
if strings.Contains(s, ".") {
o, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
if strings.HasPrefix(s, "-") {
o, err := strconv.ParseInt(s, 0, 64)
if err != nil {
if errors.Is(err, strconv.ErrRange) {
return 0, errorHelper{
value: s,
err: ErrExceedMinimumValue,
boundary: math.MinInt,
}
}
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
o, err := strconv.ParseUint(s, 0, 64)
if err != nil {
if errors.Is(err, strconv.ErrRange) {
return 0, errorHelper{
value: s,
err: ErrExceedMaximumValue,
boundary: uint(math.MaxUint),
}
}
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
// ToInt attempts to convert any [Type] value to an int.
// If the conversion results in a value outside the range of an int,
// an [ErrConversionIssue] error is returned.
func ToInt[T Number](i T) (int, error) {
return convertFromNumber[int](i)
}
// ToUint attempts to convert any [Number] value to an uint.
// If the conversion results in a value outside the range of an uint,
// an [ErrConversionIssue] error is returned.
func ToUint[T Number](i T) (uint, error) {
return convertFromNumber[uint](i)
}
// ToInt8 attempts to convert any [Number] value to an int8.
// If the conversion results in a value outside the range of an int8,
// an [ErrConversionIssue] error is returned.
func ToInt8[T Number](i T) (int8, error) {
return convertFromNumber[int8](i)
}
// ToUint8 attempts to convert any [Number] value to an uint8.
// If the conversion results in a value outside the range of an uint8,
// an [ErrConversionIssue] error is returned.
func ToUint8[T Number](i T) (uint8, error) {
return convertFromNumber[uint8](i)
}
// ToInt16 attempts to convert any [Number] value to an int16.
// If the conversion results in a value outside the range of an int16,
// an [ErrConversionIssue] error is returned.
func ToInt16[T Number](i T) (int16, error) {
return convertFromNumber[int16](i)
}
// ToUint16 attempts to convert any [Number] value to an uint16.
// If the conversion results in a value outside the range of an uint16,
// an [ErrConversionIssue] error is returned.
func ToUint16[T Number](i T) (uint16, error) {
return convertFromNumber[uint16](i)
}
// ToInt32 attempts to convert any [Number] value to an int32.
// If the conversion results in a value outside the range of an int32,
// an [ErrConversionIssue] error is returned.
func ToInt32[T Number](i T) (int32, error) {
return convertFromNumber[int32](i)
}
// ToUint32 attempts to convert any [Number] value to an uint32.
// If the conversion results in a value outside the range of an uint32,
// an [ErrConversionIssue] error is returned.
func ToUint32[T Number](i T) (uint32, error) {
return convertFromNumber[uint32](i)
}
// ToInt64 attempts to convert any [Number] value to an int64.
// If the conversion results in a value outside the range of an int64,
// an [ErrConversionIssue] error is returned.
func ToInt64[T Number](i T) (int64, error) {
return convertFromNumber[int64](i)
}
// ToUint64 attempts to convert any [Number] value to an uint64.
// If the conversion results in a value outside the range of an uint64,
// an [ErrConversionIssue] error is returned.
func ToUint64[T Number](i T) (uint64, error) {
return convertFromNumber[uint64](i)
}
// ToFloat32 attempts to convert any [Number] value to a float32.
// If the conversion results in a value outside the range of a float32,
// an [ErrConversionIssue] error is returned.
func ToFloat32[T Number](i T) (float32, error) {
return convertFromNumber[float32](i)
}
// ToFloat64 attempts to convert any [Number] value to a float64.
// If the conversion results in a value outside the range of a float64,
// an [ErrConversionIssue] error is returned.
func ToFloat64[T Number](i T) (float64, error) {
return convertFromNumber[float64](i)
}