Skip to content

Commit 1a0a47d

Browse files
author
Dean Karn
authored
extend Option.Scan supported types (#43)
1 parent c86a871 commit 1a0a47d

File tree

4 files changed

+115
-4
lines changed

4 files changed

+115
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [5.25.0] - 2024-01-22
10+
### Added
11+
- Add additional `Option.Scan` type support for `sql.Scanner` interface of Uint, Uint16, Uint32, Uint64, Int, Int, Int8, Float32, []byte, json.RawValue.
12+
913
## [5.24.0] - 2024-01-21
1014
### Added
1115
- `appext` package for application level helpers. Specifically added setting up os signal trapping and cancellation of context.Context.
@@ -90,7 +94,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9094
### Added
9195
- Added `timext.NanoTime` for fast low level monotonic time with nanosecond precision.
9296

93-
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.23.0...HEAD
97+
[Unreleased]: https://github.com/go-playground/pkg/compare/v5.25.0...HEAD
98+
[5.25.0]: https://github.com/go-playground/pkg/compare/v5.24.0..v5.25.0
9499
[5.24.0]: https://github.com/go-playground/pkg/compare/v5.23.0..v5.24.0
95100
[5.23.0]: https://github.com/go-playground/pkg/compare/v5.22.0..v5.23.0
96101
[5.22.0]: https://github.com/go-playground/pkg/compare/v5.21.3..v5.22.0

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pkg
22

3-
![Project status](https://img.shields.io/badge/version-5.24.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-5.25.0-green.svg)
44
[![Lint & Test](https://github.com/go-playground/pkg/actions/workflows/go.yml/badge.svg)](https://github.com/go-playground/pkg/actions/workflows/go.yml)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/pkg/badge.svg?branch=master)](https://coveralls.io/github/go-playground/pkg?branch=master)
66
[![GoDoc](https://godoc.org/github.com/go-playground/pkg?status.svg)](https://pkg.go.dev/mod/github.com/go-playground/pkg/v5)

values/option/option.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"database/sql/driver"
99
"encoding/json"
1010
"fmt"
11+
"math"
1112
"reflect"
1213
"time"
1314
)
@@ -225,12 +226,43 @@ func (o *Option[T]) Scan(value any) error {
225226
return err
226227
}
227228
*o = Some(reflect.ValueOf(v.Byte).Convert(val.Type()).Interface().(T))
229+
case reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
230+
v := reflect.ValueOf(value)
231+
if v.Type().ConvertibleTo(val.Type()) {
232+
*o = Some(reflect.ValueOf(v.Convert(val.Type()).Interface()).Interface().(T))
233+
} else {
234+
return fmt.Errorf("value %T not convertable to %T", value, o.value)
235+
}
236+
case reflect.Float32:
237+
var v sql.NullFloat64
238+
if err := v.Scan(value); err != nil {
239+
return err
240+
}
241+
*o = Some(reflect.ValueOf(v.Float64).Convert(val.Type()).Interface().(T))
228242
case reflect.Float64:
229243
var v sql.NullFloat64
230244
if err := v.Scan(value); err != nil {
231245
return err
232246
}
233247
*o = Some(reflect.ValueOf(v.Float64).Convert(val.Type()).Interface().(T))
248+
case reflect.Int:
249+
var v sql.NullInt64
250+
if err := v.Scan(value); err != nil {
251+
return err
252+
}
253+
if v.Int64 > math.MaxInt || v.Int64 < math.MinInt {
254+
return fmt.Errorf("value %d out of range for int", v.Int64)
255+
}
256+
*o = Some(reflect.ValueOf(v.Int64).Convert(val.Type()).Interface().(T))
257+
case reflect.Int8:
258+
var v sql.NullInt64
259+
if err := v.Scan(value); err != nil {
260+
return err
261+
}
262+
if v.Int64 > math.MaxInt8 || v.Int64 < math.MinInt8 {
263+
return fmt.Errorf("value %d out of range for int8", v.Int64)
264+
}
265+
*o = Some(reflect.ValueOf(v.Int64).Convert(val.Type()).Interface().(T))
234266
case reflect.Int16:
235267
var v sql.NullInt16
236268
if err := v.Scan(value); err != nil {
@@ -285,8 +317,12 @@ func (o *Option[T]) Scan(value any) error {
285317
v := reflect.ValueOf(value)
286318

287319
if v.Type().ConvertibleTo(byteSliceType) {
288-
if err := json.Unmarshal(v.Convert(byteSliceType).Interface().([]byte), &o.value); err != nil {
289-
return err
320+
if val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 {
321+
*o = Some(reflect.ValueOf(v.Convert(val.Type()).Interface()).Interface().(T))
322+
} else {
323+
if err := json.Unmarshal(v.Convert(byteSliceType).Interface().([]byte), &o.value); err != nil {
324+
return err
325+
}
290326
}
291327
o.isSome = true
292328
return nil

values/option/option_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package optionext
66
import (
77
"database/sql/driver"
88
"encoding/json"
9+
"math"
910
"reflect"
1011
"testing"
1112
"time"
@@ -222,12 +223,22 @@ func TestSQLScanner(t *testing.T) {
222223
var optionI64 Option[int64]
223224
var optionI32 Option[int32]
224225
var optionI16 Option[int16]
226+
var optionI8 Option[int8]
227+
var optionI Option[int]
225228
var optionString Option[string]
226229
var optionBool Option[bool]
230+
var optionF32 Option[float32]
227231
var optionF64 Option[float64]
228232
var optionByte Option[byte]
229233
var optionTime Option[time.Time]
230234
var optionInterface Option[any]
235+
var optionArrBytes Option[[]byte]
236+
var optionRawMessage Option[json.RawMessage]
237+
var optionUint64 Option[uint64]
238+
var optionUint32 Option[uint32]
239+
var optionUint16 Option[uint16]
240+
var optionUint8 Option[uint8]
241+
var optionUint Option[uint]
231242

232243
err := optionInterface.Scan(1)
233244
Equal(t, err, nil)
@@ -237,6 +248,29 @@ func TestSQLScanner(t *testing.T) {
237248
Equal(t, err, nil)
238249
Equal(t, optionInterface, Some(any("blah")))
239250

251+
err = optionUint64.Scan(uint64(200))
252+
Equal(t, err, nil)
253+
Equal(t, optionUint64, Some(uint64(200)))
254+
255+
err = optionUint32.Scan(uint32(200))
256+
Equal(t, err, nil)
257+
Equal(t, optionUint32, Some(uint32(200)))
258+
259+
err = optionUint16.Scan(uint16(200))
260+
Equal(t, err, nil)
261+
Equal(t, optionUint16, Some(uint16(200)))
262+
263+
err = optionUint8.Scan(uint8(200))
264+
Equal(t, err, nil)
265+
Equal(t, optionUint8, Some(uint8(200)))
266+
267+
err = optionUint.Scan(uint(200))
268+
Equal(t, err, nil)
269+
Equal(t, optionUint, Some(uint(200)))
270+
271+
err = optionUint64.Scan("200")
272+
Equal(t, err.Error(), "value string not convertable to uint64")
273+
240274
err = optionI64.Scan(value)
241275
Equal(t, err, nil)
242276
Equal(t, optionI64, Some(value))
@@ -249,6 +283,18 @@ func TestSQLScanner(t *testing.T) {
249283
Equal(t, err, nil)
250284
Equal(t, optionI16, Some(int16(value)))
251285

286+
err = optionI8.Scan(math.MaxInt32)
287+
Equal(t, err.Error(), "value 2147483647 out of range for int8")
288+
Equal(t, optionI8, None[int8]())
289+
290+
err = optionI8.Scan(int8(3))
291+
Equal(t, err, nil)
292+
Equal(t, optionI8, Some(int8(3)))
293+
294+
err = optionI.Scan(3)
295+
Equal(t, err, nil)
296+
Equal(t, optionI, Some(3))
297+
252298
err = optionBool.Scan(1)
253299
Equal(t, err, nil)
254300
Equal(t, optionBool, Some(true))
@@ -257,6 +303,14 @@ func TestSQLScanner(t *testing.T) {
257303
Equal(t, err, nil)
258304
Equal(t, optionString, Some("123"))
259305

306+
err = optionF32.Scan(float32(2.0))
307+
Equal(t, err, nil)
308+
Equal(t, optionF32, Some(float32(2.0)))
309+
310+
err = optionF32.Scan(math.MaxFloat64)
311+
Equal(t, err, nil)
312+
Equal(t, optionF32, Some(float32(math.Inf(1))))
313+
260314
err = optionF64.Scan(2.0)
261315
Equal(t, err, nil)
262316
Equal(t, optionF64, Some(2.0))
@@ -324,6 +378,22 @@ func TestSQLScanner(t *testing.T) {
324378
err = ct.Scan("test")
325379
Equal(t, err, nil)
326380
Equal(t, ct, Some(customStringType("test")))
381+
382+
err = optionArrBytes.Scan([]byte(`[1,2,3]`))
383+
Equal(t, err, nil)
384+
Equal(t, optionArrBytes, Some([]byte(`[1,2,3]`)))
385+
386+
err = optionArrBytes.Scan([]byte{4, 5, 6})
387+
Equal(t, err, nil)
388+
Equal(t, optionArrBytes, Some([]byte{4, 5, 6}))
389+
390+
err = optionRawMessage.Scan([]byte(`[1,2,3]`))
391+
Equal(t, err, nil)
392+
Equal(t, true, string(optionRawMessage.Unwrap()) == "[1,2,3]")
393+
394+
err = optionRawMessage.Scan([]byte{4, 5, 6})
395+
Equal(t, err, nil)
396+
Equal(t, true, string(optionRawMessage.Unwrap()) == string([]byte{4, 5, 6}))
327397
}
328398

329399
func TestNilOption(t *testing.T) {

0 commit comments

Comments
 (0)