bind scalar
This commit is contained in:
commit
88bc6f6ab7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.cover
|
||||
27
Makefile
Normal file
27
Makefile
Normal file
@ -0,0 +1,27 @@
|
||||
SOURCES = $(shell find . -name "*.go")
|
||||
|
||||
default: build
|
||||
|
||||
lib: $(SOURCES)
|
||||
go build
|
||||
|
||||
build: lib
|
||||
|
||||
check: $(SOURCES) build
|
||||
go test -count 1
|
||||
|
||||
.cover: $(SOURCES) build
|
||||
go test -count 1 -coverprofile .cover
|
||||
|
||||
cover: .cover
|
||||
go tool cover -func .cover
|
||||
|
||||
showcover: .cover
|
||||
go tool cover -html .cover
|
||||
|
||||
fmt: $(SOURCES)
|
||||
go fmt
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm -f .cover
|
||||
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module code.squareroundforest.org/arpio/bind
|
||||
|
||||
go 1.25.0
|
||||
68
lib.go
Normal file
68
lib.go
Normal file
@ -0,0 +1,68 @@
|
||||
// provides more flexible and permissive ways of setting values than reflect.Value.Set
|
||||
package bind
|
||||
|
||||
type Field struct {
|
||||
name string
|
||||
path []string
|
||||
list bool
|
||||
value any
|
||||
}
|
||||
|
||||
// the receiver must be addressable
|
||||
func BindScalar(receiver any, value ...any) bool {
|
||||
return bindScalarReflect(receiver, value)
|
||||
}
|
||||
|
||||
func BindScalarCreate[T any](value ...any) (T, bool) {
|
||||
return bindScalarCreateReflect[T](value)
|
||||
}
|
||||
|
||||
func FieldValue(path []string, value any) Field {
|
||||
return Field{path: path, value: value}
|
||||
}
|
||||
|
||||
func FieldValueByName(name string, value any) Field {
|
||||
return Field{name: name, value: value}
|
||||
}
|
||||
|
||||
func (f Field) Path() []string {
|
||||
p := make([]string, len(f.path))
|
||||
copy(p, f.path)
|
||||
return p
|
||||
}
|
||||
|
||||
func (f Field) Name() string { return f.name }
|
||||
func (f Field) List() bool { return f.list }
|
||||
func (f Field) Value() any { return f.value }
|
||||
|
||||
func Fields[T any]() []Field {
|
||||
return nil
|
||||
}
|
||||
|
||||
func FieldValues(structure any) []Field {
|
||||
return nil
|
||||
}
|
||||
|
||||
func BindFields(structure any, values []Field) []Field {
|
||||
return nil
|
||||
}
|
||||
|
||||
func BindFieldsCreate[T any](values []Field) (any, []Field) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func AcceptsScalar[T any]() bool {
|
||||
return acceptsScalarReflect[T]()
|
||||
}
|
||||
|
||||
func AcceptsFields[T any]() bool {
|
||||
return acceptsFieldsReflect[T]()
|
||||
}
|
||||
|
||||
func AcceptsList[T any]() bool {
|
||||
return acceptsListReflect[T]()
|
||||
}
|
||||
|
||||
func Bindable[T any]() bool {
|
||||
return bindableReflect[T]()
|
||||
}
|
||||
78
scalar.go
Normal file
78
scalar.go
Normal file
@ -0,0 +1,78 @@
|
||||
package bind
|
||||
|
||||
import "reflect"
|
||||
|
||||
func bindScalar(receiver reflect.Value, values []any) bool {
|
||||
if len(values) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if !acceptsScalar(receiver.Type()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(values) == 1 {
|
||||
receiver = unpackValue(receiver, pointer|iface|slice)
|
||||
r := reflect.ValueOf(values[0])
|
||||
r = unpackValue(r, pointer|iface|slice)
|
||||
v, ok := scan(receiver.Type(), r.Interface())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !receiver.CanSet() {
|
||||
return false
|
||||
}
|
||||
|
||||
receiver.Set(reflect.ValueOf(v))
|
||||
return true
|
||||
}
|
||||
|
||||
receiver = unpackValue(receiver, pointer|iface|anytype)
|
||||
if receiver.Kind() != reflect.Slice || receiver.Len() < len(values) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range values {
|
||||
if !bindScalar(receiver.Index(i), []any{values[i]}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func bindScalarCreate(t reflect.Type, values []any) (reflect.Value, bool) {
|
||||
if len(values) == 0 {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
if !acceptsScalar(t) {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
receiver, ok := allocate(t, len(values))
|
||||
if !ok {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
if !bindScalar(receiver, values) {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
return receiver, true
|
||||
}
|
||||
|
||||
func bindScalarReflect(receiver any, values []any) bool {
|
||||
return bindScalar(reflect.ValueOf(receiver), values)
|
||||
}
|
||||
|
||||
func bindScalarCreateReflect[T any](values []any) (T, bool) {
|
||||
v, ok := bindScalarCreate(reflect.TypeFor[T](), values)
|
||||
if !ok {
|
||||
var v T
|
||||
return v, false
|
||||
}
|
||||
|
||||
return v.Interface().(T), true
|
||||
}
|
||||
206
scalar_test.go
Normal file
206
scalar_test.go
Normal file
@ -0,0 +1,206 @@
|
||||
package bind_test
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/bind"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type valuer interface {
|
||||
value() int
|
||||
}
|
||||
|
||||
type counter interface {
|
||||
count() int
|
||||
}
|
||||
|
||||
type countedSlice []int
|
||||
|
||||
type valueInt int
|
||||
|
||||
func (s countedSlice) count() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (i *valueInt) value() int {
|
||||
return int(*i)
|
||||
}
|
||||
|
||||
func TestScalar(t *testing.T) {
|
||||
t.Run("bind scalar", func(t *testing.T) {
|
||||
t.Run("no value", func(t *testing.T) {
|
||||
var i int
|
||||
if bind.BindScalar(&i) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("not scalar", func(t *testing.T) {
|
||||
var s struct{ Foo int }
|
||||
if bind.BindScalar(&s, "42") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cannot scan", func(t *testing.T) {
|
||||
var i int
|
||||
if bind.BindScalar(i, "foo") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("receiver cannot be set", func(t *testing.T) {
|
||||
var i int
|
||||
if bind.BindScalar(i, "42") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("one value set", func(t *testing.T) {
|
||||
var i int
|
||||
if !bind.BindScalar(&i, "42") || i != 42 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple values, not slice", func(t *testing.T) {
|
||||
var i int
|
||||
if bind.BindScalar(&i, "21", "42", "84") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple values, small slice", func(t *testing.T) {
|
||||
s := make([]int, 2)
|
||||
if bind.BindScalar(&s, "21", "42", "84") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple values set", func(t *testing.T) {
|
||||
s := make([]int, 3)
|
||||
if !bind.BindScalar(&s, "21", "42", "84") || !slices.Equal(s, []int{21, 42, 84}) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple values set, larger slice", func(t *testing.T) {
|
||||
s := make([]int, 4)
|
||||
if !bind.BindScalar(&s, "21", "42", "84") || !slices.Equal(s, []int{21, 42, 84, 0}) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple values, cannot scan", func(t *testing.T) {
|
||||
s := make([]int, 3)
|
||||
if bind.BindScalar(&s, "21", "42", "foo") {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("set item of a slice", func(t *testing.T) {
|
||||
s := []int{21, 42, 84}
|
||||
if !bind.BindScalar(&s, "27") || !slices.Equal(s, []int{27, 42, 84}) {
|
||||
t.Fatal(s)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("interface receiver", func(t *testing.T) {
|
||||
var i any
|
||||
if !bind.BindScalar(&i, 42) || i != 42 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("interface receiver has value", func(t *testing.T) {
|
||||
var (
|
||||
i int
|
||||
iface any
|
||||
)
|
||||
|
||||
i = 21
|
||||
iface = &i
|
||||
if ok := bind.BindScalar(&iface, "42"); !ok || iface != "42" || i != 21 {
|
||||
t.Fatal(ok, iface)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrapped with non-empty interface", func(t *testing.T) {
|
||||
// this could work in theory, but fails on checking the type of the receiver
|
||||
// we leave the test undefiend, and checking only for no panic
|
||||
var iface valuer
|
||||
i := valueInt(21)
|
||||
p := &i
|
||||
iface = p
|
||||
ok := bind.BindScalar(&iface, "42")
|
||||
if ok != (i == 42) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("slice wrapped by empty interface", func(t *testing.T) {
|
||||
var iface any
|
||||
s := make([]int, 3)
|
||||
iface = s
|
||||
if ok := bind.BindScalar(&iface, "21", "42", "84"); !ok || !slices.Equal(s, []int{21, 42, 84}) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bind scalar with create", func(t *testing.T) {
|
||||
t.Run("no value", func(t *testing.T) {
|
||||
if _, ok := bind.BindScalarCreate[int](); ok {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not accept scalar", func(t *testing.T) {
|
||||
if _, ok := bind.BindScalarCreate[struct{Foo int}]("42"); ok {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty interface", func(t *testing.T) {
|
||||
if v, ok := bind.BindScalarCreate[any]("42"); !ok || v != "42" {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scalar", func(t *testing.T) {
|
||||
if v, ok := bind.BindScalarCreate[int]("42"); !ok || v != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("slice", func(t *testing.T) {
|
||||
if v, ok := bind.BindScalarCreate[[]int]("21", "42", "84"); !ok || !slices.Equal(v, []int{21, 42, 84}) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("slice with non-scalar type", func(t *testing.T) {
|
||||
if _, ok := bind.BindScalarCreate[[]func(int)]("21", "42", "84"); ok {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer to non-scalar", func(t *testing.T) {
|
||||
if _, ok := bind.BindScalarCreate[*func()]("42"); ok {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
if v, ok := bind.BindScalarCreate[*int]("42"); !ok || *v != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unscannable", func(t *testing.T) {
|
||||
if _, ok := bind.BindScalarCreate[int]("foo"); ok {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
82
scan.go
Normal file
82
scan.go
Normal file
@ -0,0 +1,82 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func intParse[T any](parse func(string, int, int) (T, error), s string, byteSize int) (T, error) {
|
||||
bitSize := byteSize * 8
|
||||
switch {
|
||||
case strings.HasPrefix(s, "0b"):
|
||||
return parse(s[2:], 2, bitSize)
|
||||
case strings.HasPrefix(s, "0x"):
|
||||
return parse(s[2:], 16, bitSize)
|
||||
case strings.HasPrefix(s, "0"):
|
||||
return parse(s[1:], 8, bitSize)
|
||||
default:
|
||||
return parse(s, 10, bitSize)
|
||||
}
|
||||
}
|
||||
|
||||
func parseInt(s string, byteSize int) (int64, error) {
|
||||
return intParse(strconv.ParseInt, s, byteSize)
|
||||
}
|
||||
|
||||
func parseUint(s string, byteSize int) (uint64, error) {
|
||||
return intParse(strconv.ParseUint, s, byteSize)
|
||||
}
|
||||
|
||||
func scanConvert(t reflect.Type, v any) (any, bool) {
|
||||
r := reflect.ValueOf(v)
|
||||
if !r.CanConvert(t) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return r.Convert(t).Interface(), true
|
||||
}
|
||||
|
||||
func scanString(t reflect.Type, s string) (any, bool) {
|
||||
var (
|
||||
v any
|
||||
err error
|
||||
)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
v, err = strconv.ParseBool(s)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
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:
|
||||
v, err = strconv.ParseFloat(s, int(t.Size())*8)
|
||||
case reflect.String:
|
||||
v = s
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
p := reflect.New(t)
|
||||
p.Elem().Set(reflect.ValueOf(v).Convert(t))
|
||||
return p.Elem().Interface(), true
|
||||
}
|
||||
|
||||
func scan(t reflect.Type, v any) (any, bool) {
|
||||
if vv, ok := scanConvert(t, v); ok {
|
||||
return vv, true
|
||||
}
|
||||
|
||||
r := reflect.ValueOf(v)
|
||||
if r.Kind() != reflect.String {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return scanString(t, fmt.Sprint(v))
|
||||
}
|
||||
120
scan_test.go
Normal file
120
scan_test.go
Normal file
@ -0,0 +1,120 @@
|
||||
package bind_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"code.squareroundforest.org/arpio/bind"
|
||||
)
|
||||
|
||||
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
|
||||
bind.BindScalar(&i64, i)
|
||||
if i64 != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unscannable", func(t *testing.T) {
|
||||
var s string
|
||||
if bind.BindScalar(&s, func() string { return "" }) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("bool", func(t *testing.T) {
|
||||
var b bool
|
||||
if !bind.BindScalar(&b, "true") || !b {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
var i int
|
||||
if !bind.BindScalar(&i, "42") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("int sized", func(t *testing.T) {
|
||||
var i int8
|
||||
if !bind.BindScalar(&i, "42") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uint", func(t *testing.T) {
|
||||
var i uint
|
||||
if !bind.BindScalar(&i, "42") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uint sized", func(t *testing.T) {
|
||||
var i uint8
|
||||
if !bind.BindScalar(&i, "42") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
var i int
|
||||
if !bind.BindScalar(&i, "0b101010") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("octal", func(t *testing.T) {
|
||||
var i int
|
||||
if !bind.BindScalar(&i, "052") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hexa", func(t *testing.T) {
|
||||
var i int
|
||||
if !bind.BindScalar(&i, "0x2a") || i != 42 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("num parse failing", func(t *testing.T) {
|
||||
i := 21
|
||||
if bind.BindScalar(&i, "foo") || i != 21 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("float", func(t *testing.T) {
|
||||
var f float64
|
||||
if !bind.BindScalar(&f, "2.41") || f != 2.41 {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
var s string
|
||||
if !bind.BindScalar(&s, "foo bar baz") || s != "foo bar baz" {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("interface", func(t *testing.T) {
|
||||
var i any
|
||||
if !bind.BindScalar(&i, "foo bar baz") || i != "foo bar baz" {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
}
|
||||
168
type.go
Normal file
168
type.go
Normal file
@ -0,0 +1,168 @@
|
||||
package bind
|
||||
|
||||
import "reflect"
|
||||
|
||||
type unpackFlag int
|
||||
|
||||
const (
|
||||
pointer unpackFlag = 1 << iota
|
||||
slice
|
||||
anytype
|
||||
iface
|
||||
)
|
||||
|
||||
func (f unpackFlag) has(v unpackFlag) bool {
|
||||
return f&v > 0
|
||||
}
|
||||
|
||||
func isAny(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Interface && t.NumMethod() == 0
|
||||
}
|
||||
|
||||
func isInterface(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Interface && t.NumMethod() > 0
|
||||
}
|
||||
|
||||
func isScalar(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Bool,
|
||||
reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.String:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isScalarMap(t reflect.Type) bool {
|
||||
if t.Kind() != reflect.Map {
|
||||
return false
|
||||
}
|
||||
|
||||
key := unpackType(t.Key(), pointer)
|
||||
if key.Kind() != reflect.String {
|
||||
return false
|
||||
}
|
||||
|
||||
value := unpackType(t.Elem(), pointer|slice)
|
||||
return isAny(value) || isScalar(value)
|
||||
}
|
||||
|
||||
func unpackType(t reflect.Type, unpack unpackFlag) reflect.Type {
|
||||
if unpack.has(pointer) && t.Kind() == reflect.Pointer {
|
||||
return unpackType(t.Elem(), unpack)
|
||||
}
|
||||
|
||||
if unpack.has(slice) && t.Kind() == reflect.Slice {
|
||||
return unpackType(t.Elem(), unpack)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func unpackValue(v reflect.Value, unpack unpackFlag) reflect.Value {
|
||||
if unpack.has(pointer) && v.Kind() == reflect.Pointer {
|
||||
return unpackValue(v.Elem(), unpack)
|
||||
}
|
||||
|
||||
if unpack.has(slice) && v.Kind() == reflect.Slice && v.Len() > 0 {
|
||||
return unpackValue(v.Index(0), unpack)
|
||||
}
|
||||
|
||||
if unpack.has(anytype) && isAny(v.Type()) && !v.IsNil() {
|
||||
return unpackValue(v.Elem(), unpack)
|
||||
}
|
||||
|
||||
if unpack.has(iface) && isInterface(v.Type()) && !v.IsNil() {
|
||||
return unpackValue(v.Elem(), unpack)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func acceptsScalar(t reflect.Type) bool {
|
||||
t = unpackType(t, pointer|slice)
|
||||
return isAny(t) || isScalar(t)
|
||||
}
|
||||
|
||||
func acceptsFields(t reflect.Type) bool {
|
||||
t = unpackType(t, pointer|slice)
|
||||
return t.Kind() == reflect.Struct || isScalarMap(t)
|
||||
}
|
||||
|
||||
func acceptsList(t reflect.Type) bool {
|
||||
if !bindable(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
t = unpackType(t, pointer)
|
||||
return t.Kind() == reflect.Slice
|
||||
}
|
||||
|
||||
func bindable(t reflect.Type) bool {
|
||||
return acceptsScalar(t) || acceptsFields(t)
|
||||
}
|
||||
|
||||
func acceptsScalarReflect[T any]() bool {
|
||||
return acceptsScalar(reflect.TypeFor[T]())
|
||||
}
|
||||
|
||||
func acceptsFieldsReflect[T any]() bool {
|
||||
return acceptsFields(reflect.TypeFor[T]())
|
||||
}
|
||||
|
||||
func acceptsListReflect[T any]() bool {
|
||||
return acceptsList(reflect.TypeFor[T]())
|
||||
}
|
||||
|
||||
func bindableReflect[T any]() bool {
|
||||
return bindable(reflect.TypeFor[T]())
|
||||
}
|
||||
|
||||
// expected to be called with types that can pass the bindable check
|
||||
func allocate(t reflect.Type, len int) (reflect.Value, bool) {
|
||||
if len == 0 {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
if isAny(t) || isScalar(t) || t.Kind() == reflect.Struct || isScalarMap(t) {
|
||||
p := reflect.New(t)
|
||||
return p.Elem(), len == 1
|
||||
}
|
||||
|
||||
if t.Kind() == reflect.Slice {
|
||||
l := reflect.MakeSlice(t, len, len)
|
||||
for i := 0; i < len; i++ {
|
||||
e, ok := allocate(t.Elem(), 1)
|
||||
if !ok {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
l.Index(i).Set(e)
|
||||
}
|
||||
|
||||
return l, true
|
||||
}
|
||||
|
||||
// must be pointer
|
||||
e, ok := allocate(t.Elem(), len)
|
||||
if !ok {
|
||||
return reflect.Zero(t), false
|
||||
}
|
||||
|
||||
p := reflect.New(t.Elem())
|
||||
p.Elem().Set(e)
|
||||
return p, true
|
||||
}
|
||||
278
type_test.go
Normal file
278
type_test.go
Normal file
@ -0,0 +1,278 @@
|
||||
package bind_test
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/bind"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypeChecks(t *testing.T) {
|
||||
t.Run("accepts scalar", func(t *testing.T) {
|
||||
t.Run("bool", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[bool]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uint", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[uint]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("float", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("string", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[bool]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("any", func(t *testing.T) {
|
||||
if !bind.AcceptsScalar[any]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
type p *int
|
||||
if !bind.AcceptsScalar[p]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("slice", func(t *testing.T) {
|
||||
type s []int
|
||||
if !bind.AcceptsScalar[s]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer and slice combined", func(t *testing.T) {
|
||||
type c *[]*[]int
|
||||
if !bind.AcceptsScalar[c]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
type s struct{ Foo int }
|
||||
if bind.AcceptsScalar[s]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
if bind.AcceptsScalar[map[string]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("interface with methods", func(t *testing.T) {
|
||||
type i interface{ Foo(int) }
|
||||
if bind.AcceptsScalar[i]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("func", func(t *testing.T) {
|
||||
if bind.AcceptsScalar[func(int)]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chan", func(t *testing.T) {
|
||||
if bind.AcceptsScalar[chan int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("accepts fields", func(t *testing.T) {
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
type s struct{ Foo int }
|
||||
if !bind.AcceptsFields[s]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
if !bind.AcceptsFields[map[string]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map with interface fields", func(t *testing.T) {
|
||||
if !bind.AcceptsFields[map[string]any]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map with list fields", func(t *testing.T) {
|
||||
if !bind.AcceptsFields[map[string][]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
type p *struct{ Foo int }
|
||||
if !bind.AcceptsFields[p]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("slice", func(t *testing.T) {
|
||||
type s []struct{ Foo int }
|
||||
if !bind.AcceptsFields[s]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer and slice combined", func(t *testing.T) {
|
||||
type c *[]*[]struct{ Foo int }
|
||||
if !bind.AcceptsFields[c]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong map key", func(t *testing.T) {
|
||||
if bind.AcceptsFields[map[int]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong map value", func(t *testing.T) {
|
||||
if bind.AcceptsFields[map[string]struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scalar", func(t *testing.T) {
|
||||
if bind.AcceptsFields[int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("interface", func(t *testing.T) {
|
||||
type i interface{ Foo(int) }
|
||||
if bind.AcceptsFields[i]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("any type", func(t *testing.T) {
|
||||
if bind.AcceptsFields[any]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("func", func(t *testing.T) {
|
||||
if bind.AcceptsFields[func(int)]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chan", func(t *testing.T) {
|
||||
if bind.AcceptsFields[chan int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("accepts list", func(t *testing.T) {
|
||||
t.Run("scalars", func(t *testing.T) {
|
||||
if !bind.AcceptsList[[]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("structs", func(t *testing.T) {
|
||||
if !bind.AcceptsList[[]struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("maps", func(t *testing.T) {
|
||||
if !bind.AcceptsList[[]map[string]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pointer to list", func(t *testing.T) {
|
||||
if !bind.AcceptsList[*[]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scalar", func(t *testing.T) {
|
||||
if bind.AcceptsList[int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
if bind.AcceptsList[struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
if bind.AcceptsList[map[string]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map", func(t *testing.T) {
|
||||
if bind.AcceptsList[map[string]int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("struct pointer", func(t *testing.T) {
|
||||
if bind.AcceptsList[*struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong elem type", func(t *testing.T) {
|
||||
if bind.AcceptsList[map[string]struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bindable", func(t *testing.T) {
|
||||
t.Run("scalar", func(t *testing.T) {
|
||||
if !bind.Bindable[int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
if !bind.Bindable[struct{ Foo int }]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("func", func(t *testing.T) {
|
||||
if bind.Bindable[func()]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chan", func(t *testing.T) {
|
||||
if bind.Bindable[chan int]() {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user