add time support
This commit is contained in:
parent
b0ff605b25
commit
0a5ab05c88
2
field.go
2
field.go
@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/iancoleman/strcase"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func pathString(f Field) string {
|
||||
|
||||
@ -279,7 +279,10 @@ func TestField(t *testing.T) {
|
||||
|
||||
t.Run("bind fields", func(t *testing.T) {
|
||||
t.Run("no circular receiver", func(t *testing.T) {
|
||||
type s struct{Foo *s; Bar int}
|
||||
type s struct {
|
||||
Foo *s
|
||||
Bar int
|
||||
}
|
||||
var v s
|
||||
if len(bind.BindFields(&v, bind.NamedValue("bar", 42))) != 1 {
|
||||
t.Fatal()
|
||||
@ -314,7 +317,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("fail to bind", func(t *testing.T) {
|
||||
type s struct{Foo int; Bar int}
|
||||
type s struct {
|
||||
Foo int
|
||||
Bar int
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -551,7 +557,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("struct fields", func(t *testing.T) {
|
||||
type s struct{Foo int; Bar struct { Baz string }}
|
||||
type s struct {
|
||||
Foo int
|
||||
Bar struct{ Baz string }
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -565,7 +574,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("non-existing field", func(t *testing.T) {
|
||||
type s struct{Foo int; Bar struct { Baz string }}
|
||||
type s struct {
|
||||
Foo int
|
||||
Bar struct{ Baz string }
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -579,7 +591,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("too many fields", func(t *testing.T) {
|
||||
type s struct{Foo int; Bar struct { Baz string }}
|
||||
type s struct {
|
||||
Foo int
|
||||
Bar struct{ Baz string }
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -594,7 +609,11 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("pointer fields", func(t *testing.T) {
|
||||
type s struct{Foo *int; Bar *struct { Baz *string }; Qux *[]struct{Quux string}}
|
||||
type s struct {
|
||||
Foo *int
|
||||
Bar *struct{ Baz *string }
|
||||
Qux *[]struct{ Quux string }
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -609,7 +628,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("unsupported pointer fields", func(t *testing.T) {
|
||||
type s struct{Foo *int; Bar *chan int}
|
||||
type s struct {
|
||||
Foo *int
|
||||
Bar *chan int
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
@ -624,7 +646,10 @@ func TestField(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("struct fields by path", func(t *testing.T) {
|
||||
type s struct{Foo int; Bar struct { Baz string }}
|
||||
type s struct {
|
||||
Foo int
|
||||
Bar struct{ Baz string }
|
||||
}
|
||||
var v s
|
||||
u := bind.BindFields(
|
||||
&v,
|
||||
|
||||
@ -1,7 +1 @@
|
||||
add time and duration support
|
||||
don't change the input value when bind returns false
|
||||
track down the cases when reflect can panic
|
||||
test:
|
||||
- repeated bindings
|
||||
- cases of allocations
|
||||
- preallocated and unallocated list sizes
|
||||
|
||||
44
scan.go
44
scan.go
@ -1,12 +1,36 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var timeLayouts = []string{
|
||||
time.RFC3339,
|
||||
time.RFC3339Nano,
|
||||
time.DateTime,
|
||||
time.DateOnly,
|
||||
time.TimeOnly,
|
||||
time.RFC822,
|
||||
time.RFC822Z,
|
||||
time.RFC850,
|
||||
time.RFC1123,
|
||||
time.RFC1123Z,
|
||||
time.ANSIC,
|
||||
time.UnixDate,
|
||||
time.RubyDate,
|
||||
time.Kitchen,
|
||||
time.Layout,
|
||||
time.Stamp,
|
||||
time.StampMilli,
|
||||
time.StampMicro,
|
||||
time.StampNano,
|
||||
}
|
||||
|
||||
func intParse[T any](parse func(string, int, int) (T, error), s string, byteSize int) (T, error) {
|
||||
bitSize := byteSize * 8
|
||||
switch {
|
||||
@ -38,6 +62,16 @@ func scanConvert(t reflect.Type, v any) (any, bool) {
|
||||
return r.Convert(t).Interface(), true
|
||||
}
|
||||
|
||||
func parseTime(s string) (any, error) {
|
||||
for _, l := range timeLayouts {
|
||||
if t, err := time.Parse(l, s); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}, errors.New("failed to parse time")
|
||||
}
|
||||
|
||||
func scanString(t reflect.Type, s string) (any, bool) {
|
||||
var (
|
||||
v any
|
||||
@ -48,7 +82,13 @@ func scanString(t reflect.Type, s string) (any, bool) {
|
||||
case reflect.Bool:
|
||||
v, err = strconv.ParseBool(s)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if isDuration(t) {
|
||||
v, err = time.ParseDuration(s)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
v, err = parseInt(s, int(t.Size()))
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v, err = parseUint(s, int(t.Size()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
@ -56,8 +96,12 @@ func scanString(t reflect.Type, s string) (any, bool) {
|
||||
case reflect.String:
|
||||
v = s
|
||||
default:
|
||||
if isTime(t) {
|
||||
v, err = parseTime(s)
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, false
|
||||
|
||||
50
scan_test.go
50
scan_test.go
@ -3,21 +3,10 @@ package bind_test
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/bind"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
// conversion
|
||||
// unscannable
|
||||
// bool
|
||||
// int
|
||||
// int sized
|
||||
// uint
|
||||
// uint sized
|
||||
// binary
|
||||
// octal
|
||||
// hexa
|
||||
// num parse failing
|
||||
|
||||
t.Run("conversion", func(t *testing.T) {
|
||||
var i64 int64
|
||||
i := 42
|
||||
@ -117,4 +106,41 @@ func TestScan(t *testing.T) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duration", func(t *testing.T) {
|
||||
var d time.Duration
|
||||
if !bind.BindScalar(&d, "9s") || d != 9*time.Second {
|
||||
t.Fatal(d)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duration convert", func(t *testing.T) {
|
||||
type dur time.Duration
|
||||
var d dur
|
||||
if !bind.BindScalar(&d, "9s") || d != dur(9*time.Second) {
|
||||
t.Fatal(d)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("time rfc", func(t *testing.T) {
|
||||
var tim time.Time
|
||||
if !bind.BindScalar(&tim, "2025-08-31T01:14:55+02:00") || tim.Year() != 2025 {
|
||||
t.Fatal(tim)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("time only", func(t *testing.T) {
|
||||
var tim time.Time
|
||||
if !bind.BindScalar(&tim, "01:14:55") || tim.Second() != 55 {
|
||||
t.Fatal(tim)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("time convert", func(t *testing.T) {
|
||||
type timey time.Time
|
||||
var tim timey
|
||||
if !bind.BindScalar(&tim, "2025-08-31T01:14:55+02:00") || time.Time(tim).Year() != 2025 {
|
||||
t.Fatal(tim)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
15
type.go
15
type.go
@ -1,6 +1,9 @@
|
||||
package bind
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type unpackFlag int
|
||||
|
||||
@ -123,6 +126,14 @@ func isInterface(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Interface && t.NumMethod() > 0
|
||||
}
|
||||
|
||||
func isTime(t reflect.Type) bool {
|
||||
return t.ConvertibleTo(reflect.TypeFor[time.Time]())
|
||||
}
|
||||
|
||||
func isDuration(t reflect.Type) bool {
|
||||
return t.ConvertibleTo(reflect.TypeFor[time.Duration]())
|
||||
}
|
||||
|
||||
func isScalar(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Bool,
|
||||
@ -142,7 +153,7 @@ func isScalar(t reflect.Type) bool {
|
||||
reflect.String:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
return isTime(t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user