├── .travis.yml ├── LICENSE ├── README.md ├── conv.go ├── conv_test.go ├── example_test.go ├── go.mod └── internal ├── convert ├── convert.go ├── fnconv.go └── fnconv_test.go ├── generated ├── generated.go ├── maptests.go └── slicetests.go ├── refconv ├── bool.go ├── float.go ├── infer.go ├── int.go ├── refconv.go ├── refconv_test.go ├── string.go ├── time.go └── uint.go ├── refutil ├── refutil.go └── refutil_test.go ├── testconv ├── assert.go ├── bool.go ├── duration.go ├── float.go ├── infer.go ├── int.go ├── map.go ├── readme.go ├── slice.go ├── string.go ├── struct.go ├── testconv.go ├── time.go └── uint.go └── testdata └── README.md.tpl /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | go: 8 | - tip 9 | - 1.8 10 | - 1.7 11 | 12 | matrix: 13 | allow_failures: 14 | - go: tip 15 | 16 | before_install: 17 | - go get -t -v ./... 18 | 19 | script: 20 | - go test -race -coverprofile=coverage.txt -covermode=atomic 21 | 22 | after_success: 23 | - bash <(curl -s https://codecov.io/bash) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Chris Stockton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Package: conv 2 | 3 | [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/cstockton/go-conv) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/cstockton/go-conv?style=flat-square)](https://goreportcard.com/report/github.com/cstockton/go-conv) 5 | [![Coverage Status](https://img.shields.io/codecov/c/github/cstockton/go-conv/master.svg?style=flat-square)](https://codecov.io/github/cstockton/go-conv?branch=master) 6 | [![Build Status](http://img.shields.io/travis/cstockton/go-conv.svg?style=flat-square)](https://travis-ci.org/cstockton/go-conv) 7 | [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/cstockton/go-conv/master/LICENSE) 8 | 9 | > Get: 10 | > ```bash 11 | > go get -u github.com/cstockton/go-conv 12 | > ``` 13 | > 14 | > Example: 15 | > ```Go 16 | > // Basic types 17 | > if got, err := conv.Bool(`TRUE`); err == nil { 18 | > fmt.Printf("conv.Bool(`TRUE`)\n -> %v\n", got) 19 | > } 20 | > if got, err := conv.Duration(`1m2s`); err == nil { 21 | > fmt.Printf("conv.Duration(`1m2s`)\n -> %v\n", got) 22 | > } 23 | > var date time.Time 24 | > err := conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`) 25 | > fmt.Printf("conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`)\n -> %v\n", got) 26 | > ``` 27 | > 28 | > Output: 29 | > ```Go 30 | > conv.Bool(`TRUE`) 31 | > -> true 32 | > conv.Duration(`1m2s`) 33 | > -> 1m2s 34 | > conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`) 35 | > -> 2015-03-07 11:06:39 +0000 PST 36 | > ``` 37 | 38 | 39 | ## Intro 40 | 41 | **Notice:** If you begin getting compilation errors use the v1 import path `gopkg.in/cstockton/go-conv.v1` for an immediate fix and to future-proof. 42 | 43 | Package conv provides fast and intuitive conversions across Go types. This library uses reflection to be robust but will bypass it for common conversions, for example string conversion to any type will never use reflection. All functions are safe for concurrent use by multiple Goroutines. 44 | 45 | ### Overview 46 | 47 | All conversion functions accept any type of value for conversion, if unable 48 | to find a reasonable conversion path they will return the target types zero 49 | value and an error. 50 | 51 | > Example: 52 | > ```Go 53 | > // The zero value and a non-nil error is returned on failure. 54 | > fmt.Println(conv.Int("Foo")) 55 | > 56 | > // Conversions are allowed as long as the underlying type is convertable, for 57 | > // example: 58 | > type MyString string 59 | > fmt.Println(conv.Int(MyString("42"))) // 42, nil 60 | > 61 | > // Pointers will be dereferenced when appropriate. 62 | > str := "42" 63 | > fmt.Println(conv.Int(&str)) // 42, nil 64 | > 65 | > // You may infer values from the base type of a pointer, giving you one 66 | > // function signature for all conversions. This may be convenient when the 67 | > // types are not known until runtime and reflection must be used. 68 | > var val int 69 | > err := conv.Infer(&val, `42`) 70 | > fmt.Println(val, err) // 42, nil 71 | > ``` 72 | > 73 | > Output: 74 | > ```Go 75 | > 0 cannot convert "Foo" (type string) to int 76 | > 42 77 | > 42 78 | > 42 79 | > ``` 80 | 81 | 82 | ### Bool 83 | 84 | Bool conversion supports all the paths provided by the standard libraries 85 | strconv.ParseBool when converting from a string, all other conversions are 86 | simply true when not the types zero value. As a special case zero length map 87 | and slice types are also false, even if initialized. 88 | 89 | > Example: 90 | > ```Go 91 | > // Bool conversion from other bool values will be returned without 92 | > // modification. 93 | > fmt.Println(conv.Bool(true)) 94 | > fmt.Println(conv.Bool(false)) 95 | > 96 | > // Bool conversion from strings consider the following values true: 97 | > // "t", "T", "true", "True", "TRUE", 98 | > // "y", "Y", "yes", "Yes", "YES", "1" 99 | > // 100 | > // It considers the following values false: 101 | > // "f", "F", "false", "False", "FALSE", 102 | > // "n", "N", "no", "No", "NO", "0" 103 | > fmt.Println(conv.Bool("T")) 104 | > fmt.Println(conv.Bool("False")) 105 | > 106 | > // Bool conversion from other supported types will return true unless it is 107 | > // the zero value for the given type. 108 | > fmt.Println(conv.Bool(int64(123))) 109 | > fmt.Println(conv.Bool(int64(0))) 110 | > fmt.Println(conv.Bool(time.Duration(123))) 111 | > fmt.Println(conv.Bool(time.Duration(0))) 112 | > fmt.Println(conv.Bool(time.Now())) 113 | > fmt.Println(conv.Bool(time.Time{})) 114 | > 115 | > // All other types will return false. 116 | > fmt.Println(conv.Bool(struct{ string }{""})) 117 | > ``` 118 | > 119 | > Output: 120 | > ```Go 121 | > true 122 | > false 123 | > true 124 | > false 125 | > true 126 | > false 127 | > true 128 | > false 129 | > true 130 | > false 131 | > false cannot convert struct { string }{string:""} (type struct { string }) to bool 132 | > ``` 133 | 134 | 135 | ### Duration 136 | 137 | Duration conversion supports all the paths provided by the standard libraries 138 | time.ParseDuration when converting from strings, with a couple enhancements 139 | outlined below. 140 | 141 | > Example: 142 | > ```Go 143 | > // Duration conversion from strings will first attempt to parse as a Go 144 | > // duration value using ParseDuration, then fall back to numeric conventions. 145 | > fmt.Println(conv.Duration("1h1m100ms")) // 1h1m0.1s 146 | > fmt.Println(conv.Duration("3660100000000")) // 1h1m0.1s 147 | > 148 | > // Numeric conversions directly convert to time.Duration nanoseconds. 149 | > fmt.Println(conv.Duration(3660100000000)) // 1h1m0.1s 150 | > 151 | > // Floats deviate from the numeric conversion rules, instead 152 | > // separating the integer and fractional portions into seconds. 153 | > fmt.Println(conv.Duration("3660.10")) // 1h1m0.1s 154 | > fmt.Println(conv.Duration(float64(3660.10))) // 1h1m0.1s 155 | > 156 | > // Complex numbers are Float conversions using the real number. 157 | > fmt.Println(conv.Duration(complex(3660.10, 0))) // 1h1m0.1s 158 | > 159 | > // Duration conversion from time.Duration and any numerical type will be 160 | > // converted using a standard Go conversion. This includes strings 161 | > fmt.Println(conv.Duration(time.Nanosecond)) // 1s 162 | > fmt.Println(conv.Duration(byte(1))) // 1ns 163 | > ``` 164 | > 165 | > Output: 166 | > ```Go 167 | > 1h1m0.1s 168 | > 1h1m0.1s 169 | > 1h1m0.1s 170 | > 1h1m0.1s 171 | > 1h1m0.1s 172 | > 1h1m0.1s 173 | > 1ns 174 | > 1ns 175 | > ``` 176 | 177 | 178 | ### Float64 179 | 180 | Float64 conversion from other float values of an identical type will be 181 | returned without modification. Float64 from other types follow the general 182 | numeric rules. 183 | 184 | > Example: 185 | > ```Go 186 | > fmt.Println(conv.Float64(float64(123.456))) // 123.456 187 | > fmt.Println(conv.Float64("-123.456")) // -123.456 188 | > fmt.Println(conv.Float64("1.7976931348623157e+308")) 189 | > ``` 190 | > 191 | > Output: 192 | > ```Go 193 | > 123.456 194 | > -123.456 195 | > 1.7976931348623157e+308 196 | > ``` 197 | 198 | 199 | ### Infer 200 | 201 | Infer will perform conversion by inferring the conversion operation from 202 | a pointer to a supported T of the `into` param. Since the value is assigned 203 | directly only a error value is returned, meaning no type assertions needed. 204 | 205 | > Example: 206 | > ```Go 207 | > // Infer requires a pointer to all types. 208 | > var into int 209 | > if err := conv.Infer(into, `42`); err != nil { 210 | > fmt.Println(err) 211 | > } 212 | > if err := conv.Infer(&into, `42`); err == nil { 213 | > fmt.Println(into) 214 | > } 215 | > 216 | > // Same as above but using new() 217 | > truth := new(bool) 218 | > if err := conv.Infer(truth, `TRUE`); err != nil { 219 | > fmt.Println("Failed!") 220 | > ``` 221 | > 222 | > Output: 223 | > ```Go 224 | > cannot infer conversion for unchangeable 0 (type int) 225 | > 42 226 | > ``` 227 | 228 | 229 | ### Int 230 | 231 | Int conversions follow the the general numeric rules. 232 | 233 | > Example: 234 | > ```Go 235 | > fmt.Println(conv.Uint("123.456")) // 123 236 | > fmt.Println(conv.Uint("-123.456")) // 0 237 | > fmt.Println(conv.Uint8(uint64(math.MaxUint64))) // 255 238 | > ``` 239 | > 240 | > Output: 241 | > ```Go 242 | > 123 243 | > 0 244 | > 255 245 | > ``` 246 | 247 | 248 | ### String 249 | 250 | String conversion from any values outside the cases below will simply be the 251 | result of calling fmt.Sprintf("%v", value), meaning it can not fail. An error 252 | is still provided and you should check it to be future proof. 253 | 254 | > Example: 255 | > ```Go 256 | > // String conversion from other string values will be returned without 257 | > // modification. 258 | > fmt.Println(conv.String("Foo")) 259 | > 260 | > // As a special case []byte will also be returned after a Go string conversion 261 | > // is applied. 262 | > fmt.Println(conv.String([]byte("Foo"))) 263 | > 264 | > // String conversion from types that do not have a valid conversion path will 265 | > // still have sane string conversion for troubleshooting. 266 | > fmt.Println(conv.String(struct{ msg string }{"Foo"})) 267 | > ``` 268 | > 269 | > Output: 270 | > ```Go 271 | > Foo 272 | > Foo 273 | > {Foo} 274 | > ``` 275 | 276 | 277 | ### Time 278 | 279 | Time conversion from other time values will be returned without modification. 280 | 281 | > Example: 282 | > ```Go 283 | > // Time conversion from other time.Time values will be returned without 284 | > // modification. 285 | > fmt.Println(`Times:`) 286 | > fmt.Println(conv.Time(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC))) 287 | > 288 | > // Time conversion from strings will be passed through time.Parse using a 289 | > // variety of formats. Strings that could not be parsed along with all other 290 | > // values will return an empty time.Time{} struct. 291 | > fmt.Println(`Strings:`) 292 | > formats := []string{ 293 | > `Mon, 02 Jan 2006 15:04:05`, 294 | > `Mon, 02 Jan 2006 15:04:05 UTC`, 295 | > `Mon, 2 Jan 2006 15:04:05`, 296 | > `Mon, 2 Jan 2006 15:04:05 UTC`, 297 | > `02 Jan 2006 15:04 UTC`, 298 | > `2 Jan 2006 15:04:05`, 299 | > `2 Jan 2006 15:04:05 UTC`, 300 | > } 301 | > for _, format := range formats { 302 | > t, err := conv.Time(format) 303 | > if err != nil { 304 | > fmt.Println(`Conversion error: `, err) 305 | > } 306 | > fmt.Printf("%v <-- (%v)\n", t, format) 307 | > } 308 | > 309 | > // Time conversion from types that do not have a valid conversion path will 310 | > // return the zero value and an error. 311 | > fmt.Println(`Errors:`) 312 | > fmt.Println(conv.Time(1)) // cannot convert 1 (type int) to time.Time 313 | > fmt.Println(conv.Time(true)) // cannot convert true (type bool) to time.Time 314 | > ``` 315 | > 316 | > Output: 317 | > ```Go 318 | > Times: 319 | > 2006-01-02 15:04:05 +0000 UTC 320 | > Strings: 321 | > 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 02 Jan 2006 15:04:05) 322 | > 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 02 Jan 2006 15:04:05 UTC) 323 | > 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 2 Jan 2006 15:04:05) 324 | > 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 2 Jan 2006 15:04:05 UTC) 325 | > 2006-01-02 15:04:00 +0000 UTC <-- (02 Jan 2006 15:04 UTC) 326 | > 2006-01-02 15:04:05 +0000 UTC <-- (2 Jan 2006 15:04:05) 327 | > 2006-01-02 15:04:05 +0000 UTC <-- (2 Jan 2006 15:04:05 UTC) 328 | > Errors: 329 | > 0001-01-01 00:00:00 +0000 UTC cannot convert 1 (type int) to time.Time 330 | > 0001-01-01 00:00:00 +0000 UTC cannot convert true (type bool) to time.Time 331 | > ``` 332 | 333 | 334 | ### Uint 335 | 336 | Uint conversions follow the the general numeric rules. 337 | 338 | > Example: 339 | > ```Go 340 | > fmt.Println(conv.Uint("123.456")) // 123 341 | > fmt.Println(conv.Uint("-123.456")) // 0 342 | > fmt.Println(conv.Uint8(uint64(math.MaxUint64))) // 255 343 | > ``` 344 | > 345 | > Output: 346 | > ```Go 347 | > 123 348 | > 0 349 | > 255 350 | > ``` 351 | 352 | 353 | ### Numerics 354 | 355 | Numeric conversion from other numeric values of an identical type will be 356 | returned without modification. Numeric conversions deviate slightly from Go 357 | when dealing with under/over flow. When performing a conversion operation 358 | that would overflow, we instead assign the maximum value for the target type. 359 | Similarly, conversions that would underflow are assigned the minimun value 360 | for that type, meaning unsigned integers are given zero values instead of 361 | spilling into large positive integers. 362 | 363 | > Example: 364 | > ```Go 365 | > // For more natural Float -> Integer when the underlying value is a string. 366 | > // Conversion functions will always try to parse the value as the target type 367 | > // first. If parsing fails float parsing with truncation will be attempted. 368 | > fmt.Println(conv.Int("-123.456")) // -123 369 | > 370 | > // This does not apply for unsigned integers if the value is negative. Instead 371 | > // performing a more intuitive (to the human) truncation to zero. 372 | > fmt.Println(conv.Uint("-123.456")) // 0 373 | > ``` 374 | > 375 | > Output: 376 | > ```Go 377 | > -123 378 | > 0 379 | > ``` 380 | 381 | 382 | ### Panics 383 | 384 | In short, panics should not occur within this library under any circumstance. 385 | This obviously excludes any oddities that may surface when the runtime is not 386 | in a healthy state, i.e. uderlying system instability, memory exhaustion. If 387 | you are able to create a reproducible panic please file a bug report. 388 | 389 | > Example: 390 | > ```Go 391 | > // The zero value for the target type is always returned. 392 | > fmt.Println(conv.Bool(nil)) 393 | > fmt.Println(conv.Bool([][]int{})) 394 | > fmt.Println(conv.Bool((chan string)(nil))) 395 | > fmt.Println(conv.Bool((*interface{})(nil))) 396 | > fmt.Println(conv.Bool((*interface{})(nil))) 397 | > fmt.Println(conv.Bool((**interface{})(nil))) 398 | > ``` 399 | > 400 | > Output: 401 | > ```Go 402 | > false cannot convert (type ) to bool 403 | > false 404 | > false 405 | > false cannot convert (*interface {})(nil) (type *interface {}) to bool 406 | > false cannot convert (*interface {})(nil) (type *interface {}) to bool 407 | > false cannot convert (**interface {})(nil) (type **interface {}) to bool 408 | > ``` 409 | 410 | 411 | ## Contributing 412 | 413 | Feel free to create issues for bugs, please ensure code coverage remains 100% 414 | with any pull requests. 415 | 416 | 417 | ## Bugs and Patches 418 | 419 | Feel free to report bugs and submit pull requests. 420 | 421 | * bugs: 422 | 423 | * patches: 424 | 425 | -------------------------------------------------------------------------------- /conv.go: -------------------------------------------------------------------------------- 1 | // Package conv provides fast and intuitive conversions across Go types. 2 | package conv 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/cstockton/go-conv/internal/refconv" 8 | ) 9 | 10 | var converter = refconv.Conv{} 11 | 12 | // Infer will perform conversion by inferring the conversion operation from 13 | // the base type of a pointer to a supported T. 14 | // 15 | // Example: 16 | // 17 | // var into int64 18 | // err := conv.Infer(&into, `12`) 19 | // // into -> 12 20 | // 21 | // See examples for more usages. 22 | func Infer(into, from interface{}) error { 23 | return converter.Infer(into, from) 24 | } 25 | 26 | // Bool will convert the given value to a bool, returns the default value of 27 | // false if a conversion can not be made. 28 | func Bool(from interface{}) (bool, error) { 29 | return converter.Bool(from) 30 | } 31 | 32 | // Duration will convert the given value to a time.Duration, returns the default 33 | // value of 0ns if a conversion can not be made. 34 | func Duration(from interface{}) (time.Duration, error) { 35 | return converter.Duration(from) 36 | } 37 | 38 | // String will convert the given value to a string, returns the default value 39 | // of "" if a conversion can not be made. 40 | func String(from interface{}) (string, error) { 41 | return converter.String(from) 42 | } 43 | 44 | // Time will convert the given value to a time.Time, returns the empty struct 45 | // time.Time{} if a conversion can not be made. 46 | func Time(from interface{}) (time.Time, error) { 47 | return converter.Time(from) 48 | } 49 | 50 | // Float32 will convert the given value to a float32, returns the default value 51 | // of 0.0 if a conversion can not be made. 52 | func Float32(from interface{}) (float32, error) { 53 | return converter.Float32(from) 54 | } 55 | 56 | // Float64 will convert the given value to a float64, returns the default value 57 | // of 0.0 if a conversion can not be made. 58 | func Float64(from interface{}) (float64, error) { 59 | return converter.Float64(from) 60 | } 61 | 62 | // Int will convert the given value to a int, returns the default value of 0 if 63 | // a conversion can not be made. 64 | func Int(from interface{}) (int, error) { 65 | return converter.Int(from) 66 | } 67 | 68 | // Int8 will convert the given value to a int8, returns the default value of 0 69 | // if a conversion can not be made. 70 | func Int8(from interface{}) (int8, error) { 71 | return converter.Int8(from) 72 | } 73 | 74 | // Int16 will convert the given value to a int16, returns the default value of 0 75 | // if a conversion can not be made. 76 | func Int16(from interface{}) (int16, error) { 77 | return converter.Int16(from) 78 | } 79 | 80 | // Int32 will convert the given value to a int32, returns the default value of 0 81 | // if a conversion can not be made. 82 | func Int32(from interface{}) (int32, error) { 83 | return converter.Int32(from) 84 | } 85 | 86 | // Int64 will convert the given value to a int64, returns the default value of 0 87 | // if a conversion can not be made. 88 | func Int64(from interface{}) (int64, error) { 89 | return converter.Int64(from) 90 | } 91 | 92 | // Uint will convert the given value to a uint, returns the default value of 0 93 | // if a conversion can not be made. 94 | func Uint(from interface{}) (uint, error) { 95 | return converter.Uint(from) 96 | } 97 | 98 | // Uint8 will convert the given value to a uint8, returns the default value of 0 99 | // if a conversion can not be made. 100 | func Uint8(from interface{}) (uint8, error) { 101 | return converter.Uint8(from) 102 | } 103 | 104 | // Uint16 will convert the given value to a uint16, returns the default value of 105 | // 0 if a conversion can not be made. 106 | func Uint16(from interface{}) (uint16, error) { 107 | return converter.Uint16(from) 108 | } 109 | 110 | // Uint32 will convert the given value to a uint32, returns the default value of 111 | // 0 if a conversion can not be made. 112 | func Uint32(from interface{}) (uint32, error) { 113 | return converter.Uint32(from) 114 | } 115 | 116 | // Uint64 will convert the given value to a uint64, returns the default value of 117 | // 0 if a conversion can not be made. 118 | func Uint64(from interface{}) (uint64, error) { 119 | return converter.Uint64(from) 120 | } 121 | -------------------------------------------------------------------------------- /conv_test.go: -------------------------------------------------------------------------------- 1 | package conv 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cstockton/go-conv/internal/testconv" 7 | ) 8 | 9 | func TestConv(t *testing.T) { 10 | testconv.RunBoolTests(t, Bool) 11 | testconv.RunDurationTests(t, Duration) 12 | testconv.RunFloat32Tests(t, Float32) 13 | testconv.RunFloat64Tests(t, Float64) 14 | testconv.RunInferTests(t, Infer) 15 | testconv.RunIntTests(t, Int) 16 | testconv.RunInt8Tests(t, Int8) 17 | testconv.RunInt16Tests(t, Int16) 18 | testconv.RunInt32Tests(t, Int32) 19 | testconv.RunInt64Tests(t, Int64) 20 | testconv.RunStringTests(t, String) 21 | testconv.RunTimeTests(t, Time) 22 | testconv.RunUintTests(t, Uint) 23 | testconv.RunUint8Tests(t, Uint8) 24 | testconv.RunUint16Tests(t, Uint16) 25 | testconv.RunUint32Tests(t, Uint32) 26 | testconv.RunUint64Tests(t, Uint64) 27 | 28 | // Generates README.md based on testdata/README.md.tpl using examples from 29 | // the example_test.go file. 30 | testconv.RunReadmeTest(t, `example_test.go`, `conv_test.go`) 31 | } 32 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package conv_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "time" 7 | 8 | conv "github.com/cstockton/go-conv" 9 | ) 10 | 11 | // All conversion functions accept any type of value for conversion, if unable 12 | // to find a reasonable conversion path they will return the target types zero 13 | // value and an error. 14 | func Example() { 15 | 16 | // The zero value and a non-nil error is returned on failure. 17 | fmt.Println(conv.Int("Foo")) 18 | 19 | // Conversions are allowed as long as the underlying type is convertable, for 20 | // example: 21 | type MyString string 22 | fmt.Println(conv.Int(MyString("42"))) // 42, nil 23 | 24 | // Pointers will be dereferenced when appropriate. 25 | str := "42" 26 | fmt.Println(conv.Int(&str)) // 42, nil 27 | 28 | // You may infer values from the base type of a pointer, giving you one 29 | // function signature for all conversions. This may be convenient when the 30 | // types are not known until runtime and reflection must be used. 31 | var val int 32 | err := conv.Infer(&val, `42`) 33 | fmt.Println(val, err) // 42, nil 34 | 35 | // Output: 36 | // 0 cannot convert "Foo" (type string) to int 37 | // 42 38 | // 42 39 | // 42 40 | } 41 | 42 | // Bool conversion supports all the paths provided by the standard libraries 43 | // strconv.ParseBool when converting from a string, all other conversions are 44 | // simply true when not the types zero value. As a special case zero length map 45 | // and slice types are also false, even if initialized. 46 | func ExampleBool() { 47 | 48 | // Bool conversion from other bool values will be returned without 49 | // modification. 50 | fmt.Println(conv.Bool(true)) 51 | fmt.Println(conv.Bool(false)) 52 | 53 | // Bool conversion from strings consider the following values true: 54 | // "t", "T", "true", "True", "TRUE", 55 | // "y", "Y", "yes", "Yes", "YES", "1" 56 | // 57 | // It considers the following values false: 58 | // "f", "F", "false", "False", "FALSE", 59 | // "n", "N", "no", "No", "NO", "0" 60 | fmt.Println(conv.Bool("T")) 61 | fmt.Println(conv.Bool("False")) 62 | 63 | // Bool conversion from other supported types will return true unless it is 64 | // the zero value for the given type. 65 | fmt.Println(conv.Bool(int64(123))) 66 | fmt.Println(conv.Bool(int64(0))) 67 | fmt.Println(conv.Bool(time.Duration(123))) 68 | fmt.Println(conv.Bool(time.Duration(0))) 69 | fmt.Println(conv.Bool(time.Now())) 70 | fmt.Println(conv.Bool(time.Time{})) 71 | 72 | // All other types will return false. 73 | fmt.Println(conv.Bool(struct{ string }{""})) 74 | 75 | // Output: 76 | // true 77 | // false 78 | // true 79 | // false 80 | // true 81 | // false 82 | // true 83 | // false 84 | // true 85 | // false 86 | // false cannot convert struct { string }{string:""} (type struct { string }) to bool 87 | } 88 | 89 | // Duration conversion supports all the paths provided by the standard libraries 90 | // time.ParseDuration when converting from strings, with a couple enhancements 91 | // outlined below. 92 | func ExampleDuration() { 93 | 94 | // Duration conversion from strings will first attempt to parse as a Go 95 | // duration value using ParseDuration, then fall back to numeric conventions. 96 | fmt.Println(conv.Duration("1h1m100ms")) // 1h1m0.1s 97 | fmt.Println(conv.Duration("3660100000000")) // 1h1m0.1s 98 | 99 | // Numeric conversions directly convert to time.Duration nanoseconds. 100 | fmt.Println(conv.Duration(3660100000000)) // 1h1m0.1s 101 | 102 | // Floats deviate from the numeric conversion rules, instead 103 | // separating the integer and fractional portions into seconds. 104 | fmt.Println(conv.Duration("3660.10")) // 1h1m0.1s 105 | fmt.Println(conv.Duration(float64(3660.10))) // 1h1m0.1s 106 | 107 | // Complex numbers are Float conversions using the real number. 108 | fmt.Println(conv.Duration(complex(3660.10, 0))) // 1h1m0.1s 109 | 110 | // Duration conversion from time.Duration and any numerical type will be 111 | // converted using a standard Go conversion. This includes strings 112 | fmt.Println(conv.Duration(time.Nanosecond)) // 1s 113 | fmt.Println(conv.Duration(byte(1))) // 1ns 114 | // Output: 115 | // 1h1m0.1s 116 | // 1h1m0.1s 117 | // 1h1m0.1s 118 | // 1h1m0.1s 119 | // 1h1m0.1s 120 | // 1h1m0.1s 121 | // 1ns 122 | // 1ns 123 | } 124 | 125 | // Float64 conversion from other float values of an identical type will be 126 | // returned without modification. Float64 from other types follow the general 127 | // numeric rules. 128 | func ExampleFloat64() { 129 | 130 | fmt.Println(conv.Float64(float64(123.456))) // 123.456 131 | fmt.Println(conv.Float64("-123.456")) // -123.456 132 | fmt.Println(conv.Float64("1.7976931348623157e+308")) 133 | // Output: 134 | // 123.456 135 | // -123.456 136 | // 1.7976931348623157e+308 137 | } 138 | 139 | // Infer will perform conversion by inferring the conversion operation from 140 | // a pointer to a supported T of the `into` param. Since the value is assigned 141 | // directly only a error value is returned, meaning no type assertions needed. 142 | func ExampleInfer() { 143 | 144 | // Infer requires a pointer to all types. 145 | var into int 146 | if err := conv.Infer(into, `42`); err != nil { 147 | fmt.Println(err) 148 | } 149 | if err := conv.Infer(&into, `42`); err == nil { 150 | fmt.Println(into) 151 | } 152 | 153 | // Same as above but using new() 154 | truth := new(bool) 155 | if err := conv.Infer(truth, `TRUE`); err != nil { 156 | fmt.Println("Failed!") 157 | } 158 | // Output: 159 | // cannot infer conversion for unchangeable 0 (type int) 160 | // 42 161 | } 162 | 163 | // Int conversions follow the the general numeric rules. 164 | func ExampleInt() { 165 | 166 | fmt.Println(conv.Uint("123.456")) // 123 167 | fmt.Println(conv.Uint("-123.456")) // 0 168 | fmt.Println(conv.Uint8(uint64(math.MaxUint64))) // 255 169 | // Output: 170 | // 123 171 | // 0 172 | // 255 173 | } 174 | 175 | // String conversion from any values outside the cases below will simply be the 176 | // result of calling fmt.Sprintf("%v", value), meaning it can not fail. An error 177 | // is still provided and you should check it to be future proof. 178 | func ExampleString() { 179 | 180 | // String conversion from other string values will be returned without 181 | // modification. 182 | fmt.Println(conv.String("Foo")) 183 | 184 | // As a special case []byte will also be returned after a Go string conversion 185 | // is applied. 186 | fmt.Println(conv.String([]byte("Foo"))) 187 | 188 | // String conversion from types that do not have a valid conversion path will 189 | // still have sane string conversion for troubleshooting. 190 | fmt.Println(conv.String(struct{ msg string }{"Foo"})) 191 | // Output: 192 | // Foo 193 | // Foo 194 | // {Foo} 195 | } 196 | 197 | // Time conversion from other time values will be returned without modification. 198 | func ExampleTime() { 199 | 200 | // Time conversion from other time.Time values will be returned without 201 | // modification. 202 | fmt.Println(`Times:`) 203 | fmt.Println(conv.Time(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC))) 204 | 205 | // Time conversion from strings will be passed through time.Parse using a 206 | // variety of formats. Strings that could not be parsed along with all other 207 | // values will return an empty time.Time{} struct. 208 | fmt.Println(`Strings:`) 209 | formats := []string{ 210 | `Mon, 02 Jan 2006 15:04:05`, 211 | `Mon, 02 Jan 2006 15:04:05 UTC`, 212 | `Mon, 2 Jan 2006 15:04:05`, 213 | `Mon, 2 Jan 2006 15:04:05 UTC`, 214 | `02 Jan 2006 15:04 UTC`, 215 | `2 Jan 2006 15:04:05`, 216 | `2 Jan 2006 15:04:05 UTC`, 217 | } 218 | for _, format := range formats { 219 | t, err := conv.Time(format) 220 | if err != nil { 221 | fmt.Println(`Conversion error: `, err) 222 | } 223 | fmt.Printf("%v <-- (%v)\n", t, format) 224 | } 225 | 226 | // Time conversion from types that do not have a valid conversion path will 227 | // return the zero value and an error. 228 | fmt.Println(`Errors:`) 229 | fmt.Println(conv.Time(1)) // cannot convert 1 (type int) to time.Time 230 | fmt.Println(conv.Time(true)) // cannot convert true (type bool) to time.Time 231 | // Output: 232 | // Times: 233 | // 2006-01-02 15:04:05 +0000 UTC 234 | // Strings: 235 | // 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 02 Jan 2006 15:04:05) 236 | // 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 02 Jan 2006 15:04:05 UTC) 237 | // 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 2 Jan 2006 15:04:05) 238 | // 2006-01-02 15:04:05 +0000 UTC <-- (Mon, 2 Jan 2006 15:04:05 UTC) 239 | // 2006-01-02 15:04:00 +0000 UTC <-- (02 Jan 2006 15:04 UTC) 240 | // 2006-01-02 15:04:05 +0000 UTC <-- (2 Jan 2006 15:04:05) 241 | // 2006-01-02 15:04:05 +0000 UTC <-- (2 Jan 2006 15:04:05 UTC) 242 | // Errors: 243 | // 0001-01-01 00:00:00 +0000 UTC cannot convert 1 (type int) to time.Time 244 | // 0001-01-01 00:00:00 +0000 UTC cannot convert true (type bool) to time.Time 245 | } 246 | 247 | // Uint conversions follow the the general numeric rules. 248 | func ExampleUint() { 249 | 250 | fmt.Println(conv.Uint("123.456")) // 123 251 | fmt.Println(conv.Uint("-123.456")) // 0 252 | fmt.Println(conv.Uint8(uint64(math.MaxUint64))) // 255 253 | // Output: 254 | // 123 255 | // 0 256 | // 255 257 | } 258 | 259 | // Numeric conversion from other numeric values of an identical type will be 260 | // returned without modification. Numeric conversions deviate slightly from Go 261 | // when dealing with under/over flow. When performing a conversion operation 262 | // that would overflow, we instead assign the maximum value for the target type. 263 | // Similarly, conversions that would underflow are assigned the minimun value 264 | // for that type, meaning unsigned integers are given zero values instead of 265 | // spilling into large positive integers. 266 | func Example_numerics() { 267 | 268 | // For more natural Float -> Integer when the underlying value is a string. 269 | // Conversion functions will always try to parse the value as the target type 270 | // first. If parsing fails float parsing with truncation will be attempted. 271 | fmt.Println(conv.Int("-123.456")) // -123 272 | 273 | // This does not apply for unsigned integers if the value is negative. Instead 274 | // performing a more intuitive (to the human) truncation to zero. 275 | fmt.Println(conv.Uint("-123.456")) // 0 276 | // Output: 277 | // -123 278 | // 0 279 | } 280 | 281 | // In short, panics should not occur within this library under any circumstance. 282 | // This obviously excludes any oddities that may surface when the runtime is not 283 | // in a healthy state, i.e. uderlying system instability, memory exhaustion. If 284 | // you are able to create a reproducible panic please file a bug report. 285 | func Example_panics() { 286 | 287 | // The zero value for the target type is always returned. 288 | fmt.Println(conv.Bool(nil)) 289 | fmt.Println(conv.Bool([][]int{})) 290 | fmt.Println(conv.Bool((chan string)(nil))) 291 | fmt.Println(conv.Bool((*interface{})(nil))) 292 | fmt.Println(conv.Bool((*interface{})(nil))) 293 | fmt.Println(conv.Bool((**interface{})(nil))) 294 | // Output: 295 | // false cannot convert (type ) to bool 296 | // false 297 | // false 298 | // false cannot convert (*interface {})(nil) (type *interface {}) to bool 299 | // false cannot convert (*interface {})(nil) (type *interface {}) to bool 300 | // false cannot convert (**interface {})(nil) (type **interface {}) to bool 301 | } 302 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cstockton/go-conv 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /internal/convert/convert.go: -------------------------------------------------------------------------------- 1 | // Package convert contains common conversion interfaces. 2 | package convert 3 | 4 | import "time" 5 | 6 | // Converter supports conversion across Go types. 7 | type Converter interface { 8 | 9 | // Bool returns the bool representation from the given interface value. 10 | // Returns the default value of false and an error on failure. 11 | Bool(from interface{}) (to bool, err error) 12 | 13 | // Duration returns the time.Duration representation from the given 14 | // interface{} value. Returns the default value of 0 and an error on failure. 15 | Duration(from interface{}) (to time.Duration, err error) 16 | 17 | // Float32 returns the float32 representation from the given empty interface 18 | // value. Returns the default value of 0 and an error on failure. 19 | Float32(from interface{}) (to float32, err error) 20 | 21 | // Float64 returns the float64 representation from the given interface 22 | // value. Returns the default value of 0 and an error on failure. 23 | Float64(from interface{}) (to float64, err error) 24 | 25 | // Infer will perform conversion by inferring the conversion operation from 26 | // a pointer to a supported T of the `into` param. 27 | Infer(into, from interface{}) error 28 | 29 | // Int returns the int representation from the given empty interface 30 | // value. Returns the default value of 0 and an error on failure. 31 | Int(from interface{}) (to int, err error) 32 | 33 | // Int8 returns the int8 representation from the given empty interface 34 | // value. Returns the default value of 0 and an error on failure. 35 | Int8(from interface{}) (to int8, err error) 36 | 37 | // Int16 returns the int16 representation from the given empty interface 38 | // value. Returns the default value of 0 and an error on failure. 39 | Int16(from interface{}) (to int16, err error) 40 | 41 | // Int32 returns the int32 representation from the given empty interface 42 | // value. Returns the default value of 0 and an error on failure. 43 | Int32(from interface{}) (to int32, err error) 44 | 45 | // Int64 returns the int64 representation from the given interface 46 | // value. Returns the default value of 0 and an error on failure. 47 | Int64(from interface{}) (to int64, err error) 48 | 49 | // String returns the string representation from the given interface 50 | // value and can not fail. An error is provided only for API cohesion. 51 | String(from interface{}) (to string, err error) 52 | 53 | // Time returns the time.Time{} representation from the given interface 54 | // value. Returns an empty time.Time struct and an error on failure. 55 | Time(from interface{}) (to time.Time, err error) 56 | 57 | // Uint returns the uint representation from the given empty interface 58 | // value. Returns the default value of 0 and an error on failure. 59 | Uint(from interface{}) (to uint, err error) 60 | 61 | // Uint8 returns the uint8 representation from the given empty interface 62 | // value. Returns the default value of 0 and an error on failure. 63 | Uint8(from interface{}) (to uint8, err error) 64 | 65 | // Uint16 returns the uint16 representation from the given empty interface 66 | // value. Returns the default value of 0 and an error on failure. 67 | Uint16(from interface{}) (to uint16, err error) 68 | 69 | // Uint32 returns the uint32 representation from the given empty interface 70 | // value. Returns the default value of 0 and an error on failure. 71 | Uint32(from interface{}) (to uint32, err error) 72 | 73 | // Uint64 returns the uint64 representation from the given interface 74 | // value. Returns the default value of 0 and an error on failure. 75 | Uint64(from interface{}) (to uint64, err error) 76 | } 77 | -------------------------------------------------------------------------------- /internal/convert/fnconv.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | func WithError(err error, fn FnConv) FnConv { 9 | return WithErrorAfter(err, 0, fn) 10 | } 11 | 12 | func WithErrorAfter(err error, after int, fn FnConv) FnConv { 13 | var n int64 14 | return func(into, from interface{}) error { 15 | if atomic.AddInt64(&n, 1) > int64(after) { 16 | return err 17 | } 18 | n++ 19 | return nil 20 | } 21 | } 22 | 23 | type FnConv func(into, from interface{}) error 24 | 25 | func (fn FnConv) Bool(from interface{}) (out bool, err error) { 26 | err = fn(&out, from) 27 | return 28 | } 29 | 30 | func (fn FnConv) Duration(from interface{}) (out time.Duration, err error) { 31 | err = fn(&out, from) 32 | return 33 | } 34 | 35 | func (fn FnConv) Float32(from interface{}) (out float32, err error) { 36 | err = fn(&out, from) 37 | return 38 | } 39 | 40 | func (fn FnConv) Float64(from interface{}) (out float64, err error) { 41 | err = fn(&out, from) 42 | return 43 | } 44 | 45 | func (fn FnConv) Infer(into, from interface{}) (err error) { 46 | err = fn(into, from) 47 | return 48 | } 49 | 50 | func (fn FnConv) Int(from interface{}) (out int, err error) { 51 | err = fn(&out, from) 52 | return 53 | } 54 | 55 | func (fn FnConv) Int8(from interface{}) (out int8, err error) { 56 | err = fn(&out, from) 57 | return 58 | } 59 | 60 | func (fn FnConv) Int16(from interface{}) (out int16, err error) { 61 | err = fn(&out, from) 62 | return 63 | } 64 | 65 | func (fn FnConv) Int32(from interface{}) (out int32, err error) { 66 | err = fn(&out, from) 67 | return 68 | } 69 | 70 | func (fn FnConv) Int64(from interface{}) (out int64, err error) { 71 | err = fn(&out, from) 72 | return 73 | } 74 | 75 | func (fn FnConv) Map(into, from interface{}) (err error) { 76 | err = fn(into, from) 77 | return 78 | } 79 | 80 | func (fn FnConv) Slice(into, from interface{}) (err error) { 81 | err = fn(into, from) 82 | return 83 | } 84 | 85 | func (fn FnConv) String(from interface{}) (out string, err error) { 86 | err = fn(&out, from) 87 | return 88 | } 89 | 90 | func (fn FnConv) Struct(into, from interface{}) (err error) { 91 | err = fn(into, from) 92 | return 93 | } 94 | 95 | func (fn FnConv) Time(from interface{}) (out time.Time, err error) { 96 | err = fn(&out, from) 97 | return 98 | } 99 | 100 | func (fn FnConv) Uint(from interface{}) (out uint, err error) { 101 | err = fn(&out, from) 102 | return 103 | } 104 | 105 | func (fn FnConv) Uint8(from interface{}) (out uint8, err error) { 106 | err = fn(&out, from) 107 | return 108 | } 109 | 110 | func (fn FnConv) Uint16(from interface{}) (out uint16, err error) { 111 | err = fn(&out, from) 112 | return 113 | } 114 | 115 | func (fn FnConv) Uint32(from interface{}) (out uint32, err error) { 116 | err = fn(&out, from) 117 | return 118 | } 119 | 120 | func (fn FnConv) Uint64(from interface{}) (out uint64, err error) { 121 | err = fn(&out, from) 122 | return 123 | } 124 | -------------------------------------------------------------------------------- /internal/convert/fnconv_test.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSmoke(t *testing.T) { 8 | var c interface{} = FnConv(func(into, from interface{}) error { return nil }) 9 | _ = c.(Converter) 10 | } 11 | -------------------------------------------------------------------------------- /internal/generated/generated.go: -------------------------------------------------------------------------------- 1 | package generated 2 | 3 | import "time" 4 | 5 | var ( 6 | expBoolVal1 = true 7 | expBoolVal2 = false 8 | expDurationVal1 = time.Duration(10) // Duration("10ns") 9 | expDurationVal2 = time.Duration(20000) // Duration("20µs") 10 | expDurationVal3 = time.Duration(30000000) // Duration("30ms") 11 | expFloat32Val1 = float32(1.2) 12 | expFloat32Val2 = float32(3.45) 13 | expFloat32Val3 = float32(6.78) 14 | expFloat64Val1 = float64(1.2) 15 | expFloat64Val2 = float64(3.45) 16 | expFloat64Val3 = float64(6.78) 17 | expIntVal1 = int(12) 18 | expIntVal2 = int(34) 19 | expIntVal3 = int(56) 20 | expInt16Val1 = int16(12) 21 | expInt16Val2 = int16(34) 22 | expInt16Val3 = int16(56) 23 | expInt32Val1 = int32(12) 24 | expInt32Val2 = int32(34) 25 | expInt32Val3 = int32(56) 26 | expInt64Val1 = int64(12) 27 | expInt64Val2 = int64(34) 28 | expInt64Val3 = int64(56) 29 | expInt8Val1 = int8(12) 30 | expInt8Val2 = int8(34) 31 | expInt8Val3 = int8(56) 32 | expStringVal1 = "k1" 33 | expStringVal2 = "K2" 34 | expStringVal3 = "03" 35 | expUintVal1 = uint(12) 36 | expTimeVal1 = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC) 37 | expTimeVal2 = time.Date(2006, 1, 2, 16, 4, 5, 0, time.UTC) 38 | expTimeVal3 = time.Date(2006, 1, 2, 17, 4, 5, 0, time.UTC) 39 | // expTimeVal1 = mustTime("2006-01-02T15:04:05Z") 40 | // expTimeVal2 = mustTime("2006-01-02T16:04:05Z") 41 | // expTimeVal3 = mustTime("2006-01-02T17:04:05Z") 42 | expUintVal2 = uint(34) 43 | expUintVal3 = uint(56) 44 | expUint16Val1 = uint16(12) 45 | expUint16Val2 = uint16(34) 46 | expUint16Val3 = uint16(56) 47 | expUint32Val1 = uint32(12) 48 | expUint32Val2 = uint32(34) 49 | expUint32Val3 = uint32(56) 50 | expUint64Val1 = uint64(12) 51 | expUint64Val2 = uint64(34) 52 | expUint64Val3 = uint64(56) 53 | expUint8Val1 = uint8(12) 54 | expUint8Val2 = uint8(34) 55 | expUint8Val3 = uint8(56) 56 | 57 | strToNumeric = [42]struct { 58 | from string 59 | to int64 60 | }{ 61 | {"0", 0}, 62 | {"-0", 0}, 63 | {"1", 1}, 64 | {"-1", -1}, 65 | {"12", 12}, 66 | {"-12", -12}, 67 | {"123", 123}, 68 | {"-123", -123}, 69 | {"1234", 1234}, 70 | {"-1234", -1234}, 71 | {"12345", 12345}, 72 | {"-12345", -12345}, 73 | {"123456", 123456}, 74 | {"-123456", -123456}, 75 | {"1234567", 1234567}, 76 | {"-1234567", -1234567}, 77 | {"12345678", 12345678}, 78 | {"-12345678", -12345678}, 79 | {"123456789", 123456789}, 80 | {"-123456789", -123456789}, 81 | {"1234567890", 1234567890}, 82 | {"-1234567890", -1234567890}, 83 | {"12345678901", 12345678901}, 84 | {"-12345678901", -12345678901}, 85 | {"123456789012", 123456789012}, 86 | {"-123456789012", -123456789012}, 87 | {"1234567890123", 1234567890123}, 88 | {"-1234567890123", -1234567890123}, 89 | {"12345678901234", 12345678901234}, 90 | {"-12345678901234", -12345678901234}, 91 | {"123456789012345", 123456789012345}, 92 | {"-123456789012345", -123456789012345}, 93 | {"1234567890123456", 1234567890123456}, 94 | {"-1234567890123456", -1234567890123456}, 95 | {"12345678901234567", 12345678901234567}, 96 | {"-12345678901234567", -12345678901234567}, 97 | {"123456789012345678", 123456789012345678}, 98 | {"-123456789012345678", -123456789012345678}, 99 | {"1234567890123456789", 1234567890123456789}, 100 | {"-1234567890123456789", -1234567890123456789}, 101 | {"9223372036854775807", 1<<63 - 1}, 102 | {"-9223372036854775808", -1 << 63}, 103 | } 104 | ) 105 | -------------------------------------------------------------------------------- /internal/generated/slicetests.go: -------------------------------------------------------------------------------- 1 | package generated 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // SliceTest represents a single Slice conversion test. 8 | type SliceTest struct { 9 | Into interface{} 10 | From []string 11 | Exp interface{} 12 | } 13 | 14 | // SliceTests is a slice of all Slice conversion tests. 15 | func NewSliceTests() (tests []SliceTest) { 16 | tests = []SliceTest{ 17 | {new([]bool), 18 | []string{"Yes", "FALSE"}, 19 | []bool{expBoolVal1, expBoolVal2}}, 20 | {new([]*bool), 21 | []string{"Yes", "FALSE"}, 22 | []*bool{&expBoolVal1, &expBoolVal2}}, 23 | {new([]time.Duration), 24 | []string{"10ns", "20µs", "30ms"}, 25 | []time.Duration{expDurationVal1, expDurationVal2, expDurationVal3}}, 26 | {new([]*time.Duration), 27 | []string{"10ns", "20µs", "30ms"}, 28 | []*time.Duration{&expDurationVal1, &expDurationVal2, &expDurationVal3}}, 29 | {new([]float32), 30 | []string{"1.2", "3.45", "6.78"}, 31 | []float32{expFloat32Val1, expFloat32Val2, expFloat32Val3}}, 32 | {new([]*float32), 33 | []string{"1.2", "3.45", "6.78"}, 34 | []*float32{&expFloat32Val1, &expFloat32Val2, &expFloat32Val3}}, 35 | {new([]float64), 36 | []string{"1.2", "3.45", "6.78"}, 37 | []float64{expFloat64Val1, expFloat64Val2, expFloat64Val3}}, 38 | {new([]*float64), 39 | []string{"1.2", "3.45", "6.78"}, 40 | []*float64{&expFloat64Val1, &expFloat64Val2, &expFloat64Val3}}, 41 | {new([]int), 42 | []string{"12", "34", "56"}, 43 | []int{expIntVal1, expIntVal2, expIntVal3}}, 44 | {new([]*int), 45 | []string{"12", "34", "56"}, 46 | []*int{&expIntVal1, &expIntVal2, &expIntVal3}}, 47 | {new([]int16), 48 | []string{"12", "34", "56"}, 49 | []int16{expInt16Val1, expInt16Val2, expInt16Val3}}, 50 | {new([]*int16), 51 | []string{"12", "34", "56"}, 52 | []*int16{&expInt16Val1, &expInt16Val2, &expInt16Val3}}, 53 | {new([]int32), 54 | []string{"12", "34", "56"}, 55 | []int32{expInt32Val1, expInt32Val2, expInt32Val3}}, 56 | {new([]*int32), 57 | []string{"12", "34", "56"}, 58 | []*int32{&expInt32Val1, &expInt32Val2, &expInt32Val3}}, 59 | {new([]int64), 60 | []string{"12", "34", "56"}, 61 | []int64{expInt64Val1, expInt64Val2, expInt64Val3}}, 62 | {new([]*int64), 63 | []string{"12", "34", "56"}, 64 | []*int64{&expInt64Val1, &expInt64Val2, &expInt64Val3}}, 65 | {new([]int8), 66 | []string{"12", "34", "56"}, 67 | []int8{expInt8Val1, expInt8Val2, expInt8Val3}}, 68 | {new([]*int8), 69 | []string{"12", "34", "56"}, 70 | []*int8{&expInt8Val1, &expInt8Val2, &expInt8Val3}}, 71 | {new([]string), 72 | []string{"k1", "K2", "03"}, 73 | []string{expStringVal1, expStringVal2, expStringVal3}}, 74 | {new([]*string), 75 | []string{"k1", "K2", "03"}, 76 | []*string{&expStringVal1, &expStringVal2, &expStringVal3}}, 77 | {new([]time.Time), 78 | []string{ 79 | "2 Jan 2006 15:04:05 -0700 (UTC)", 80 | "Mon, 2 Jan 16:04:05 UTC 2006", 81 | "Mon, 02 Jan 2006 17:04:05 (UTC)"}, 82 | []time.Time{expTimeVal1, expTimeVal2, expTimeVal3}}, 83 | {new([]*time.Time), 84 | []string{ 85 | "2 Jan 2006 15:04:05 -0700 (UTC)", 86 | "Mon, 2 Jan 16:04:05 UTC 2006", 87 | "Mon, 02 Jan 2006 17:04:05 (UTC)"}, 88 | []*time.Time{&expTimeVal1, &expTimeVal2, &expTimeVal3}}, 89 | {new([]uint), 90 | []string{"12", "34", "56"}, 91 | []uint{expUintVal1, expUintVal2, expUintVal3}}, 92 | {new([]*uint), 93 | []string{"12", "34", "56"}, 94 | []*uint{&expUintVal1, &expUintVal2, &expUintVal3}}, 95 | {new([]uint16), 96 | []string{"12", "34", "56"}, 97 | []uint16{expUint16Val1, expUint16Val2, expUint16Val3}}, 98 | {new([]*uint16), 99 | []string{"12", "34", "56"}, 100 | []*uint16{&expUint16Val1, &expUint16Val2, &expUint16Val3}}, 101 | {new([]uint32), 102 | []string{"12", "34", "56"}, 103 | []uint32{expUint32Val1, expUint32Val2, expUint32Val3}}, 104 | {new([]*uint32), 105 | []string{"12", "34", "56"}, 106 | []*uint32{&expUint32Val1, &expUint32Val2, &expUint32Val3}}, 107 | {new([]uint64), 108 | []string{"12", "34", "56"}, 109 | []uint64{expUint64Val1, expUint64Val2, expUint64Val3}}, 110 | {new([]*uint64), 111 | []string{"12", "34", "56"}, 112 | []*uint64{&expUint64Val1, &expUint64Val2, &expUint64Val3}}, 113 | {new([]uint8), 114 | []string{"12", "34", "56"}, 115 | []uint8{expUint8Val1, expUint8Val2, expUint8Val3}}, 116 | {new([]*uint8), 117 | []string{"12", "34", "56"}, 118 | []*uint8{&expUint8Val1, &expUint8Val2, &expUint8Val3}}, 119 | } 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /internal/refconv/bool.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/cstockton/go-conv/internal/refutil" 9 | ) 10 | 11 | type boolConverter interface { 12 | Bool() (bool, error) 13 | } 14 | 15 | // Bool attempts to convert the given value to bool, returns the zero value 16 | // and an error on failure. 17 | func (c Conv) Bool(from interface{}) (bool, error) { 18 | if T, ok := from.(string); ok { 19 | return c.convStrToBool(T) 20 | } else if T, ok := from.(bool); ok { 21 | return T, nil 22 | } else if c, ok := from.(boolConverter); ok { 23 | return c.Bool() 24 | } 25 | 26 | value := refutil.IndirectVal(reflect.ValueOf(from)) 27 | kind := value.Kind() 28 | switch { 29 | case reflect.String == kind: 30 | return c.convStrToBool(value.String()) 31 | case refutil.IsKindNumeric(kind): 32 | if parsed, ok := c.convNumToBool(kind, value); ok { 33 | return parsed, nil 34 | } 35 | case reflect.Bool == kind: 36 | return value.Bool(), nil 37 | case refutil.IsKindLength(kind): 38 | return value.Len() > 0, nil 39 | case reflect.Struct == kind && value.CanInterface(): 40 | v := value.Interface() 41 | if t, ok := v.(time.Time); ok { 42 | return emptyTime != t, nil 43 | } 44 | } 45 | return false, newConvErr(from, "bool") 46 | } 47 | 48 | func (c Conv) convStrToBool(v string) (bool, error) { 49 | // @TODO Need to find a clean way to expose the truth list to be modified by 50 | // API to allow INTL. 51 | if 1 > len(v) || len(v) > 5 { 52 | return false, fmt.Errorf("cannot parse string with len %d as bool", len(v)) 53 | } 54 | 55 | // @TODO lut 56 | switch v { 57 | case "1", "t", "T", "true", "True", "TRUE", "y", "Y", "yes", "Yes", "YES": 58 | return true, nil 59 | case "0", "f", "F", "false", "False", "FALSE", "n", "N", "no", "No", "NO": 60 | return false, nil 61 | } 62 | return false, fmt.Errorf("cannot parse %#v (type string) as bool", v) 63 | } 64 | -------------------------------------------------------------------------------- /internal/refconv/float.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "strconv" 7 | 8 | "github.com/cstockton/go-conv/internal/refutil" 9 | ) 10 | 11 | func (c Conv) convStrToFloat64(v string) (float64, bool) { 12 | if parsed, perr := strconv.ParseFloat(v, 64); perr == nil { 13 | return parsed, true 14 | } 15 | if parsed, perr := c.Bool(v); perr == nil { 16 | if parsed { 17 | return 1, true 18 | } 19 | return 0, true 20 | } 21 | return 0, false 22 | } 23 | 24 | type floatConverter interface { 25 | Float64() (float64, error) 26 | } 27 | 28 | // Float64 attempts to convert the given value to float64, returns the zero 29 | // value and an error on failure. 30 | func (c Conv) Float64(from interface{}) (float64, error) { 31 | if T, ok := from.(float64); ok { 32 | return T, nil 33 | } 34 | if c, ok := from.(floatConverter); ok { 35 | return c.Float64() 36 | } 37 | 38 | value := refutil.IndirectVal(reflect.ValueOf(from)) 39 | kind := value.Kind() 40 | switch { 41 | case reflect.String == kind: 42 | if parsed, ok := c.convStrToFloat64(value.String()); ok { 43 | return parsed, nil 44 | } 45 | case refutil.IsKindInt(kind): 46 | return float64(value.Int()), nil 47 | case refutil.IsKindUint(kind): 48 | return float64(value.Uint()), nil 49 | case refutil.IsKindFloat(kind): 50 | return value.Float(), nil 51 | case refutil.IsKindComplex(kind): 52 | return real(value.Complex()), nil 53 | case reflect.Bool == kind: 54 | if value.Bool() { 55 | return 1, nil 56 | } 57 | return 0, nil 58 | case refutil.IsKindLength(kind): 59 | return float64(value.Len()), nil 60 | } 61 | return 0, newConvErr(from, "float64") 62 | } 63 | 64 | // Float32 attempts to convert the given value to Float32, returns the zero 65 | // value and an error on failure. 66 | func (c Conv) Float32(from interface{}) (float32, error) { 67 | if T, ok := from.(float32); ok { 68 | return T, nil 69 | } 70 | 71 | if res, err := c.Float64(from); err == nil { 72 | if res > math.MaxFloat32 { 73 | res = math.MaxFloat32 74 | } else if res < -math.MaxFloat32 { 75 | res = -math.MaxFloat32 76 | } 77 | return float32(res), err 78 | } 79 | return 0, newConvErr(from, "float32") 80 | } 81 | -------------------------------------------------------------------------------- /internal/refconv/infer.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Infer will perform conversion by inferring the conversion operation from 9 | // the T of `into`. 10 | func (c Conv) Infer(into, from interface{}) error { 11 | var value reflect.Value 12 | switch into := into.(type) { 13 | case reflect.Value: 14 | value = into 15 | default: 16 | value = reflect.ValueOf(into) 17 | } 18 | 19 | if !value.IsValid() { 20 | return fmt.Errorf("%T is not a valid value", into) 21 | } 22 | 23 | if value.Kind() == reflect.Ptr { 24 | value = value.Elem() 25 | } 26 | 27 | if !value.CanSet() { 28 | return fmt.Errorf(`cannot infer conversion for unchangeable %v (type %[1]T)`, into) 29 | } 30 | 31 | v, err := c.infer(value, from) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | value.Set(reflect.ValueOf(v)) 37 | return nil 38 | } 39 | 40 | func (c Conv) infer(val reflect.Value, from interface{}) (interface{}, error) { 41 | switch val.Kind() { 42 | case reflect.Bool: 43 | return c.Bool(from) 44 | case reflect.Float32: 45 | return c.Float32(from) 46 | case reflect.Float64: 47 | return c.Float64(from) 48 | case reflect.Int: 49 | return c.Int(from) 50 | case reflect.Int8: 51 | return c.Int8(from) 52 | case reflect.Int16: 53 | return c.Int16(from) 54 | case reflect.Int32: 55 | return c.Int32(from) 56 | case reflect.String: 57 | return c.String(from) 58 | case reflect.Uint: 59 | return c.Uint(from) 60 | case reflect.Uint8: 61 | return c.Uint8(from) 62 | case reflect.Uint16: 63 | return c.Uint16(from) 64 | case reflect.Uint32: 65 | return c.Uint32(from) 66 | case reflect.Uint64: 67 | return c.Uint64(from) 68 | case reflect.Int64: 69 | if val.Type() == typeOfDuration { 70 | return c.Duration(from) 71 | } 72 | return c.Int64(from) 73 | case reflect.Struct: 74 | if val.Type() == typeOfTime { 75 | return c.Time(from) 76 | } 77 | fallthrough 78 | default: 79 | return nil, fmt.Errorf(`cannot infer conversion for %v (type %[1]v)`, val) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /internal/refconv/int.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "strconv" 8 | 9 | "github.com/cstockton/go-conv/internal/refutil" 10 | ) 11 | 12 | var ( 13 | mathMaxInt int64 14 | mathMinInt int64 15 | mathMaxUint uint64 16 | mathIntSize = strconv.IntSize 17 | ) 18 | 19 | func initIntSizes(size int) { 20 | switch size { 21 | case 64: 22 | mathMaxInt = math.MaxInt64 23 | mathMinInt = math.MinInt64 24 | mathMaxUint = math.MaxUint64 25 | case 32: 26 | mathMaxInt = math.MaxInt32 27 | mathMinInt = math.MinInt32 28 | mathMaxUint = math.MaxUint32 29 | } 30 | } 31 | 32 | func init() { 33 | // this is so it can be unit tested. 34 | initIntSizes(mathIntSize) 35 | } 36 | 37 | func (c Conv) convStrToInt64(v string) (int64, error) { 38 | if parsed, err := strconv.ParseInt(v, 10, 0); err == nil { 39 | return parsed, nil 40 | } 41 | if parsed, err := strconv.ParseFloat(v, 64); err == nil { 42 | return int64(parsed), nil 43 | } 44 | if parsed, err := c.convStrToBool(v); err == nil { 45 | if parsed { 46 | return 1, nil 47 | } 48 | return 0, nil 49 | } 50 | return 0, fmt.Errorf("cannot convert %#v (type string) to int64", v) 51 | } 52 | 53 | type intConverter interface { 54 | Int64() (int64, error) 55 | } 56 | 57 | // Int64 attempts to convert the given value to int64, returns the zero value 58 | // and an error on failure. 59 | func (c Conv) Int64(from interface{}) (int64, error) { 60 | if T, ok := from.(string); ok { 61 | return c.convStrToInt64(T) 62 | } else if T, ok := from.(int64); ok { 63 | return T, nil 64 | } 65 | if c, ok := from.(intConverter); ok { 66 | return c.Int64() 67 | } 68 | 69 | value := refutil.IndirectVal(reflect.ValueOf(from)) 70 | kind := value.Kind() 71 | switch { 72 | case reflect.String == kind: 73 | return c.convStrToInt64(value.String()) 74 | case refutil.IsKindInt(kind): 75 | return value.Int(), nil 76 | case refutil.IsKindUint(kind): 77 | val := value.Uint() 78 | if val > math.MaxInt64 { 79 | val = math.MaxInt64 80 | } 81 | return int64(val), nil 82 | case refutil.IsKindFloat(kind): 83 | return int64(value.Float()), nil 84 | case refutil.IsKindComplex(kind): 85 | return int64(real(value.Complex())), nil 86 | case reflect.Bool == kind: 87 | if value.Bool() { 88 | return 1, nil 89 | } 90 | return 0, nil 91 | case refutil.IsKindLength(kind): 92 | return int64(value.Len()), nil 93 | } 94 | return 0, newConvErr(from, "int64") 95 | } 96 | 97 | // Int attempts to convert the given value to int, returns the zero value and an 98 | // error on failure. 99 | func (c Conv) Int(from interface{}) (int, error) { 100 | if T, ok := from.(int); ok { 101 | return T, nil 102 | } 103 | 104 | to64, err := c.Int64(from) 105 | if err != nil { 106 | return 0, newConvErr(from, "int") 107 | } 108 | if to64 > mathMaxInt { 109 | to64 = mathMaxInt // only possible on 32bit arch 110 | } else if to64 < mathMinInt { 111 | to64 = mathMinInt // only possible on 32bit arch 112 | } 113 | return int(to64), nil 114 | } 115 | 116 | // Int8 attempts to convert the given value to int8, returns the zero value and 117 | // an error on failure. 118 | func (c Conv) Int8(from interface{}) (int8, error) { 119 | if T, ok := from.(int8); ok { 120 | return T, nil 121 | } 122 | 123 | to64, err := c.Int64(from) 124 | if err != nil { 125 | return 0, newConvErr(from, "int8") 126 | } 127 | if to64 > math.MaxInt8 { 128 | to64 = math.MaxInt8 129 | } else if to64 < math.MinInt8 { 130 | to64 = math.MinInt8 131 | } 132 | return int8(to64), nil 133 | } 134 | 135 | // Int16 attempts to convert the given value to int16, returns the zero value 136 | // and an error on failure. 137 | func (c Conv) Int16(from interface{}) (int16, error) { 138 | if T, ok := from.(int16); ok { 139 | return T, nil 140 | } 141 | 142 | to64, err := c.Int64(from) 143 | if err != nil { 144 | return 0, newConvErr(from, "int16") 145 | } 146 | if to64 > math.MaxInt16 { 147 | to64 = math.MaxInt16 148 | } else if to64 < math.MinInt16 { 149 | to64 = math.MinInt16 150 | } 151 | return int16(to64), nil 152 | } 153 | 154 | // Int32 attempts to convert the given value to int32, returns the zero value 155 | // and an error on failure. 156 | func (c Conv) Int32(from interface{}) (int32, error) { 157 | if T, ok := from.(int32); ok { 158 | return T, nil 159 | } 160 | 161 | to64, err := c.Int64(from) 162 | if err != nil { 163 | return 0, newConvErr(from, "int32") 164 | } 165 | if to64 > math.MaxInt32 { 166 | to64 = math.MaxInt32 167 | } else if to64 < math.MinInt32 { 168 | to64 = math.MinInt32 169 | } 170 | return int32(to64), nil 171 | } 172 | -------------------------------------------------------------------------------- /internal/refconv/refconv.go: -------------------------------------------------------------------------------- 1 | // Package refconv implements the Converter interface by using the standard 2 | // libraries reflection package. 3 | package refconv 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | // Conv implements the Converter interface by using the reflection package. It 10 | // will never panic, does not require initialization or share state so is safe 11 | // for use by multiple Goroutines. 12 | type Conv struct{} 13 | 14 | func newConvErr(from interface{}, to string) error { 15 | return fmt.Errorf("cannot convert %#v (type %[1]T) to %v", from, to) 16 | } 17 | -------------------------------------------------------------------------------- /internal/refconv/refconv_test.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math" 7 | "os" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/cstockton/go-conv/internal/testconv" 13 | ) 14 | 15 | // Test converter. 16 | func TestMain(m *testing.M) { 17 | chkMathIntSize := mathIntSize 18 | chkMathMaxInt := mathMaxInt 19 | chkMathMinInt := mathMinInt 20 | chkMathMaxUint := mathMaxUint 21 | chkEmptyTime := time.Time{} 22 | 23 | flag.Parse() 24 | res := m.Run() 25 | 26 | // validate our max (u|)int sizes don't get written to on accident. 27 | if chkMathIntSize != mathIntSize { 28 | panic("chkEmptyTime != emptyTime") 29 | } 30 | if chkMathMaxInt != mathMaxInt { 31 | panic("chkMathMaxInt != mathMaxInt") 32 | } 33 | if chkMathMinInt != mathMinInt { 34 | panic("chkMathMinInt != mathMaxInt") 35 | } 36 | if chkMathMaxUint != mathMaxUint { 37 | panic("chkMathMaxUint != mathMaxUint") 38 | } 39 | if chkEmptyTime != emptyTime { 40 | panic("chkEmptyTime != emptyTime") 41 | } 42 | 43 | os.Exit(res) 44 | } 45 | 46 | // Runs common tests to make sure conversion rules are satisfied. 47 | func TestConv(t *testing.T) { 48 | var c Conv 49 | testconv.RunBoolTests(t, c.Bool) 50 | testconv.RunDurationTests(t, c.Duration) 51 | testconv.RunFloat32Tests(t, c.Float32) 52 | testconv.RunFloat64Tests(t, c.Float64) 53 | testconv.RunInferTests(t, c.Infer) 54 | testconv.RunIntTests(t, c.Int) 55 | testconv.RunInt8Tests(t, c.Int8) 56 | testconv.RunInt16Tests(t, c.Int16) 57 | testconv.RunInt32Tests(t, c.Int32) 58 | testconv.RunInt64Tests(t, c.Int64) 59 | testconv.RunStringTests(t, c.String) 60 | testconv.RunTimeTests(t, c.Time) 61 | testconv.RunUintTests(t, c.Uint) 62 | testconv.RunUint8Tests(t, c.Uint8) 63 | testconv.RunUint16Tests(t, c.Uint16) 64 | testconv.RunUint32Tests(t, c.Uint32) 65 | testconv.RunUint64Tests(t, c.Uint64) 66 | } 67 | 68 | func TestConvHelpers(t *testing.T) { 69 | var c Conv 70 | t.Run("convNumToBool", func(t *testing.T) { 71 | var val reflect.Value 72 | if got, ok := c.convNumToBool(0, val); ok || got { 73 | t.Fatal("expected failure") 74 | } 75 | }) 76 | t.Run("convNumToDuration", func(t *testing.T) { 77 | var val reflect.Value 78 | if _, ok := c.convNumToDuration(0, val); ok { 79 | t.Fatal("expected convNumToDuration to return false on invalid kind") 80 | } 81 | }) 82 | t.Run("timeFromString", func(t *testing.T) { 83 | if _, ok := convStringToTime(""); ok { 84 | t.Fatal("expected timeFromString to return false on 0 len str") 85 | } 86 | }) 87 | } 88 | 89 | func TestBounds(t *testing.T) { 90 | defer initIntSizes(mathIntSize) 91 | 92 | var c Conv 93 | chk := func() { 94 | chkMaxInt, err := c.Int(fmt.Sprintf("%v", math.MaxInt64)) 95 | if err != nil { 96 | t.Error(err) 97 | } 98 | if int64(chkMaxInt) != mathMaxInt { 99 | t.Errorf("chkMaxInt exp %v; got %v", chkMaxInt, mathMaxInt) 100 | } 101 | 102 | chkMinInt, err := c.Int(fmt.Sprintf("%v", math.MinInt64)) 103 | if err != nil { 104 | t.Error(err) 105 | } 106 | if int64(chkMinInt) != mathMinInt { 107 | t.Errorf("chkMaxInt exp %v; got %v", chkMinInt, mathMaxInt) 108 | } 109 | 110 | chkUint, err := c.Uint(fmt.Sprintf("%v", uint64(math.MaxUint64))) 111 | if err != nil { 112 | t.Error(err) 113 | } 114 | if uint64(chkUint) != mathMaxUint { 115 | t.Errorf("chkMaxInt exp %v; got %v", chkMinInt, chkUint) 116 | } 117 | } 118 | 119 | initIntSizes(32) 120 | chk() 121 | 122 | initIntSizes(64) 123 | chk() 124 | } 125 | -------------------------------------------------------------------------------- /internal/refconv/string.go: -------------------------------------------------------------------------------- 1 | // Package refconv implements the Converter interface by using the standard 2 | // libraries reflection package. 3 | package refconv 4 | 5 | import ( 6 | "fmt" 7 | "math" 8 | "math/cmplx" 9 | "reflect" 10 | 11 | "github.com/cstockton/go-conv/internal/refutil" 12 | ) 13 | 14 | type stringConverter interface { 15 | String() (string, error) 16 | } 17 | 18 | // String returns the string representation from the given interface{} value 19 | // and can not currently fail. Although an error is currently provided only for 20 | // API cohesion you should still check it to be future proof. 21 | func (c Conv) String(from interface{}) (string, error) { 22 | switch T := from.(type) { 23 | case string: 24 | return T, nil 25 | case stringConverter: 26 | return T.String() 27 | case []byte: 28 | return string(T), nil 29 | case *[]byte: 30 | // @TODO Maybe validate the bytes are valid runes 31 | return string(*T), nil 32 | case *string: 33 | return *T, nil 34 | default: 35 | return fmt.Sprintf("%v", from), nil 36 | } 37 | } 38 | 39 | func (c Conv) convNumToBool(k reflect.Kind, value reflect.Value) (bool, bool) { 40 | switch { 41 | case refutil.IsKindInt(k): 42 | return 0 != value.Int(), true 43 | case refutil.IsKindUint(k): 44 | return 0 != value.Uint(), true 45 | case refutil.IsKindFloat(k): 46 | T := value.Float() 47 | if math.IsNaN(T) { 48 | return false, true 49 | } 50 | return 0 != T, true 51 | case refutil.IsKindComplex(k): 52 | T := value.Complex() 53 | if cmplx.IsNaN(T) { 54 | return false, true 55 | } 56 | return 0 != real(T), true 57 | } 58 | return false, false 59 | } 60 | -------------------------------------------------------------------------------- /internal/refconv/time.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/cmplx" 7 | "reflect" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/cstockton/go-conv/internal/refutil" 12 | ) 13 | 14 | var ( 15 | emptyTime = time.Time{} 16 | typeOfTime = reflect.TypeOf(emptyTime) 17 | typeOfDuration = reflect.TypeOf(time.Duration(0)) 18 | ) 19 | 20 | func (c Conv) convStrToDuration(v string) (time.Duration, error) { 21 | if parsed, err := time.ParseDuration(v); err == nil { 22 | return parsed, nil 23 | } 24 | if parsed, err := strconv.ParseInt(v, 10, 0); err == nil { 25 | return time.Duration(parsed), nil 26 | } 27 | if parsed, err := strconv.ParseFloat(v, 64); err == nil { 28 | return time.Duration(1e9 * parsed), nil 29 | } 30 | return 0, fmt.Errorf("cannot parse %#v (type string) as time.Duration", v) 31 | } 32 | 33 | func (c Conv) convNumToDuration(k reflect.Kind, v reflect.Value) (time.Duration, bool) { 34 | switch { 35 | case refutil.IsKindInt(k): 36 | return time.Duration(v.Int()), true 37 | case refutil.IsKindUint(k): 38 | T := v.Uint() 39 | if T > math.MaxInt64 { 40 | T = math.MaxInt64 41 | } 42 | return time.Duration(T), true 43 | case refutil.IsKindFloat(k): 44 | T := v.Float() 45 | if math.IsNaN(T) || math.IsInf(T, 0) { 46 | return 0, true 47 | } 48 | return time.Duration(1e9 * T), true 49 | case refutil.IsKindComplex(k): 50 | T := v.Complex() 51 | if cmplx.IsNaN(T) || cmplx.IsInf(T) { 52 | return 0, true 53 | } 54 | return time.Duration(1e9 * real(T)), true 55 | } 56 | return 0, false 57 | } 58 | 59 | type durationConverter interface { 60 | Duration() (time.Duration, error) 61 | } 62 | 63 | // Duration attempts to convert the given value to time.Duration, returns the 64 | // zero value and an error on failure. 65 | func (c Conv) Duration(from interface{}) (time.Duration, error) { 66 | if T, ok := from.(string); ok { 67 | return c.convStrToDuration(T) 68 | } else if T, ok := from.(time.Duration); ok { 69 | return T, nil 70 | } else if c, ok := from.(durationConverter); ok { 71 | return c.Duration() 72 | } 73 | 74 | value := refutil.IndirectVal(reflect.ValueOf(from)) 75 | kind := value.Kind() 76 | switch { 77 | case reflect.String == kind: 78 | return c.convStrToDuration(value.String()) 79 | case refutil.IsKindNumeric(kind): 80 | if parsed, ok := c.convNumToDuration(kind, value); ok { 81 | return parsed, nil 82 | } 83 | } 84 | return 0, newConvErr(from, "time.Duration") 85 | } 86 | 87 | type timeConverter interface { 88 | Time() (time.Time, error) 89 | } 90 | 91 | // Time attempts to convert the given value to time.Time, returns the zero value 92 | // of time.Time and an error on failure. 93 | func (c Conv) Time(from interface{}) (time.Time, error) { 94 | if T, ok := from.(time.Time); ok { 95 | return T, nil 96 | } else if T, ok := from.(*time.Time); ok { 97 | return *T, nil 98 | } else if c, ok := from.(timeConverter); ok { 99 | return c.Time() 100 | } 101 | 102 | value := reflect.ValueOf(refutil.Indirect(from)) 103 | kind := value.Kind() 104 | switch { 105 | case reflect.String == kind: 106 | if T, ok := convStringToTime(value.String()); ok { 107 | return T, nil 108 | } 109 | case reflect.Struct == kind: 110 | if value.Type().ConvertibleTo(typeOfTime) { 111 | valueConv := value.Convert(typeOfTime) 112 | if valueConv.CanInterface() { 113 | return valueConv.Interface().(time.Time), nil 114 | } 115 | } 116 | field := value.FieldByName("Time") 117 | if field.IsValid() && field.CanInterface() { 118 | return c.Time(field.Interface()) 119 | } 120 | } 121 | return emptyTime, newConvErr(from, "time.Time") 122 | } 123 | 124 | type formatInfo struct { 125 | format string 126 | needed string 127 | } 128 | 129 | var formats = []formatInfo{ 130 | {time.RFC3339Nano, ""}, 131 | {time.RFC3339, ""}, 132 | {time.RFC850, ""}, 133 | {time.RFC1123, ""}, 134 | {time.RFC1123Z, ""}, 135 | {"02 Jan 06 15:04:05", ""}, 136 | {"02 Jan 06 15:04:05 +-0700", ""}, 137 | {"02 Jan 06 15:4:5 MST", ""}, 138 | {"02 Jan 2006 15:04:05", ""}, 139 | {"2 Jan 2006 15:04:05", ""}, 140 | {"2 Jan 2006 15:04:05 MST", ""}, 141 | {"2 Jan 2006 15:04:05 -0700", ""}, 142 | {"2 Jan 2006 15:04:05 -0700 (MST)", ""}, 143 | {"02 January 2006 15:04", ""}, 144 | {"02 Jan 2006 15:04 MST", ""}, 145 | {"02 Jan 2006 15:04:05 MST", ""}, 146 | {"02 Jan 2006 15:04:05 -0700", ""}, 147 | {"02 Jan 2006 15:04:05 -0700 (MST)", ""}, 148 | {"Mon, 2 Jan 15:04:05 MST 2006", ""}, 149 | {"Mon, 2 Jan 15:04:05 MST 2006", ""}, 150 | {"Mon, 02 Jan 2006 15:04:05", ""}, 151 | {"Mon, 02 Jan 2006 15:04:05 (MST)", ""}, 152 | {"Mon, 2 Jan 2006 15:04:05", ""}, 153 | {"Mon, 2 Jan 2006 15:04:05 MST", ""}, 154 | {"Mon, 2 Jan 2006 15:04:05 -0700", ""}, 155 | {"Mon, 2 Jan 2006 15:04:05 -0700 (MST)", ""}, 156 | {"Mon, 02 Jan 06 15:04:05 MST", ""}, 157 | {"Mon, 02 Jan 2006 15:04:05 -0700", ""}, 158 | {"Mon, 02 Jan 2006 15:04:05 -0700 MST", ""}, 159 | {"Mon, 02 Jan 2006 15:04:05 -0700 (MST)", ""}, 160 | {"Mon, 02 Jan 2006 15:04:05 -0700 (MST-07:00)", ""}, 161 | {"Mon, 02 Jan 2006 15:04:05 -0700 (MST MST)", ""}, 162 | {"Mon, 02 Jan 2006 15:04 -0700", ""}, 163 | {"Mon, 02 Jan 2006 15:04 -0700 (MST)", ""}, 164 | {"Mon Jan 02 15:05:05 2006 MST", ""}, 165 | {"Monday, 02 Jan 2006 15:04 -0700", ""}, 166 | {"Monday, 02 Jan 2006 15:04:05 -0700", ""}, 167 | {time.UnixDate, ""}, 168 | {time.RubyDate, ""}, 169 | {time.RFC822, ""}, 170 | {time.RFC822Z, ""}, 171 | } 172 | 173 | // Quick google yields no date parsing libraries, first thing that came to mind 174 | // was trying all the formats in time package. This is reasonable enough until 175 | // I can find a decent lexer or polish up my "timey" Go lib. I am using the 176 | // table of dates politely released into public domain by github.com/tomarus: 177 | // https://github.com/tomarus/parsedate/blob/master/parsedate.go 178 | func convStringToTime(s string) (time.Time, bool) { 179 | if len(s) == 0 { 180 | return time.Time{}, false 181 | } 182 | for _, f := range formats { 183 | _, err := time.Parse(f.format, s) 184 | if err != nil { 185 | continue 186 | } 187 | if t, err := time.Parse( 188 | f.format+f.needed, s+time.Now().Format(f.needed)); err == nil { 189 | return t, true 190 | } 191 | } 192 | return time.Time{}, false 193 | } 194 | -------------------------------------------------------------------------------- /internal/refconv/uint.go: -------------------------------------------------------------------------------- 1 | package refconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "strconv" 8 | 9 | "github.com/cstockton/go-conv/internal/refutil" 10 | ) 11 | 12 | func (c Conv) convStrToUint64(v string) (uint64, error) { 13 | if parsed, err := strconv.ParseUint(v, 10, 0); err == nil { 14 | return parsed, nil 15 | } 16 | if parsed, err := strconv.ParseFloat(v, 64); err == nil { 17 | return uint64(math.Max(0, parsed)), nil 18 | } 19 | if parsed, err := c.convStrToBool(v); err == nil { 20 | if parsed { 21 | return 1, nil 22 | } 23 | return 0, nil 24 | } 25 | return 0, fmt.Errorf("cannot convert %#v (type string) to uint64", v) 26 | } 27 | 28 | type uintConverter interface { 29 | Uint64() (uint64, error) 30 | } 31 | 32 | // Uint64 attempts to convert the given value to uint64, returns the zero value 33 | // and an error on failure. 34 | func (c Conv) Uint64(from interface{}) (uint64, error) { 35 | if T, ok := from.(string); ok { 36 | return c.convStrToUint64(T) 37 | } else if T, ok := from.(uint64); ok { 38 | return T, nil 39 | } 40 | if c, ok := from.(uintConverter); ok { 41 | return c.Uint64() 42 | } 43 | 44 | value := refutil.IndirectVal(reflect.ValueOf(from)) 45 | kind := value.Kind() 46 | switch { 47 | case reflect.String == kind: 48 | return c.convStrToUint64(value.String()) 49 | case refutil.IsKindUint(kind): 50 | return value.Uint(), nil 51 | case refutil.IsKindInt(kind): 52 | val := value.Int() 53 | if val < 0 { 54 | val = 0 55 | } 56 | return uint64(val), nil 57 | case refutil.IsKindFloat(kind): 58 | return uint64(math.Max(0, value.Float())), nil 59 | case refutil.IsKindComplex(kind): 60 | return uint64(math.Max(0, real(value.Complex()))), nil 61 | case reflect.Bool == kind: 62 | if value.Bool() { 63 | return 1, nil 64 | } 65 | return 0, nil 66 | case refutil.IsKindLength(kind): 67 | return uint64(value.Len()), nil 68 | } 69 | 70 | return 0, newConvErr(from, "uint64") 71 | } 72 | 73 | // Uint attempts to convert the given value to uint, returns the zero value and 74 | // an error on failure. 75 | func (c Conv) Uint(from interface{}) (uint, error) { 76 | if T, ok := from.(uint); ok { 77 | return T, nil 78 | } 79 | 80 | to64, err := c.Uint64(from) 81 | if err != nil { 82 | return 0, newConvErr(from, "uint") 83 | } 84 | if to64 > mathMaxUint { 85 | to64 = mathMaxUint // only possible on 32bit arch 86 | } 87 | return uint(to64), nil 88 | } 89 | 90 | // Uint8 attempts to convert the given value to uint8, returns the zero value 91 | // and an error on failure. 92 | func (c Conv) Uint8(from interface{}) (uint8, error) { 93 | if T, ok := from.(uint8); ok { 94 | return T, nil 95 | } 96 | 97 | to64, err := c.Uint64(from) 98 | if err != nil { 99 | return 0, newConvErr(from, "uint8") 100 | } 101 | if to64 > math.MaxUint8 { 102 | to64 = math.MaxUint8 103 | } 104 | return uint8(to64), nil 105 | } 106 | 107 | // Uint16 attempts to convert the given value to uint16, returns the zero value 108 | // and an error on failure. 109 | func (c Conv) Uint16(from interface{}) (uint16, error) { 110 | if T, ok := from.(uint16); ok { 111 | return T, nil 112 | } 113 | 114 | to64, err := c.Uint64(from) 115 | if err != nil { 116 | return 0, newConvErr(from, "uint16") 117 | } 118 | if to64 > math.MaxUint16 { 119 | to64 = math.MaxUint16 120 | } 121 | return uint16(to64), nil 122 | } 123 | 124 | // Uint32 attempts to convert the given value to uint32, returns the zero value 125 | // and an error on failure. 126 | func (c Conv) Uint32(from interface{}) (uint32, error) { 127 | if T, ok := from.(uint32); ok { 128 | return T, nil 129 | } 130 | 131 | to64, err := c.Uint64(from) 132 | if err != nil { 133 | return 0, newConvErr(from, "uint32") 134 | } 135 | if to64 > math.MaxUint32 { 136 | to64 = math.MaxUint32 137 | } 138 | return uint32(to64), nil 139 | } 140 | -------------------------------------------------------------------------------- /internal/refutil/refutil.go: -------------------------------------------------------------------------------- 1 | package refutil 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // IsKindComplex returns true if the given Kind is a complex value. 9 | func IsKindComplex(k reflect.Kind) bool { 10 | return reflect.Complex64 == k || k == reflect.Complex128 11 | } 12 | 13 | // IsKindFloat returns true if the given Kind is a float value. 14 | func IsKindFloat(k reflect.Kind) bool { 15 | return reflect.Float32 == k || k == reflect.Float64 16 | } 17 | 18 | // IsKindInt returns true if the given Kind is a int value. 19 | func IsKindInt(k reflect.Kind) bool { 20 | return reflect.Int <= k && k <= reflect.Int64 21 | } 22 | 23 | // IsKindUint returns true if the given Kind is a uint value. 24 | func IsKindUint(k reflect.Kind) bool { 25 | return reflect.Uint <= k && k <= reflect.Uintptr 26 | } 27 | 28 | // IsKindNumeric returns true if the given Kind is a numeric value. 29 | func IsKindNumeric(k reflect.Kind) bool { 30 | return (reflect.Int <= k && k <= reflect.Uint64) || 31 | (reflect.Float32 <= k && k <= reflect.Complex128) 32 | } 33 | 34 | // IsKindNillable will return true if the Kind is a chan, func, interface, map, 35 | // pointer, or slice value, false otherwise. 36 | func IsKindNillable(k reflect.Kind) bool { 37 | return (reflect.Chan <= k && k <= reflect.Slice) || k == reflect.UnsafePointer 38 | } 39 | 40 | // IsKindLength will return true if the Kind has a length. 41 | func IsKindLength(k reflect.Kind) bool { 42 | return reflect.Array == k || reflect.Chan == k || reflect.Map == k || 43 | reflect.Slice == k || reflect.String == k 44 | } 45 | 46 | // IndirectVal is like Indirect but faster when the caller is using working with 47 | // a reflect.Value. 48 | func IndirectVal(val reflect.Value) reflect.Value { 49 | var last uintptr 50 | for { 51 | if val.Kind() != reflect.Ptr { 52 | return val 53 | } 54 | 55 | ptr := val.Pointer() 56 | if ptr == last { 57 | return val 58 | } 59 | last, val = ptr, val.Elem() 60 | } 61 | } 62 | 63 | // Indirect will perform recursive indirection on the given value. It should 64 | // never panic and will return a value unless indirection is impossible due to 65 | // infinite recursion in cases like `type Element *Element`. 66 | func Indirect(value interface{}) interface{} { 67 | for { 68 | 69 | val := reflect.ValueOf(value) 70 | if val.Kind() != reflect.Ptr { 71 | // Value is not a pointer. 72 | return value 73 | } 74 | 75 | res := reflect.Indirect(val) 76 | if !res.IsValid() || !res.CanInterface() { 77 | // Invalid value or can't be returned as interface{}. 78 | return value 79 | } 80 | 81 | // Test for a circular type. 82 | if res.Kind() == reflect.Ptr && val.Pointer() == res.Pointer() { 83 | return value 84 | } 85 | 86 | // Next round. 87 | value = res.Interface() 88 | } 89 | } 90 | 91 | // Recover will attempt to execute f, if f return a non-nil error it will be 92 | // returned. If f panics this function will attempt to recover() and return a 93 | // error instead. 94 | func Recover(f func() error) (err error) { 95 | defer func() { 96 | if r := recover(); r != nil { 97 | switch T := r.(type) { 98 | case error: 99 | err = T 100 | default: 101 | err = fmt.Errorf("panic: %v", r) 102 | } 103 | } 104 | }() 105 | err = f() 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /internal/refutil/refutil_test.go: -------------------------------------------------------------------------------- 1 | package refutil 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkIndirect(b *testing.B) { 11 | var escape int64 12 | int64v := int64(123) 13 | int64vp := &int64v 14 | int64vpp := &int64vp 15 | int64vppp := &int64vpp 16 | int64vpppp := &int64vppp 17 | 18 | b.Run(`Indirect`, func(b *testing.B) { 19 | b.ReportAllocs() 20 | 21 | for i := 0; i < b.N; i++ { 22 | got := Indirect(int64vpppp).(int64) 23 | if got != int64v { 24 | b.Fatal(`bad value`) 25 | } 26 | escape = got 27 | } 28 | }) 29 | if escape != int64v { 30 | b.Fatal(`bad value`) 31 | } 32 | 33 | b.Run(`IndirectVal`, func(b *testing.B) { 34 | b.ReportAllocs() 35 | 36 | for i := 0; i < b.N; i++ { 37 | val := reflect.ValueOf(int64vpppp) 38 | got := IndirectVal(val).Interface().(int64) 39 | if got != int64v { 40 | b.Fatal(`bad value`) 41 | } 42 | escape = got 43 | } 44 | }) 45 | if escape != int64v { 46 | b.Fatal(`bad value`) 47 | } 48 | } 49 | 50 | func TestIndirect(t *testing.T) { 51 | type testIndirectCircular *testIndirectCircular 52 | teq := func(t testing.TB, exp, got interface{}) { 53 | if !reflect.DeepEqual(exp, got) { 54 | t.Errorf("DeepEqual failed:\n exp: %#v\n got: %#v", exp, got) 55 | } 56 | } 57 | nilValues := []interface{}{ 58 | (*interface{})(nil), (**interface{})(nil), (***interface{})(nil), 59 | (func())(nil), (*func())(nil), (**func())(nil), (***func())(nil), 60 | (chan int)(nil), (*chan int)(nil), (**chan int)(nil), (***chan int)(nil), 61 | ([]int)(nil), (*[]int)(nil), (**[]int)(nil), (***[]int)(nil), 62 | (map[int]int)(nil), (*map[int]int)(nil), (**map[int]int)(nil), 63 | (***map[int]int)(nil), 64 | } 65 | 66 | t.Run("Basic", func(t *testing.T) { 67 | int64v := int64(123) 68 | int64vp := &int64v 69 | int64vpp := &int64vp 70 | int64vppp := &int64vpp 71 | int64vpppp := &int64vppp 72 | teq(t, Indirect(int64v), int64v) 73 | teq(t, IndirectVal(reflect.ValueOf(int64v)).Interface(), int64v) 74 | teq(t, Indirect(int64vp), int64v) 75 | teq(t, IndirectVal(reflect.ValueOf(int64vp)).Interface(), int64v) 76 | teq(t, Indirect(int64vpp), int64v) 77 | teq(t, IndirectVal(reflect.ValueOf(int64vpp)).Interface(), int64v) 78 | teq(t, Indirect(int64vppp), int64v) 79 | teq(t, IndirectVal(reflect.ValueOf(int64vppp)).Interface(), int64v) 80 | teq(t, Indirect(int64vpppp), int64v) 81 | teq(t, IndirectVal(reflect.ValueOf(int64vpppp)).Interface(), int64v) 82 | }) 83 | t.Run("Nils", func(t *testing.T) { 84 | for _, n := range nilValues { 85 | Indirect(n) 86 | IndirectVal(reflect.ValueOf(n)) 87 | } 88 | }) 89 | t.Run("Circular", func(t *testing.T) { 90 | var circular testIndirectCircular 91 | circular = &circular 92 | teq(t, Indirect(circular), circular) 93 | teq(t, IndirectVal(reflect.ValueOf(circular)).Interface(), circular) 94 | }) 95 | } 96 | 97 | func TestRecoverFn(t *testing.T) { 98 | t.Run("CallsFunc", func(t *testing.T) { 99 | var called bool 100 | 101 | err := Recover(func() error { 102 | called = true 103 | return nil 104 | }) 105 | if err != nil { 106 | t.Error("expected no error in recoverFn()") 107 | } 108 | if !called { 109 | t.Error("Expected recoverFn() to call func") 110 | } 111 | }) 112 | t.Run("PropagatesError", func(t *testing.T) { 113 | err := fmt.Errorf("expect this error") 114 | rerr := Recover(func() error { 115 | return err 116 | }) 117 | if err != rerr { 118 | t.Error("expected recoverFn() to propagate") 119 | } 120 | }) 121 | t.Run("PropagatesPanicError", func(t *testing.T) { 122 | err := fmt.Errorf("expect this error") 123 | rerr := Recover(func() error { 124 | panic(err) 125 | }) 126 | if err != rerr { 127 | t.Error("Expected recoverFn() to propagate") 128 | } 129 | }) 130 | t.Run("PropagatesRuntimeError", func(t *testing.T) { 131 | err := Recover(func() error { 132 | sl := []int{} 133 | _ = sl[0] 134 | return nil 135 | }) 136 | if err == nil { 137 | t.Error("expected runtime error to propagate") 138 | } 139 | if _, ok := err.(runtime.Error); !ok { 140 | t.Error("expected runtime error to retain type type") 141 | } 142 | }) 143 | t.Run("PropagatesString", func(t *testing.T) { 144 | exp := "panic: string type panic" 145 | rerr := Recover(func() error { 146 | panic("string type panic") 147 | }) 148 | if exp != rerr.Error() { 149 | t.Errorf("expected recoverFn() to return %v, got: %v", exp, rerr) 150 | } 151 | }) 152 | } 153 | 154 | func TestKind(t *testing.T) { 155 | var ( 156 | intKinds = []reflect.Kind{reflect.Int, reflect.Int8, reflect.Int16, 157 | reflect.Int32, reflect.Int64} 158 | uintKinds = []reflect.Kind{reflect.Uint, reflect.Uint8, reflect.Uint16, 159 | reflect.Uint32, reflect.Uint64} 160 | floatKinds = []reflect.Kind{reflect.Float32, reflect.Float64} 161 | complexKinds = []reflect.Kind{reflect.Complex64, reflect.Complex128} 162 | lengthKinds = []reflect.Kind{reflect.Array, reflect.Chan, reflect.Map, 163 | reflect.Slice, reflect.String} 164 | nillableKinds = []reflect.Kind{reflect.Chan, reflect.Func, reflect.Interface, 165 | reflect.Map, reflect.Slice, reflect.Ptr} 166 | ) 167 | type kindFunc func(k reflect.Kind) bool 168 | type testKind struct { 169 | exp bool 170 | kinds []reflect.Kind 171 | f []kindFunc 172 | } 173 | tnew := func(exp bool, k []reflect.Kind, f ...kindFunc) testKind { 174 | return testKind{exp, k, f} 175 | } 176 | tests := []testKind{ 177 | tnew(true, intKinds, IsKindInt, IsKindNumeric), 178 | tnew(false, intKinds, IsKindComplex, IsKindFloat, 179 | IsKindUint, IsKindLength, IsKindNillable), 180 | tnew(true, uintKinds, IsKindNumeric, IsKindUint), 181 | tnew(false, uintKinds, IsKindComplex, IsKindFloat, 182 | IsKindInt, IsKindLength, IsKindNillable), 183 | tnew(true, floatKinds, IsKindFloat, IsKindNumeric), 184 | tnew(false, floatKinds, IsKindComplex, IsKindInt, 185 | IsKindUint, IsKindLength, IsKindNillable), 186 | tnew(true, complexKinds, IsKindComplex, IsKindNumeric), 187 | tnew(false, complexKinds, IsKindFloat, IsKindInt, 188 | IsKindUint, IsKindLength, IsKindNillable), 189 | tnew(true, lengthKinds, IsKindLength), 190 | tnew(true, nillableKinds, IsKindNillable), 191 | } 192 | for _, tc := range tests { 193 | for _, f := range tc.f { 194 | t.Run(fmt.Sprintf("%v", tc.kinds[0]), func(t *testing.T) { 195 | for _, kind := range tc.kinds { 196 | if got := f(kind); got != tc.exp { 197 | t.Errorf("%#v(%v)\nexp: %v\ngot: %v", f, kind, tc.exp, got) 198 | } 199 | } 200 | }) 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /internal/testconv/assert.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "path" 8 | "reflect" 9 | "runtime" 10 | "strings" 11 | "time" 12 | 13 | "github.com/cstockton/go-conv/internal/refutil" 14 | ) 15 | 16 | var ( 17 | assertions Assertions 18 | assertionsIndex = make(AssertionsIndex) 19 | ) 20 | 21 | const ( 22 | TypeKind reflect.Kind = reflect.UnsafePointer + iota 23 | DurationKind 24 | TimeKind 25 | SliceMask reflect.Kind = (1 << 8) 26 | MapMask = (1 << 16) 27 | ChanMask = (1 << 24) 28 | ) 29 | 30 | var refKindNames = map[reflect.Kind]string{ 31 | DurationKind: "time.Duration", 32 | TimeKind: "time.Time", 33 | } 34 | 35 | func reflectHasMask(k, mask reflect.Kind) bool { 36 | return (k & mask) == mask 37 | } 38 | 39 | func reflectKindStr(k reflect.Kind) string { 40 | if reflectHasMask(k, ChanMask|SliceMask|MapMask) { 41 | return refKindNames[k] 42 | } 43 | return k.String() 44 | } 45 | 46 | // returns the reflect.Kind with the additional masks / types for conversions. 47 | func convKindStr(v interface{}) string { return reflectKindStr(convKind(v)) } 48 | func convKind(v interface{}) reflect.Kind { 49 | switch T := refutil.Indirect(v).(type) { 50 | case reflect.Kind: 51 | return T 52 | case Expecter: 53 | return T.Kind() 54 | case time.Time: 55 | return TimeKind 56 | case time.Duration: 57 | return DurationKind 58 | default: 59 | kind := reflect.TypeOf(T).Kind() 60 | if reflect.Slice == kind || kind == reflect.Array { 61 | kind |= SliceMask 62 | } else if kind == reflect.Map { 63 | kind |= MapMask 64 | } else if kind == reflect.Chan { 65 | kind |= SliceMask 66 | } 67 | return kind 68 | } 69 | } 70 | 71 | // Expecter defines a type of conversion for v and will return an error if the 72 | // value is unexpected. 73 | type Expecter interface { 74 | 75 | // Exp is given the result of conversion and an error if one occurred. The 76 | // type of conversion operation should be the type of Kind(). 77 | Expect(got interface{}, err error) error 78 | 79 | // Value is the expected result. 80 | Value() interface{} 81 | 82 | // Kind returns the type of conversion that should be given to Exepct(). 83 | Kind() reflect.Kind 84 | } 85 | 86 | type AssertionsLookup struct { 87 | assertion *Assertion 88 | expecter Expecter 89 | } 90 | 91 | // AssertionsIndex is map of assertions by the Expecter kind. 92 | type AssertionsIndex map[reflect.Kind][]AssertionsLookup 93 | 94 | // Assertions is slice of assertions. 95 | type Assertions []*Assertion 96 | 97 | // EachOf will visit each assertion that contains an Expecter for reflect.Kind. 98 | func (a Assertions) EachOf(k reflect.Kind, f func(a *Assertion, e Expecter)) int { 99 | found := assertionsIndex[k] 100 | for _, v := range found { 101 | f(v.assertion, v.expecter) 102 | } 103 | return len(found) 104 | } 105 | 106 | // Assertion represents a set of expected values from a Converter. It's the 107 | // general implementation of asserter. It will compare each Expects to the 108 | // associated Converter.T(). It returns an error for each failed conversion 109 | // or an empty error slice if no failures occurred. 110 | type Assertion struct { 111 | 112 | // From is the underlying value to be used in conversions. 113 | From interface{} 114 | 115 | // Name of this assertion 116 | Name string 117 | 118 | // Code to construct Interface 119 | Code string 120 | 121 | // Type of Interface field using fmt %#T. 122 | Type string 123 | 124 | // File is the file the assertion is defined in. 125 | File string 126 | 127 | // Line is the line number the assertion is defined in. 128 | Line int 129 | 130 | // Description for this assertion. 131 | Desc string 132 | 133 | // Expects contains a list of values this Assertion expects to see as a result 134 | // of calling the conversion function for the associated type. For example 135 | // appending a single int64 to this slice would mean it is expected that: 136 | // conv.Int64(assertion.From) == Expect[0].(int64) 137 | // When appending multiple values of the same type, the last one appended is 138 | // used. 139 | Exps []Expecter 140 | } 141 | 142 | func (a Assertion) String() string { 143 | return fmt.Sprintf("[assertion %v:%d] from `%v` (%[3]T)", 144 | a.File, a.Line, a.From) 145 | } 146 | 147 | // assert will create an assertion type for the first argument given. Args is 148 | // consumed into the Expects slice, with slices of interfaces being flattened. 149 | func assert(value interface{}, args ...interface{}) { 150 | a := &Assertion{ 151 | From: value, 152 | Type: typename(value), 153 | } 154 | split := strings.SplitN(a.Type, ".", 2) 155 | a.Name = strings.Title(split[len(split)-1]) 156 | a.Code = fmt.Sprintf("%#v", value) 157 | _, a.File, a.Line, _ = runtime.Caller(1) 158 | a.File = path.Base(a.File) 159 | 160 | var slurp func(interface{}) 161 | slurp = func(value interface{}) { 162 | switch T := value.(type) { 163 | case []interface{}: 164 | for _, t := range T { 165 | slurp(t) 166 | } 167 | case []Expecter: 168 | for _, t := range T { 169 | slurp(t) 170 | } 171 | case Expecter: 172 | a.Exps = append(a.Exps, T) 173 | default: 174 | a.Exps = append(a.Exps, Exp{value}) 175 | } 176 | } 177 | for _, arg := range args { 178 | slurp(arg) 179 | } 180 | for _, exp := range a.Exps { 181 | k := exp.Kind() 182 | assertionsIndex[k] = append(assertionsIndex[k], AssertionsLookup{a, exp}) 183 | } 184 | assertions = append(assertions, a) 185 | } 186 | 187 | // Exp is used for ducmentation purposes. 188 | type Exp struct { 189 | Want interface{} 190 | } 191 | 192 | func (e Exp) Kind() reflect.Kind { 193 | return convKind(e.Want) 194 | } 195 | 196 | func (e Exp) Value() interface{} { 197 | return e.Want 198 | } 199 | 200 | func (e Exp) Expect(got interface{}, err error) error { 201 | if err == nil && !reflect.DeepEqual(got, e.Want) { 202 | return fmt.Errorf("exp: (%T) %[1]v\ngot: (%T) %[2]v", e.Want, got) 203 | } 204 | return err 205 | } 206 | 207 | func (e Exp) String() string { 208 | return fmt.Sprintf("to %v (type %[1]T)", e.Want) 209 | } 210 | 211 | // ErrorExp ensures a given conversion failed. 212 | type ErrorExp struct { 213 | Exp 214 | ErrStr string 215 | } 216 | 217 | func experr(want interface{}, contains string) Expecter { 218 | return ErrorExp{ErrStr: contains, Exp: Exp{Want: want}} 219 | } 220 | 221 | func (e ErrorExp) Expect(got interface{}, err error) error { 222 | if err != nil { 223 | if len(e.ErrStr) == 0 { 224 | return err 225 | } 226 | if !strings.Contains(err.Error(), e.ErrStr) { 227 | return fmt.Errorf("error did not match:\n exp: %v\n got: %v", e.ErrStr, err) 228 | } 229 | } else if len(e.ErrStr) > 0 { 230 | return errors.New("expected non-nil err") 231 | } 232 | return nil 233 | } 234 | 235 | // Float64Exp asserts that (converter.Float64() - Exp) < epsilon64 236 | type Float64Exp struct { 237 | Want float64 238 | } 239 | 240 | const ( 241 | epsilon64 = float64(.00000000000000001) 242 | ) 243 | 244 | func (e Float64Exp) Kind() reflect.Kind { return reflect.Float64 } 245 | func (e Float64Exp) Expect(got interface{}, err error) error { 246 | val := got.(float64) 247 | abs := math.Abs(val - e.Want) 248 | if abs < epsilon64 { 249 | return err 250 | } 251 | return fmt.Errorf("%#v.assert(%#v): abs value %v exceeded epsilon %v", 252 | e, val, abs, epsilon64) 253 | } 254 | 255 | func (e Float64Exp) Value() interface{} { 256 | return e.Want 257 | } 258 | 259 | // Float32Exp asserts that (converter.Float32() - Exp) < epsilon64 260 | type Float32Exp struct { 261 | Want float32 262 | } 263 | 264 | func (e Float32Exp) Kind() reflect.Kind { return reflect.Float32 } 265 | func (e Float32Exp) Expect(got interface{}, err error) error { 266 | val := got.(float32) 267 | abs := math.Abs(float64(val - e.Want)) 268 | if abs < epsilon64 { 269 | return err 270 | } 271 | return fmt.Errorf("%#v.assert(%#v): abs value %v exceeded epsilon %v", 272 | e, val, abs, epsilon64) 273 | } 274 | 275 | func (e Float32Exp) Value() interface{} { 276 | return e.Want 277 | } 278 | 279 | // TimeExp helps validate time.Time() conversions, specifically because under 280 | // some conversions time.Now() may be used. It will check that the difference 281 | // between the Moment is the same as the given value after truncation and 282 | // rounding (if either is set) is identical. 283 | type TimeExp struct { 284 | Moment time.Time 285 | Offset time.Duration 286 | Round time.Duration 287 | Truncate time.Duration 288 | } 289 | 290 | func (e TimeExp) String() string { return fmt.Sprintf("%v", e.Moment) } 291 | func (e TimeExp) Kind() reflect.Kind { return TimeKind } 292 | func (e TimeExp) Expect(got interface{}, err error) error { 293 | val := got.(time.Time).Add(e.Offset) 294 | if e.Round != 0 { 295 | val = val.Round(e.Round) 296 | } 297 | if e.Truncate != 0 { 298 | val = val.Round(e.Truncate) 299 | } 300 | if !e.Moment.Equal(val) { 301 | return fmt.Errorf( 302 | "times did not match:\n exp: %v\n got: %v", e.Moment, val) 303 | } 304 | return nil 305 | } 306 | 307 | func (e TimeExp) Value() interface{} { 308 | return e.Moment 309 | } 310 | 311 | // DurationExp supports fuzzy duration conversions. 312 | type DurationExp struct { 313 | Want time.Duration 314 | Round time.Duration 315 | } 316 | 317 | func (e DurationExp) Kind() reflect.Kind { return DurationKind } 318 | func (e DurationExp) Expect(got interface{}, err error) error { 319 | d := got.(time.Duration) 320 | if e.Round != 0 { 321 | neg := d < 0 322 | if d < 0 { 323 | d = -d 324 | } 325 | if m := d % e.Round; m+m < e.Round { 326 | d = d - m 327 | } else { 328 | d = d + e.Round - m 329 | } 330 | if neg { 331 | d = -d 332 | } 333 | } 334 | if e.Want != d { 335 | return fmt.Errorf("%#v: %#v != %#v", e, e.Want, d) 336 | } 337 | return nil 338 | } 339 | 340 | func (e DurationExp) Value() interface{} { 341 | return e.Want 342 | } 343 | 344 | func typename(value interface{}) (name string) { 345 | parts := strings.SplitN(fmt.Sprintf("%T", value), ".", 2) 346 | 347 | if len(parts) == 2 { 348 | name = parts[0] + "." + parts[1] 349 | } else { 350 | name = parts[0] 351 | } 352 | return 353 | } 354 | -------------------------------------------------------------------------------- /internal/testconv/bool.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func RunBoolTests(t *testing.T, fn func(interface{}) (bool, error)) { 12 | RunTest(t, reflect.Bool, func(v interface{}) (interface{}, error) { 13 | return fn(v) 14 | }) 15 | } 16 | 17 | type testBoolConverter bool 18 | 19 | func (t testBoolConverter) Bool() (bool, error) { 20 | return !bool(t), nil 21 | } 22 | 23 | func init() { 24 | 25 | // strings: truthy 26 | trueStrings := []string{ 27 | "1", "t", "T", "true", "True", "TRUE", "y", "Y", "yes", "Yes", "YES"} 28 | for _, truthy := range trueStrings { 29 | assert(truthy, true) 30 | assert(testStringConverter(truthy), true) 31 | } 32 | 33 | // strings: falsy 34 | falseStrings := []string{ 35 | "0", "f", "F", "false", "False", "FALSE", "n", "N", "no", "No", "NO"} 36 | for _, falsy := range falseStrings { 37 | assert(falsy, false) 38 | assert(testStringConverter(falsy), false) 39 | } 40 | 41 | // numerics: true 42 | for _, i := range []int{-1, 1} { 43 | assert(i, true) 44 | assert(int8(i), true) 45 | assert(int16(i), true) 46 | assert(int32(i), true) 47 | assert(int64(i), true) 48 | assert(uint(i), true) 49 | assert(uint8(i), true) 50 | assert(uint16(i), true) 51 | assert(uint32(i), true) 52 | assert(uint64(i), true) 53 | assert(float32(i), true) 54 | assert(float64(i), true) 55 | assert(complex(float32(i), 0), true) 56 | assert(complex(float64(i), 0), true) 57 | } 58 | 59 | // int/uint: false 60 | assert(int(0), false) 61 | assert(int8(0), false) 62 | assert(int16(0), false) 63 | assert(int32(0), false) 64 | assert(int64(0), false) 65 | assert(uint(0), false) 66 | assert(uint8(0), false) 67 | assert(uint16(0), false) 68 | assert(uint32(0), false) 69 | assert(uint64(0), false) 70 | 71 | // float: NaN and 0 are false. 72 | assert(float32(math.NaN()), false) 73 | assert(math.NaN(), false) 74 | assert(float32(0), false) 75 | assert(float64(0), false) 76 | 77 | // complex: NaN and 0 are false. 78 | assert(complex64(cmplx.NaN()), false) 79 | assert(cmplx.NaN(), false) 80 | assert(complex(float32(0), 0), false) 81 | assert(complex(float64(0), 0), false) 82 | 83 | // time 84 | assert(time.Time{}, false) 85 | assert(time.Now(), true) 86 | 87 | // bool 88 | assert(false, false) 89 | assert(true, true) 90 | 91 | // underlying bool 92 | type ulyBool bool 93 | assert(ulyBool(false), false) 94 | assert(ulyBool(true), true) 95 | 96 | // implements bool converter 97 | assert(testBoolConverter(false), true) 98 | assert(testBoolConverter(true), false) 99 | 100 | // test length kinds 101 | assert([]string{"one", "two"}, true) 102 | assert(map[int]string{1: "one", 2: "two"}, true) 103 | assert([]string{}, false) 104 | assert([]string(nil), false) 105 | 106 | // errors 107 | assert(nil, experr(false, `cannot convert (type ) to bool`)) 108 | assert("foo", experr(false, `cannot parse "foo" (type string) as bool`)) 109 | assert("tooLong", experr( 110 | false, `cannot parse string with len 7 as bool`)) 111 | assert(struct{}{}, experr( 112 | false, `cannot convert struct {}{} (type struct {}) to `)) 113 | 114 | } 115 | -------------------------------------------------------------------------------- /internal/testconv/duration.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func RunDurationTests(t *testing.T, fn func(interface{}) (time.Duration, error)) { 11 | RunTest(t, DurationKind, func(v interface{}) (interface{}, error) { 12 | return fn(v) 13 | }) 14 | } 15 | 16 | type testDurationConverter time.Duration 17 | 18 | func (d testDurationConverter) Duration() (time.Duration, error) { 19 | return time.Duration(d) + time.Minute, nil 20 | } 21 | 22 | func init() { 23 | var ( 24 | dZero time.Duration 25 | d42ns = time.Nanosecond * 42 26 | d2m = time.Minute * 2 27 | d34s = time.Second * 34 28 | d567ms = time.Millisecond * 567 29 | d234567 = d2m + d34s + d567ms 30 | d154567f32 float32 = 154.567 31 | d154567f64 = 154.567 32 | dMAX = time.Duration(math.MaxInt64) 33 | ) 34 | 35 | // strings 36 | assert("2m34.567s", d234567) 37 | assert("-2m34.567s", -d234567) 38 | assert("154.567", d234567) 39 | assert("-154.567", -d234567) 40 | assert("42", d42ns) 41 | assert(testStringConverter("42"), d42ns) 42 | 43 | // durations 44 | assert(d234567, d234567) 45 | assert(dZero, dZero) 46 | assert(new(time.Duration), dZero) 47 | 48 | // underlying 49 | type ulyDuration time.Duration 50 | assert(ulyDuration(time.Second), time.Second) 51 | assert(ulyDuration(-time.Second), -time.Second) 52 | 53 | // implements converter 54 | assert(testDurationConverter(time.Second), time.Second+time.Minute) 55 | assert(testDurationConverter(-time.Second), time.Minute-time.Second) 56 | 57 | // numerics 58 | assert(int(42), d42ns) 59 | assert(int8(42), d42ns) 60 | assert(int16(42), d42ns) 61 | assert(int32(42), d42ns) 62 | assert(int64(42), d42ns) 63 | assert(uint(42), d42ns) 64 | assert(uint8(42), d42ns) 65 | assert(uint16(42), d42ns) 66 | assert(uint32(42), d42ns) 67 | assert(uint64(42), d42ns) 68 | 69 | // floats 70 | assert(d154567f32, DurationExp{d234567, time.Millisecond}) 71 | assert(d154567f64, d234567) 72 | assert(math.NaN(), dZero) 73 | assert(math.Inf(1), dZero) 74 | assert(math.Inf(-1), dZero) 75 | 76 | // complex 77 | assert(complex(d154567f32, 0), DurationExp{d234567, time.Millisecond}) 78 | assert(complex(d154567f64, 0), d234567) 79 | assert(cmplx.NaN(), dZero) 80 | assert(cmplx.Inf(), dZero) 81 | 82 | // overflow 83 | assert(uint64(math.MaxUint64), dMAX) 84 | 85 | // errors 86 | assert(nil, experr(dZero, `cannot convert (type ) to time.Duration`)) 87 | assert("foo", experr(dZero, `cannot parse "foo" (type string) as time.Duration`)) 88 | assert("tooLong", experr( 89 | dZero, `cannot parse "tooLong" (type string) as time.Duration`)) 90 | assert(struct{}{}, experr( 91 | dZero, `cannot convert struct {}{} (type struct {}) to `)) 92 | assert([]string{"1s"}, experr( 93 | dZero, `cannot convert []string{"1s"} (type []string) to `)) 94 | assert([]string{}, experr( 95 | dZero, `cannot convert []string{} (type []string) to `)) 96 | } 97 | -------------------------------------------------------------------------------- /internal/testconv/float.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func RunFloat32Tests(t *testing.T, fn func(interface{}) (float32, error)) { 11 | RunTest(t, reflect.Float32, func(v interface{}) (interface{}, error) { 12 | return fn(v) 13 | }) 14 | } 15 | 16 | func RunFloat64Tests(t *testing.T, fn func(interface{}) (float64, error)) { 17 | RunTest(t, reflect.Float64, func(v interface{}) (interface{}, error) { 18 | return fn(v) 19 | }) 20 | } 21 | 22 | type testFloat64Converter float64 23 | 24 | func (t testFloat64Converter) Float64() (float64, error) { 25 | return float64(t) + 5, nil 26 | } 27 | 28 | func init() { 29 | type ulyFloat32 float32 30 | type ulyFloat64 float64 31 | 32 | exp := func(e32 float32, e64 float64) []Expecter { 33 | return []Expecter{Float32Exp{e32}, Float64Exp{e64}} 34 | } 35 | experrs := func(s string) []Expecter { 36 | return []Expecter{experr(float32(0), s), experr(float64(0), s)} 37 | } 38 | 39 | // basics 40 | assert(0, exp(0, 0)) 41 | assert(1, exp(1, 1)) 42 | assert(false, exp(0, 0)) 43 | assert(true, exp(1, 1)) 44 | assert("false", exp(0, 0)) 45 | assert("true", exp(1, 1)) 46 | 47 | // test length kinds 48 | assert([]string{"one", "two"}, exp(2, 2)) 49 | assert(map[int]string{1: "one", 2: "two"}, exp(2, 2)) 50 | 51 | // test implements Float64(float64, error) 52 | assert(testFloat64Converter(5), exp(10, 10)) 53 | 54 | // max bounds 55 | assert(math.MaxFloat32, exp(math.MaxFloat32, math.MaxFloat32)) 56 | assert(math.MaxFloat64, exp(math.MaxFloat32, math.MaxFloat64)) 57 | 58 | // min bounds 59 | assert(-math.MaxFloat32, exp(-math.MaxFloat32, -math.MaxFloat32)) 60 | assert(-math.MaxFloat64, exp(-math.MaxFloat32, -math.MaxFloat64)) 61 | 62 | // ints 63 | assert(int(10), exp(10, float64(10))) 64 | assert(int8(10), exp(10, float64(10))) 65 | assert(int16(10), exp(10, float64(10))) 66 | assert(int32(10), exp(10, float64(10))) 67 | assert(int64(10), exp(10, float64(10))) 68 | 69 | // uints 70 | assert(uint(10), exp(10, float64(10))) 71 | assert(uint8(10), exp(10, float64(10))) 72 | assert(uint16(10), exp(10, float64(10))) 73 | assert(uint32(10), exp(10, float64(10))) 74 | assert(uint64(10), exp(10, float64(10))) 75 | 76 | // perms of various type 77 | for i := float32(-3.0); i < 3.0; i += .5 { 78 | 79 | // underlying 80 | assert(ulyFloat32(i), exp(i, float64(i))) 81 | assert(ulyFloat64(i), exp(i, float64(i))) 82 | 83 | // implements 84 | assert(testFloat64Converter(i), exp(i+5, float64(i+5))) 85 | assert(testFloat64Converter(ulyFloat64(i)), exp(i+5, float64(i+5))) 86 | 87 | // floats 88 | assert(i, exp(i, float64(i))) 89 | assert(float64(i), exp(i, float64(i))) 90 | 91 | // complex 92 | assert(complex(i, 0), exp(i, float64(i))) 93 | assert(complex(float64(i), 0), exp(i, float64(i))) 94 | 95 | // from string int 96 | assert(fmt.Sprintf("%#v", i), exp(i, float64(i))) 97 | assert(testStringConverter(fmt.Sprintf("%#v", i)), exp(i, float64(i))) 98 | 99 | // from string float form 100 | assert(fmt.Sprintf("%#v", i), exp(i, float64(i))) 101 | } 102 | 103 | assert("foo", experrs(`cannot convert "foo" (type string) to `)) 104 | assert(struct{}{}, experrs(`cannot convert struct {}{} (type struct {}) to `)) 105 | assert(nil, experrs(`cannot convert (type ) to `)) 106 | 107 | } 108 | -------------------------------------------------------------------------------- /internal/testconv/infer.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func RunInferTests(t *testing.T, fn func(into, from interface{}) error) { 9 | // Should work with all other assertions 10 | t.Run("Interface Assertions", func(t *testing.T) { 11 | for _, a := range assertions { 12 | for _, e := range a.Exps { 13 | 14 | // Create a pointer from the exp value for inference 15 | intoVal := reflect.New(reflect.ValueOf(e.Value()).Type()) 16 | 17 | // Infer conversion & dereference. 18 | err := fn(intoVal.Interface(), a.From) 19 | got := intoVal.Elem().Interface() 20 | 21 | if err = e.Expect(got, err); err != nil { 22 | t.Fatalf("(FAIL) %v %v\n%v", a, e, err) 23 | } else { 24 | t.Logf("(PASS) %v %v", a, e) 25 | } 26 | } 27 | } 28 | }) 29 | 30 | t.Run("Reflection Assertions", func(t *testing.T) { 31 | for _, a := range assertions { 32 | for _, e := range a.Exps { 33 | 34 | // Create a pointer from the exp value for inference 35 | intoVal := reflect.New(reflect.ValueOf(e.Value()).Type()) 36 | 37 | // Infer conversion & dereference. 38 | err := fn(intoVal, a.From) 39 | got := intoVal.Elem().Interface() 40 | 41 | if err = e.Expect(got, err); err != nil { 42 | t.Fatalf("(FAIL) %v %v\n%v", a, e, err) 43 | } else { 44 | t.Logf("(PASS) %v %v", a, e) 45 | } 46 | } 47 | } 48 | }) 49 | 50 | // Touch the negative cases 51 | t.Run("Negative", func(t *testing.T) { 52 | type negativeTest struct { 53 | into, from interface{} 54 | } 55 | tests := []negativeTest{ 56 | {nil, nil}, // kind invalid 57 | {nil, (interface{})(nil)}, // from kind 58 | {0, nil}, // non ptr 59 | {&struct{}{}, nil}, // struct fallthrough 60 | {reflect.ValueOf(nil), nil}, // value invalid 61 | {reflect.ValueOf("5"), nil}, // value not settable 62 | {(*complex128)(nil), nil}, 63 | {(*interface{})(nil), nil}, 64 | {(*interface{})(nil), (interface{})(nil)}, 65 | {nil, new(int)}, 66 | {new(int), nil}, 67 | } 68 | for _, test := range tests { 69 | if err := fn(test.into, test.from); err == nil { 70 | t.Fatalf("(FAIL) exp non-nil error for %v -> %v", test.into, test.from) 71 | } 72 | } 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /internal/testconv/int.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func RunIntTests(t *testing.T, fn func(interface{}) (int, error)) { 11 | RunTest(t, reflect.Int, func(v interface{}) (interface{}, error) { 12 | return fn(v) 13 | }) 14 | } 15 | 16 | func RunInt8Tests(t *testing.T, fn func(interface{}) (int8, error)) { 17 | RunTest(t, reflect.Int8, func(v interface{}) (interface{}, error) { 18 | return fn(v) 19 | }) 20 | } 21 | 22 | func RunInt16Tests(t *testing.T, fn func(interface{}) (int16, error)) { 23 | RunTest(t, reflect.Int16, func(v interface{}) (interface{}, error) { 24 | return fn(v) 25 | }) 26 | } 27 | 28 | func RunInt32Tests(t *testing.T, fn func(interface{}) (int32, error)) { 29 | RunTest(t, reflect.Int32, func(v interface{}) (interface{}, error) { 30 | return fn(v) 31 | }) 32 | } 33 | 34 | func RunInt64Tests(t *testing.T, fn func(interface{}) (int64, error)) { 35 | RunTest(t, reflect.Int64, func(v interface{}) (interface{}, error) { 36 | return fn(v) 37 | }) 38 | } 39 | 40 | // Numeric constants for testing. 41 | const ( 42 | IntSize int = 32 << (^uint(0) >> 63) 43 | MinUint uint = 0 44 | MaxUint uint = ^MinUint 45 | MaxInt int = int(MaxUint >> 1) 46 | MinInt int = ^MaxInt 47 | ) 48 | 49 | type testInt64Converter int64 50 | 51 | func (t testInt64Converter) Int64() (int64, error) { 52 | return int64(t) + 5, nil 53 | } 54 | 55 | func init() { 56 | type ulyInt int 57 | type ulyInt8 int8 58 | type ulyInt16 int8 59 | type ulyInt32 int8 60 | type ulyInt64 int64 61 | 62 | exp := func(e int, e8 int8, e16 int16, e32 int32, e64 int64) []Expecter { 63 | return []Expecter{Exp{e}, Exp{e8}, Exp{e16}, Exp{e32}, Exp{e64}} 64 | } 65 | experrs := func(s string) []Expecter { 66 | return []Expecter{ 67 | experr(int(0), s), experr(int8(0), s), experr(int16(0), s), 68 | experr(int32(0), s), experr(int64(0), s)} 69 | } 70 | 71 | // basics 72 | assert(-1, exp(-1, -1, -1, -1, -1)) 73 | assert(0, exp(0, 0, 0, 0, 0)) 74 | assert(1, exp(1, 1, 1, 1, 1)) 75 | assert(false, exp(0, 0, 0, 0, 0)) 76 | assert(true, exp(1, 1, 1, 1, 1)) 77 | assert("false", exp(0, 0, 0, 0, 0)) 78 | assert("true", exp(1, 1, 1, 1, 1)) 79 | 80 | // test length kinds 81 | assert([]string{"one", "two"}, 2, 2, 2, 2, 2) 82 | assert(map[int]string{1: "one", 2: "two"}, 2, 2, 2, 2, 2) 83 | 84 | // test implements Int64(int64, error) 85 | assert(testInt64Converter(5), 10, 10, 10, 10, 10) 86 | 87 | // overflow 88 | assert(uint64(math.MaxUint64), exp(MaxInt, math.MaxInt8, 89 | math.MaxInt16, math.MaxInt32, math.MaxInt64)) 90 | 91 | // underflow 92 | assert(int64(math.MinInt64), exp(MinInt, math.MinInt8, math.MinInt16, 93 | math.MinInt32, math.MinInt64)) 94 | 95 | // max bounds 96 | assert(math.MaxInt8, exp(math.MaxInt8, math.MaxInt8, math.MaxInt8, 97 | math.MaxInt8, math.MaxInt8)) 98 | assert(math.MaxInt16, exp(math.MaxInt16, math.MaxInt8, math.MaxInt16, 99 | math.MaxInt16, math.MaxInt16)) 100 | assert(math.MaxInt32, exp(math.MaxInt32, math.MaxInt8, math.MaxInt16, 101 | math.MaxInt32, math.MaxInt32)) 102 | assert(math.MaxInt64, exp(MaxInt, math.MaxInt8, math.MaxInt16, 103 | math.MaxInt32, math.MaxInt64)) 104 | 105 | // min bounds 106 | assert(math.MinInt8, exp(math.MinInt8, math.MinInt8, math.MinInt8, 107 | math.MinInt8, math.MinInt8)) 108 | assert(math.MinInt16, exp(math.MinInt16, math.MinInt8, math.MinInt16, 109 | math.MinInt16, math.MinInt16)) 110 | assert(math.MinInt32, exp(math.MinInt32, math.MinInt8, math.MinInt16, 111 | math.MinInt32, math.MinInt32)) 112 | assert(int64(math.MinInt64), exp(MinInt, math.MinInt8, math.MinInt16, 113 | math.MinInt32, math.MinInt64)) 114 | 115 | // perms of various type 116 | for i := int(math.MinInt8); i < math.MaxInt8; i += 0xB { 117 | 118 | // uints 119 | if i > 0 { 120 | assert(uint(i), i, int8(i), int16(i), int32(i), int64(i)) 121 | assert(uint8(i), i, int8(i), int16(i), int32(i), int64(i)) 122 | assert(uint16(i), i, int8(i), int16(i), int32(i), int64(i)) 123 | assert(uint32(i), i, int8(i), int16(i), int32(i), int64(i)) 124 | assert(uint64(i), i, int8(i), int16(i), int32(i), int64(i)) 125 | } 126 | 127 | // underlying 128 | assert(ulyInt(i), i, int8(i), int16(i), int32(i), int64(i)) 129 | assert(ulyInt8(i), i, int8(i), int16(i), int32(i), int64(i)) 130 | assert(ulyInt16(i), i, int8(i), int16(i), int32(i), int64(i)) 131 | assert(ulyInt32(i), i, int8(i), int16(i), int32(i), int64(i)) 132 | assert(ulyInt64(i), i, int8(i), int16(i), int32(i), int64(i)) 133 | 134 | // implements 135 | if i < math.MaxInt8-5 { 136 | assert(testInt64Converter(i), 137 | i+5, int8(i+5), int16(i+5), int32(i+5), int64(i+5)) 138 | assert(testInt64Converter(ulyInt(i)), 139 | i+5, int8(i+5), int16(i+5), int32(i+5), int64(i+5)) 140 | } 141 | 142 | // ints 143 | assert(i, i, int8(i), int16(i), int32(i), int64(i)) 144 | assert(int8(i), i, int8(i), int16(i), int32(i), int64(i)) 145 | assert(int16(i), i, int8(i), int16(i), int32(i), int64(i)) 146 | assert(int32(i), i, int8(i), int16(i), int32(i), int64(i)) 147 | assert(int64(i), i, int8(i), int16(i), int32(i), int64(i)) 148 | 149 | // floats 150 | assert(float32(i), i, int8(i), int16(i), int32(i), int64(i)) 151 | assert(float64(i), i, int8(i), int16(i), int32(i), int64(i)) 152 | 153 | // complex 154 | assert(complex(float32(i), 0), 155 | i, int8(i), int16(i), int32(i), int64(i)) 156 | assert(complex(float64(i), 0), 157 | i, int8(i), int16(i), int32(i), int64(i)) 158 | 159 | // from string int 160 | assert(fmt.Sprintf("%d", i), 161 | i, int8(i), int16(i), int32(i), int64(i)) 162 | assert(testStringConverter(fmt.Sprintf("%d", i)), 163 | i, int8(i), int16(i), int32(i), int64(i)) 164 | 165 | // from string float form 166 | assert(fmt.Sprintf("%d.0", i), 167 | i, int8(i), int16(i), int32(i), int64(i)) 168 | } 169 | 170 | assert("foo", experrs(`"foo" (type string) `)) 171 | assert(struct{}{}, experrs(`cannot convert struct {}{} (type struct {}) to `)) 172 | assert(nil, experrs(`cannot convert (type ) to `)) 173 | } 174 | -------------------------------------------------------------------------------- /internal/testconv/map.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | // import ( 4 | // "fmt" 5 | // "reflect" 6 | // "strconv" 7 | // "testing" 8 | // 9 | // "github.com/cstockton/go-conv/internal/generated" 10 | // "github.com/cstockton/go-conv/internal/refutil" 11 | // ) 12 | // 13 | // func RunMapTests(t *testing.T, fn func(into, from interface{}) error) { 14 | // t.Run("Smoke", func(t *testing.T) { 15 | // t.Run("StringIntMapFromStrings", func(t *testing.T) { 16 | // into := make(map[string]int64) 17 | // err := fn(into, []string{"12", "345", "6789"}) 18 | // if err != nil { 19 | // t.Error(err) 20 | // } 21 | // 22 | // exp := map[string]int64{"0": 12, "1": 345, "2": 6789} 23 | // if !reflect.DeepEqual(exp, into) { 24 | // t.Fatalf("exp (%T) --> %[1]v != %v <-- (%[2]T) got", exp, into) 25 | // } 26 | // 27 | // err = fn(nil, []string{"12", "345", "6789"}) 28 | // if err == nil { 29 | // t.Error("expected non-nil err") 30 | // } 31 | // }) 32 | // t.Run("StringIntPtrMapFromStrings", func(t *testing.T) { 33 | // i1, i2, i3 := new(int64), new(int64), new(int64) 34 | // *i1, *i2, *i3 = 12, 345, 6789 35 | // exp := map[string]*int64{"0": i1, "1": i2, "2": i3} 36 | // 37 | // into := make(map[string]*int64) 38 | // err := fn(into, []string{"12", "345", "6789"}) 39 | // if err != nil { 40 | // t.Error(err) 41 | // } 42 | // if !reflect.DeepEqual(exp, into) { 43 | // t.Fatalf("exp (%T) --> %[1]v != %v <-- (%[2]T) got", exp, into) 44 | // } 45 | // into = make(map[string]*int64) 46 | // err = fn(into, []string{"12", "345", "6789"}) 47 | // if err != nil { 48 | // t.Error(err) 49 | // } 50 | // if !reflect.DeepEqual(exp, into) { 51 | // t.Fatalf("exp (%T) --> %[1]v != %v <-- (%[2]T) got", exp, into) 52 | // } 53 | // }) 54 | // }) 55 | // 56 | // // tests all supported sources 57 | // for _, test := range generated.NewMapTests() { 58 | // from, exp := test.From, test.Exp 59 | // run := func(into interface{}) { 60 | // name := fmt.Sprintf(`From(%T)/Into(%T)`, from, into) 61 | // t.Run(name, func(t *testing.T) { 62 | // err := fn(into, from) 63 | // if err != nil { 64 | // t.Error(err) 65 | // } 66 | // 67 | // if !reflect.DeepEqual(exp, refutil.Indirect(into)) { 68 | // t.Logf("from (%T) --> %[1]v", from) 69 | // t.Fatalf("\nexp (%T) --> %[1]v\ngot (%[2]T) --> %[2]v", exp, into) 70 | // } 71 | // }) 72 | // } 73 | // 74 | // // Test the normal type 75 | // run(test.Into) 76 | // 77 | // typ := reflect.TypeOf(test.Into) 78 | // mapVal, mapValPtr := reflect.MakeMap(typ), reflect.New(typ) 79 | // mapValPtr.Elem().Set(mapVal) 80 | // 81 | // // Ensure pointer to a map works as well. 82 | // run(mapValPtr.Interface()) 83 | // } 84 | // } 85 | // 86 | // // Summary: Not much of a tax here, about 2x as slow. 87 | // // 88 | // // BenchmarkMap// to /Conv: 89 | // // Measures the most convenient form of conversion using this library. 90 | // // 91 | // // BenchmarkSlice// to /Conv: 92 | // // Measures using the library only for the conversion, looping for apending. 93 | // // 94 | // // BenchmarkSlice// to /Conv: 95 | // // Measures not using this library at all, pure Go implementation. 96 | // // 97 | // // BenchmarkMap/Length(1024)/[]string_to_map[int]string/Conv-24 1000 1321364 ns/op 98 | // // BenchmarkMap/Length(1024)/[]string_to_map[int]string/LoopConv-24 2000 896001 ns/op 99 | // // BenchmarkMap/Length(1024)/[]string_to_map[int]string/LoopStdlib-24 2000 652117 ns/op 100 | // // BenchmarkMap/Length(64)/[]string_to_map[int]string/Conv-24 20000 74431 ns/op 101 | // // BenchmarkMap/Length(64)/[]string_to_map[int]string/LoopConv-24 20000 56702 ns/op 102 | // // BenchmarkMap/Length(64)/[]string_to_map[int]string/LoopStdlib-24 30000 44191 ns/op 103 | // // BenchmarkMap/Length(16)/[]string_to_map[int]string/Conv-24 100000 18422 ns/op 104 | // // BenchmarkMap/Length(16)/[]string_to_map[int]string/LoopConv-24 100000 14193 ns/op 105 | // // BenchmarkMap/Length(16)/[]string_to_map[int]string/LoopStdlib-24 200000 10021 ns/op 106 | // // BenchmarkMap/Length(4)/[]string_to_map[int]string/Conv-24 300000 4402 ns/op 107 | // // BenchmarkMap/Length(4)/[]string_to_map[int]string/LoopConv-24 500000 2783 ns/op 108 | // // BenchmarkMap/Length(4)/[]string_to_map[int]string/LoopStdlib-24 1000000 1986 ns/op 109 | // func RunMapBenchmarks(b *testing.B, fn func(into, from interface{}) error) { 110 | // for _, num := range []int{1024, 64, 16, 4} { 111 | // num := num 112 | // 113 | // // slow down is really tolerable, only a factor of 1-3 tops 114 | // b.Run(fmt.Sprintf("Length(%d)", num), func(b *testing.B) { 115 | // 116 | // b.Run("[]string to map[int]string", func(b *testing.B) { 117 | // strs := make([]string, num) 118 | // for n := 0; n < num; n++ { 119 | // strs[n] = fmt.Sprintf("%v00", n) 120 | // } 121 | // b.ResetTimer() 122 | // 123 | // b.Run("Conv", func(b *testing.B) { 124 | // for i := 0; i < b.N; i++ { 125 | // into := make(map[string]int64) 126 | // err := fn(into, strs) 127 | // if err != nil { 128 | // b.Error(err) 129 | // } 130 | // if len(into) != num { 131 | // b.Error("bad impl") 132 | // } 133 | // } 134 | // }) 135 | // b.Run("Stdlib", func(b *testing.B) { 136 | // for i := 0; i < b.N; i++ { 137 | // into := make(map[string]int64) 138 | // 139 | // for seq, s := range strs { 140 | // k := fmt.Sprintf("%v", seq) 141 | // v, err := strconv.ParseInt(s, 10, 0) 142 | // if err != nil { 143 | // b.Error(err) 144 | // } 145 | // into[k] = v 146 | // } 147 | // if len(into) != num { 148 | // b.Error("bad impl") 149 | // } 150 | // } 151 | // }) 152 | // }) 153 | // }) 154 | // } 155 | // } 156 | -------------------------------------------------------------------------------- /internal/testconv/readme.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "go/doc" 8 | "go/format" 9 | "go/parser" 10 | "go/token" 11 | "io" 12 | "io/ioutil" 13 | "sort" 14 | "strings" 15 | "testing" 16 | "text/template" 17 | ) 18 | 19 | func RunReadmeTest(t *testing.T, srcs ...string) { 20 | fset := token.NewFileSet() 21 | 22 | var examples []*doc.Example 23 | for _, src := range srcs { 24 | astFile, err := parser.ParseFile(fset, src, nil, parser.ParseComments) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | examples = append(examples, doc.Examples(astFile)...) 29 | } 30 | generateReadme(t, fset, examples) 31 | } 32 | 33 | func generateReadme(t *testing.T, fset *token.FileSet, examples []*doc.Example) { 34 | sg := NewSrcGen("README.md") 35 | readmeExamples := make(ReadmeExamples, len(examples)) 36 | 37 | var buf bytes.Buffer 38 | for i, example := range examples { 39 | buf.Reset() 40 | 41 | var code string 42 | if example.Play != nil { 43 | format.Node(&buf, fset, example.Play) 44 | 45 | play, search := buf.String(), "func main() {" 46 | idx := strings.Index(play, search) 47 | if idx == -1 { 48 | t.Fatalf("bad formatting in example %v, could not find main() func", example.Name) 49 | } 50 | code = play[idx+len(search) : len(play)] 51 | } else { 52 | format.Node(&buf, fset, example.Code) 53 | code = buf.String() 54 | } 55 | 56 | code = strings.Trim(code, "\t\r\n{}") 57 | code = rewrap(&buf, "\n > ", code) 58 | if 0 == len(code) { 59 | t.Fatalf("bad formatting in example %v, had no code", example.Name) 60 | } 61 | 62 | output := rewrap(&buf, "\n > ", example.Output) 63 | if 0 == len(output) { 64 | t.Fatalf("bad formatting in example %v, had no output", example.Name) 65 | } 66 | 67 | title := strings.Title(strings.TrimLeft(example.Name, "_")) 68 | if 0 == len(title) { 69 | title = "Overview" 70 | } 71 | 72 | // the header example has no summary 73 | summary := rewrap(&buf, "\n ", example.Doc) 74 | if 0 == len(summary) && title != "Package" { 75 | t.Fatalf("bad formatting in example %v, had no summary", example.Name) 76 | } 77 | 78 | readmeExamples[i] = ReadmeExample{ 79 | Example: example, 80 | Title: title, 81 | Summary: summary, 82 | Code: code, 83 | Output: output, 84 | } 85 | } 86 | 87 | sort.Sort(readmeExamples) 88 | sg.FuncMap["Examples"] = func() []ReadmeExample { 89 | return readmeExamples 90 | } 91 | 92 | if err := sg.Run(); err != nil { 93 | t.Fatal(err) 94 | } 95 | } 96 | 97 | type ReadmeExample struct { 98 | Example *doc.Example 99 | Complete bool 100 | Title string 101 | Summary string 102 | Code string 103 | Output string 104 | } 105 | 106 | type ReadmeExamples []ReadmeExample 107 | 108 | func (r ReadmeExamples) Len() int { return len(r) } 109 | func (r ReadmeExamples) Swap(i, j int) { r[i], r[j] = r[j], r[i] } 110 | func (r ReadmeExamples) Less(i, j int) bool { return r[i].Example.Order < r[j].Example.Order } 111 | 112 | func rewrap(buf *bytes.Buffer, with, s string) string { 113 | buf.Reset() 114 | if len(s) == 0 { 115 | return `` 116 | } 117 | for i := 1; i < len(s); i++ { 118 | if s[i-1] == '\n' { 119 | if s[i] == '\t' { 120 | i++ 121 | } 122 | buf.WriteString(with) 123 | continue 124 | } 125 | buf.WriteByte(s[i-1]) 126 | } 127 | if end := s[len(s)-1]; end != '\n' { 128 | buf.WriteByte(end) 129 | } 130 | return buf.String() 131 | } 132 | 133 | type SrcGen struct { 134 | t *testing.T 135 | Disabled bool 136 | Data interface{} 137 | FuncMap template.FuncMap 138 | Name string 139 | SrcPath string 140 | SrcBytes []byte 141 | TplPath string 142 | TplBytes []byte // Actual template bytes 143 | TplGenBytes []byte // Bytes produced from executed template 144 | } 145 | 146 | func NewSrcGen(name string) *SrcGen { 147 | funcMap := make(template.FuncMap) 148 | funcMap["args"] = func(s ...interface{}) interface{} { 149 | return s 150 | } 151 | funcMap["TrimSpace"] = strings.TrimSpace 152 | return &SrcGen{Name: "README.md", FuncMap: funcMap} 153 | } 154 | 155 | func (g *SrcGen) Run() error { 156 | if g.Disabled { 157 | return fmt.Errorf(`error: run failed because Disabled field is set for "%s"`, g.Name) 158 | } 159 | firstErr := func(funcs ...func() error) (err error) { 160 | for _, f := range funcs { 161 | err = f() 162 | if err != nil { 163 | return 164 | } 165 | } 166 | return 167 | } 168 | return firstErr(g.Check, g.Load, g.Generate, g.Format, g.Commit) 169 | } 170 | 171 | func (g *SrcGen) Check() error { 172 | g.Name = strings.TrimSpace(g.Name) 173 | g.TplPath = strings.TrimSpace(g.TplPath) 174 | g.SrcPath = strings.TrimSpace(g.SrcPath) 175 | if len(g.Name) == 0 { 176 | return errors.New("error: check for Name field failed because it was empty") 177 | } 178 | if len(g.TplPath) == 0 { 179 | g.TplPath = fmt.Sprintf(`internal/testdata/%s.tpl`, g.Name) 180 | } 181 | if len(g.SrcPath) == 0 { 182 | g.SrcPath = fmt.Sprintf(`%s`, g.Name) 183 | } 184 | return nil 185 | } 186 | 187 | func (g *SrcGen) Load() error { 188 | var err error 189 | if g.TplBytes, err = ioutil.ReadFile(g.TplPath); err != nil { 190 | return fmt.Errorf(`error: load io error "%s" reading TplPath "%s"`, err, g.TplPath) 191 | } 192 | if g.SrcBytes, err = ioutil.ReadFile(g.SrcPath); err != nil { 193 | return fmt.Errorf(`error: load io error "%s" reading SrcPath "%s"`, err, g.SrcPath) 194 | } 195 | return nil 196 | } 197 | 198 | func (g *SrcGen) Generate() error { 199 | tpl, err := template.New(g.Name).Funcs(g.FuncMap).Parse(string(g.TplBytes)) 200 | if err != nil { 201 | return fmt.Errorf(`error: generate error "%s" parsing TplPath "%s"`, err, g.TplPath) 202 | } 203 | 204 | var buf bytes.Buffer 205 | if err = tpl.Execute(&buf, g.Data); err != nil { 206 | return fmt.Errorf(`error: generate error "%s" executing TplPath "%s"`, err, g.TplPath) 207 | } 208 | g.TplGenBytes = buf.Bytes() 209 | return nil 210 | } 211 | 212 | func (g *SrcGen) Format() error { 213 | 214 | // Only run gofmt for .go source code. 215 | if !strings.HasSuffix(g.SrcPath, ".go") { 216 | return nil 217 | } 218 | 219 | fmtBytes, err := format.Source(g.TplGenBytes) 220 | if err != nil { 221 | return fmt.Errorf(`error: format error "%s" executing TplPath "%s"`, err, g.TplPath) 222 | } 223 | g.TplGenBytes = fmtBytes 224 | return err 225 | } 226 | 227 | func (g *SrcGen) IsStale() bool { 228 | return !bytes.Equal(g.SrcBytes, g.TplGenBytes) 229 | } 230 | 231 | func (g *SrcGen) Dump(w io.Writer) string { 232 | sep := strings.Repeat("-", 80) 233 | fmt.Fprintf(w, "%[1]s\n TplBytes:\n%[1]s\n%s\n%[1]s\n", sep, g.TplBytes) 234 | fmt.Fprintf(w, " SrcBytes:\n%[1]s\n%s\n%[1]s\n", sep, g.SrcBytes) 235 | fmt.Fprintf(w, " TplGenBytes (IsStale: %v):\n%s\n%[3]s\n%[2]s\n", 236 | g.IsStale(), sep, g.TplGenBytes) 237 | return g.Name 238 | } 239 | 240 | func (g *SrcGen) String() string { 241 | return g.Name 242 | } 243 | 244 | func (g *SrcGen) Commit() error { 245 | if !g.IsStale() { 246 | return nil 247 | } 248 | return ioutil.WriteFile(g.SrcPath, g.TplGenBytes, 0644) 249 | } 250 | -------------------------------------------------------------------------------- /internal/testconv/slice.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | // import ( 4 | // "fmt" 5 | // "reflect" 6 | // "strconv" 7 | // "testing" 8 | // 9 | // "github.com/cstockton/go-conv/internal/generated" 10 | // "github.com/cstockton/go-conv/internal/refutil" 11 | // ) 12 | // 13 | // func RunSliceTests(t *testing.T, fn func(into, from interface{}) error) { 14 | // t.Run("Smoke", func(t *testing.T) { 15 | // t.Run("IntSliceFromStrings", func(t *testing.T) { 16 | // var into []int 17 | // exp := []int{12, 345, 6789} 18 | // 19 | // err := fn(&into, []string{"12", "345", "6789"}) 20 | // if err != nil { 21 | // t.Error(err) 22 | // } 23 | // if !reflect.DeepEqual(exp, into) { 24 | // t.Fatalf("exp (%T) --> %[1]v != %v <-- (%[2]T) got", exp, into) 25 | // } 26 | // 27 | // err = fn(nil, []string{"12", "345", "6789"}) 28 | // if err == nil { 29 | // t.Error("expected non-nil err") 30 | // } 31 | // }) 32 | // t.Run("IntPtrSliceFromStrings", func(t *testing.T) { 33 | // var into []*int 34 | // i1, i2, i3 := new(int), new(int), new(int) 35 | // *i1, *i2, *i3 = 12, 345, 6789 36 | // exp := []*int{i1, i2, i3} 37 | // 38 | // err := fn(&into, []string{"12", "345", "6789"}) 39 | // if err != nil { 40 | // t.Error(err) 41 | // } 42 | // if !reflect.DeepEqual(exp, into) { 43 | // t.Fatalf("exp --> (%T) %#[1]v != %T %#[2]v <-- got", exp, into) 44 | // } 45 | // 46 | // into = []*int{} 47 | // err = fn(&into, []string{"12", "345", "6789"}) 48 | // if err != nil { 49 | // t.Error(err) 50 | // } 51 | // if !reflect.DeepEqual(exp, into) { 52 | // t.Fatalf("exp --> (%T) %#[1]v != %T %#[2]v <-- got", exp, into) 53 | // } 54 | // }) 55 | // }) 56 | // 57 | // // tests all supported sources 58 | // for _, test := range generated.NewSliceTests() { 59 | // into, from, exp := test.Into, test.From, test.Exp 60 | // 61 | // name := fmt.Sprintf(`From(%T)/Into(%T)`, from, into) 62 | // t.Run(name, func(t *testing.T) { 63 | // err := fn(into, from) 64 | // if err != nil { 65 | // t.Error(err) 66 | // } 67 | // 68 | // if !reflect.DeepEqual(exp, refutil.Indirect(into)) { 69 | // t.Logf("from (%T) --> %[1]v", from) 70 | // t.Fatalf("\nexp (%T) --> %[1]v\ngot (%[2]T) --> %[2]v", exp, into) 71 | // } 72 | // }) 73 | // } 74 | // } 75 | // 76 | // // Summary: 77 | // // 78 | // // BenchmarkSlice// to /Conv: 79 | // // Measures the most convenient form of conversion using this library. 80 | // // 81 | // // BenchmarkSlice// to /Conv: 82 | // // Measures using the library only for the conversion, looping for apending. 83 | // // 84 | // // BenchmarkSlice// to /Conv: 85 | // // Measures not using this library at all, pure Go implementation. 86 | // // 87 | // func RunSliceBenchmarks(b *testing.B, fn func(into, from interface{}) error) { 88 | // for _, num := range []int{1024, 64, 16, 4} { 89 | // num := num 90 | // 91 | // // slow down is really tolerable, only a factor of 1-3 tops 92 | // b.Run(fmt.Sprintf("Length(%d)", num), func(b *testing.B) { 93 | // 94 | // b.Run("[]string to []int64", func(b *testing.B) { 95 | // strs := make([]string, num) 96 | // for n := 0; n < num; n++ { 97 | // strs[n] = fmt.Sprintf("%v00", n) 98 | // } 99 | // b.ResetTimer() 100 | // 101 | // b.Run("Conv", func(b *testing.B) { 102 | // for i := 0; i < b.N; i++ { 103 | // var into []int64 104 | // err := fn(&into, strs) 105 | // if err != nil { 106 | // b.Error(err) 107 | // } 108 | // if len(into) != num { 109 | // b.Error("bad impl") 110 | // } 111 | // } 112 | // }) 113 | // b.Run("Stdlib", func(b *testing.B) { 114 | // for i := 0; i < b.N; i++ { 115 | // var into []int64 116 | // 117 | // for _, s := range strs { 118 | // v, err := strconv.ParseInt(s, 10, 0) 119 | // if err != nil { 120 | // b.Error(err) 121 | // } 122 | // into = append(into, v) 123 | // } 124 | // if len(into) != num { 125 | // b.Error("bad impl") 126 | // } 127 | // } 128 | // }) 129 | // }) 130 | // 131 | // b.Run("[]string to []*int64", func(b *testing.B) { 132 | // strs := make([]string, num) 133 | // for n := 0; n < num; n++ { 134 | // strs[n] = fmt.Sprintf("%v00", n) 135 | // } 136 | // b.ResetTimer() 137 | // 138 | // b.Run("Library", func(b *testing.B) { 139 | // for i := 0; i < b.N; i++ { 140 | // var into []*int64 141 | // err := fn(&into, strs) 142 | // if err != nil { 143 | // b.Error(err) 144 | // } 145 | // if len(into) != num { 146 | // b.Error("bad impl") 147 | // } 148 | // } 149 | // }) 150 | // b.Run("Stdlib", func(b *testing.B) { 151 | // for i := 0; i < b.N; i++ { 152 | // into := new([]*int64) 153 | // 154 | // for _, s := range strs { 155 | // v, err := strconv.ParseInt(s, 10, 0) 156 | // if err != nil { 157 | // b.Error(err) 158 | // } 159 | // *into = append(*into, &v) 160 | // } 161 | // if len(*into) != num { 162 | // b.Error("bad impl") 163 | // } 164 | // } 165 | // }) 166 | // }) 167 | // }) 168 | // } 169 | // } 170 | -------------------------------------------------------------------------------- /internal/testconv/string.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func RunStringTests(t *testing.T, fn func(interface{}) (string, error)) { 9 | RunTest(t, reflect.String, func(v interface{}) (interface{}, error) { 10 | return fn(v) 11 | }) 12 | } 13 | 14 | type testStringConverter string 15 | 16 | // @TODO Check Stringer before Converter interface, or after? 17 | func (t testStringConverter) String() (string, error) { 18 | return string(t) + "Tested", nil 19 | } 20 | 21 | func init() { 22 | 23 | // basic 24 | assert(`hello`, `hello`) 25 | assert(``, ``) 26 | assert([]byte(`hello`), `hello`) 27 | assert([]byte(``), ``) 28 | 29 | // ptr indirection 30 | assert(new(string), ``) 31 | assert(new([]byte), ``) 32 | 33 | // underlying string 34 | type ulyString string 35 | assert(ulyString(`hello`), `hello`) 36 | assert(ulyString(``), ``) 37 | 38 | // implements string converter 39 | assert(testStringConverter(`hello`), `helloTested`) 40 | assert(testStringConverter(`hello`), `helloTested`) 41 | } 42 | -------------------------------------------------------------------------------- /internal/testconv/struct.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | // TODO(cstockton): Consider adding back struct/slice/map conversion. Currently 4 | // removed because the rules are really nuanced and I don't want to define all 5 | // the edge cases right now for them. 6 | -------------------------------------------------------------------------------- /internal/testconv/testconv.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func RunTest(t *testing.T, k reflect.Kind, fn func(interface{}) (interface{}, error)) { 9 | t.Run(k.String(), func(t *testing.T) { 10 | if n := assertions.EachOf(k, func(a *Assertion, e Expecter) { 11 | if err := e.Expect(fn(a.From)); err != nil { 12 | t.Errorf("(FAIL) %v %v\n%v", a, e, err) 13 | } else { 14 | t.Logf("(PASS) %v %v", a, e) 15 | } 16 | }); n < 1 { 17 | t.Fatalf("no test coverage ran for %v conversions", k) 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/testconv/time.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func RunTimeTests(t *testing.T, fn func(interface{}) (time.Time, error)) { 9 | RunTest(t, TimeKind, func(v interface{}) (interface{}, error) { 10 | return fn(v) 11 | }) 12 | } 13 | 14 | type testTimeConverter time.Time 15 | 16 | func (t testTimeConverter) Time() (time.Time, error) { 17 | return time.Time(t).Add(time.Minute), nil 18 | } 19 | 20 | func init() { 21 | var ( 22 | emptyTime time.Time 23 | t2006 = time.Date(2006, time.January, 2, 15, 4, 5, 999999999, time.UTC) 24 | ) 25 | 26 | // basic 27 | assert(time.Time{}, time.Time{}) 28 | assert(new(time.Time), time.Time{}) 29 | assert(t2006, t2006) 30 | 31 | // strings 32 | fmts := []string{ 33 | "02 Jan 06 15:04:05", 34 | "2 Jan 2006 15:04:05", 35 | "2 Jan 2006 15:04:05 -0700 (UTC)", 36 | "02 Jan 2006 15:04 UTC", 37 | "02 Jan 2006 15:04:05 UTC", 38 | "02 Jan 2006 15:04:05 -0700 (UTC)", 39 | "Mon, 2 Jan 15:04:05 UTC 2006", 40 | "Mon, 2 Jan 15:04:05 UTC 2006", 41 | "Mon, 02 Jan 2006 15:04:05", 42 | "Mon, 02 Jan 2006 15:04:05 (UTC)", 43 | "Mon, 2 Jan 2006 15:04:05", 44 | } 45 | for _, s := range fmts { 46 | assert(s, TimeExp{Moment: t2006.Truncate(time.Minute), Truncate: time.Minute}) 47 | assert(testStringConverter(s), TimeExp{Moment: t2006.Truncate(time.Minute), Truncate: time.Minute}) 48 | } 49 | 50 | // underlying 51 | type ulyTime time.Time 52 | assert(ulyTime(t2006), t2006) 53 | assert(ulyTime(t2006), t2006) 54 | 55 | // implements converter 56 | assert(testTimeConverter(t2006), t2006.Add(time.Minute)) 57 | 58 | // embedded time 59 | type embedTime struct{ time.Time } 60 | assert(embedTime{t2006}, t2006) 61 | 62 | // errors 63 | assert(nil, experr(emptyTime, `cannot convert (type ) to time.Time`)) 64 | assert("foo", experr(emptyTime, `cannot convert "foo" (type string) to time.Time`)) 65 | assert("tooLong", experr( 66 | emptyTime, `cannot convert "tooLong" (type string) to time.Time`)) 67 | assert(struct{}{}, experr( 68 | emptyTime, `cannot convert struct {}{} (type struct {}) to `)) 69 | assert([]string{"1s"}, experr( 70 | emptyTime, `cannot convert []string{"1s"} (type []string) to `)) 71 | assert([]string{}, experr( 72 | emptyTime, `cannot convert []string{} (type []string) to `)) 73 | } 74 | -------------------------------------------------------------------------------- /internal/testconv/uint.go: -------------------------------------------------------------------------------- 1 | package testconv 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func RunUintTests(t *testing.T, fn func(interface{}) (uint, error)) { 11 | RunTest(t, reflect.Uint, func(v interface{}) (interface{}, error) { 12 | return fn(v) 13 | }) 14 | } 15 | 16 | func RunUint8Tests(t *testing.T, fn func(interface{}) (uint8, error)) { 17 | RunTest(t, reflect.Uint8, func(v interface{}) (interface{}, error) { 18 | return fn(v) 19 | }) 20 | } 21 | 22 | func RunUint16Tests(t *testing.T, fn func(interface{}) (uint16, error)) { 23 | RunTest(t, reflect.Uint16, func(v interface{}) (interface{}, error) { 24 | return fn(v) 25 | }) 26 | } 27 | 28 | func RunUint32Tests(t *testing.T, fn func(interface{}) (uint32, error)) { 29 | RunTest(t, reflect.Uint32, func(v interface{}) (interface{}, error) { 30 | return fn(v) 31 | }) 32 | } 33 | 34 | func RunUint64Tests(t *testing.T, fn func(interface{}) (uint64, error)) { 35 | RunTest(t, reflect.Uint64, func(v interface{}) (interface{}, error) { 36 | return fn(v) 37 | }) 38 | } 39 | 40 | type testUint64Converter uint64 41 | 42 | func (t testUint64Converter) Uint64() (uint64, error) { 43 | return uint64(t) + 5, nil 44 | } 45 | 46 | func init() { 47 | type ulyUint uint 48 | type ulyUint8 uint8 49 | type ulyUint16 uint8 50 | type ulyUint32 uint8 51 | type ulyUint64 uint64 52 | 53 | exp := func(e uint, e8 uint8, e16 uint16, e32 uint32, e64 uint64) []Expecter { 54 | return []Expecter{Exp{e}, Exp{e8}, Exp{e16}, Exp{e32}, Exp{e64}} 55 | } 56 | experrs := func(s string) []Expecter { 57 | return []Expecter{ 58 | experr(uint(0), s), experr(uint8(0), s), experr(uint16(0), s), 59 | experr(uint32(0), s), experr(uint64(0), s)} 60 | } 61 | 62 | // basics 63 | assert(0, exp(0, 0, 0, 0, 0)) 64 | assert(1, exp(1, 1, 1, 1, 1)) 65 | assert(false, exp(0, 0, 0, 0, 0)) 66 | assert(true, exp(1, 1, 1, 1, 1)) 67 | assert("false", exp(0, 0, 0, 0, 0)) 68 | assert("true", exp(1, 1, 1, 1, 1)) 69 | 70 | // test length kinds 71 | assert([]string{"one", "two"}, exp(2, 2, 2, 2, 2)) 72 | assert(map[int]string{1: "one", 2: "two"}, exp(2, 2, 2, 2, 2)) 73 | 74 | // test implements Uint64(uint64, error) 75 | assert(testUint64Converter(5), exp(10, 10, 10, 10, 10)) 76 | 77 | // max bounds 78 | assert(math.MaxUint8, exp(math.MaxUint8, math.MaxUint8, math.MaxUint8, 79 | math.MaxUint8, math.MaxUint8)) 80 | assert(math.MaxUint16, exp(math.MaxUint16, math.MaxUint8, math.MaxUint16, 81 | math.MaxUint16, math.MaxUint16)) 82 | assert(math.MaxUint32, exp(math.MaxUint32, math.MaxUint8, math.MaxUint16, 83 | math.MaxUint32, math.MaxUint32)) 84 | assert(uint64(math.MaxUint64), exp(MaxUint, math.MaxUint8, 85 | math.MaxUint16, math.MaxUint32, uint64(math.MaxUint64))) 86 | 87 | // min bounds 88 | assert(math.MinInt8, exp(0, 0, 0, 0, 0)) 89 | assert(math.MinInt16, exp(0, 0, 0, 0, 0)) 90 | assert(math.MinInt32, exp(0, 0, 0, 0, 0)) 91 | assert(int64(math.MinInt64), exp(0, 0, 0, 0, 0)) 92 | 93 | // perms of various type 94 | for n := uint(0); n < math.MaxUint8; n += 0xB { 95 | i := n 96 | 97 | // uints 98 | if n < 1 { 99 | i = 0 100 | } else { 101 | assert(uintptr(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 102 | assert(i, i, uint8(i), uint16(i), uint32(i), uint64(i)) 103 | assert(uint8(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 104 | assert(uint16(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 105 | assert(uint32(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 106 | assert(uint64(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 107 | } 108 | 109 | // underlying 110 | assert(ulyUint(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 111 | assert(ulyUint8(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 112 | assert(ulyUint16(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 113 | assert(ulyUint32(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 114 | assert(ulyUint64(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 115 | 116 | // implements 117 | if i < math.MaxUint8-5 { 118 | assert(testUint64Converter(i), 119 | i+5, uint8(i+5), uint16(i+5), uint32(i+5), uint64(i+5)) 120 | assert(testUint64Converter(ulyUint(i)), 121 | i+5, uint8(i+5), uint16(i+5), uint32(i+5), uint64(i+5)) 122 | } 123 | 124 | // ints 125 | if i < math.MaxInt8 { 126 | assert(int(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 127 | assert(int8(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 128 | assert(int16(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 129 | assert(int32(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 130 | assert(int64(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 131 | } 132 | 133 | // floats 134 | assert(float32(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 135 | assert(float64(i), i, uint8(i), uint16(i), uint32(i), uint64(i)) 136 | 137 | // complex 138 | assert(complex(float32(i), 0), 139 | i, uint8(i), uint16(i), uint32(i), uint64(i)) 140 | assert(complex(float64(i), 0), 141 | i, uint8(i), uint16(i), uint32(i), uint64(i)) 142 | 143 | // from string int 144 | assert(fmt.Sprintf("%d", i), 145 | i, uint8(i), uint16(i), uint32(i), uint64(i)) 146 | assert(testStringConverter(fmt.Sprintf("%d", i)), 147 | i, uint8(i), uint16(i), uint32(i), uint64(i)) 148 | 149 | // from string float form 150 | assert(fmt.Sprintf("%d.0", i), 151 | i, uint8(i), uint16(i), uint32(i), uint64(i)) 152 | } 153 | 154 | assert(nil, experrs(`cannot convert (type ) to `)) 155 | assert("foo", experrs(` "foo" (type string) `)) 156 | assert(struct{}{}, experrs(`cannot convert struct {}{} (type struct {}) to `)) 157 | } 158 | -------------------------------------------------------------------------------- /internal/testdata/README.md.tpl: -------------------------------------------------------------------------------- 1 | # Go Package: conv 2 | 3 | [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/cstockton/go-conv) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/cstockton/go-conv?style=flat-square)](https://goreportcard.com/report/github.com/cstockton/go-conv) 5 | [![Coverage Status](https://img.shields.io/codecov/c/github/cstockton/go-conv/master.svg?style=flat-square)](https://codecov.io/github/cstockton/go-conv?branch=master) 6 | [![Build Status](http://img.shields.io/travis/cstockton/go-conv.svg?style=flat-square)](https://travis-ci.org/cstockton/go-conv) 7 | [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/cstockton/go-conv/master/LICENSE) 8 | 9 | > Get: 10 | > ```bash 11 | > go get -u github.com/cstockton/go-conv 12 | > ``` 13 | > 14 | > Example: 15 | > ```Go 16 | > // Basic types 17 | > if got, err := conv.Bool(`TRUE`); err == nil { 18 | > fmt.Printf("conv.Bool(`TRUE`)\n -> %v\n", got) 19 | > } 20 | > if got, err := conv.Duration(`1m2s`); err == nil { 21 | > fmt.Printf("conv.Duration(`1m2s`)\n -> %v\n", got) 22 | > } 23 | > var date time.Time 24 | > err := conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`) 25 | > fmt.Printf("conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`)\n -> %v\n", got) 26 | > ``` 27 | > 28 | > Output: 29 | > ```Go 30 | > conv.Bool(`TRUE`) 31 | > -> true 32 | > conv.Duration(`1m2s`) 33 | > -> 1m2s 34 | > conv.Infer(&date, `Sat Mar 7 11:06:39 PST 2015`) 35 | > -> 2015-03-07 11:06:39 +0000 PST 36 | > ``` 37 | 38 | 39 | ## Intro 40 | 41 | **Notice:** If you begin getting compilation errors use the v1 import path `gopkg.in/cstockton/go-conv.v1` for an immediate fix and to future-proof. 42 | 43 | Package conv provides fast and intuitive conversions across Go types. This library uses reflection to be robust but will bypass it for common conversions, for example string conversion to any type will never use reflection. All functions are safe for concurrent use by multiple Goroutines. 44 | 45 | {{ range Examples -}} 46 | ### {{ .Title }} 47 | 48 | {{ .Summary }} 49 | 50 | > Example: 51 | > ```Go 52 | > {{ .Code }} 53 | > ``` 54 | > 55 | > Output: 56 | > ```Go 57 | > {{ .Output }} 58 | > ``` 59 | 60 | 61 | {{ end -}} 62 | 63 | ## Contributing 64 | 65 | Feel free to create issues for bugs, please ensure code coverage remains 100% 66 | with any pull requests. 67 | 68 | 69 | ## Bugs and Patches 70 | 71 | Feel free to report bugs and submit pull requests. 72 | 73 | * bugs: 74 | 75 | * patches: 76 | 77 | --------------------------------------------------------------------------------