From cfa9b6e6f790fec62489b430ecadd5c76e4cf2df Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Sun, 31 Aug 2025 02:56:26 +0200 Subject: [PATCH] add Scalar type enumeration --- field.go | 6 +++-- field_test.go | 6 ++--- lib.go | 34 +++++++++++++++++++-------- notes.txt | 6 +++++ type.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 15 deletions(-) diff --git a/field.go b/field.go index f025043..2081a79 100644 --- a/field.go +++ b/field.go @@ -65,7 +65,8 @@ func fields(t reflect.Type) []Field { if acceptsScalar(tfi.Type) { var fi Field ft := unpackType(tfi.Type, pointer|slice) - fi.isBool = ft.Kind() == reflect.Bool + fi.typ = scalarType(ft.Kind()) + fi.size = scalarSize(ft.Kind()) fi.list = acceptsList(tfi.Type) fi.name = strcase.ToKebab(tfi.Name) fi.path = []string{tfi.Name} @@ -104,7 +105,8 @@ func fields(t reflect.Type) []Field { func fieldFromValue(v reflect.Value) Field { var fi Field fi.value = v.Interface() - fi.isBool = v.Kind() == reflect.Bool + fi.typ = scalarType(v.Kind()) + fi.size = valueSize(v) return fi } diff --git a/field_test.go b/field_test.go index 8e5bd79..6c3386b 100644 --- a/field_test.go +++ b/field_test.go @@ -60,7 +60,7 @@ func TestField(t *testing.T) { } que := m["que"] - if !slices.Equal(que.Path(), []string{"Que"}) || !que.Bool() { + if !slices.Equal(que.Path(), []string{"Que"}) || que.Type() != bind.Bool { t.Fatal(que.Name()) } @@ -184,8 +184,8 @@ func TestField(t *testing.T) { v := s{Foo: 42, Bar: true} f := bind.FieldValues(v) if len(f) != 2 || - f[0].Name() != "foo" || !slices.Equal(f[0].Path(), []string{"Foo"}) || f[0].Value() != 42 || f[0].Bool() || - f[1].Name() != "bar" || !slices.Equal(f[1].Path(), []string{"Bar"}) || f[1].Value() != true || !f[1.].Bool() { + f[0].Name() != "foo" || !slices.Equal(f[0].Path(), []string{"Foo"}) || f[0].Value() != 42 || f[0].Type() == bind.Bool || + f[1].Name() != "bar" || !slices.Equal(f[1].Path(), []string{"Bar"}) || f[1].Value() != true || f[1.].Type() != bind.Bool { t.Fatal() } }) diff --git a/lib.go b/lib.go index d4ff89d..17049bd 100644 --- a/lib.go +++ b/lib.go @@ -2,15 +2,28 @@ // circular type structures not supported // it handles scalar fields // primary use cases by design are: command line options, environment variables, ini file fields, URL query parameters, HTTP form values +// traverses pointers, slices, wrapper interfaces package bind +type Scalar int + +const ( + Any Scalar = iota + Bool + Int + Uint + Float + String +) + type Field struct { - path []string - name string - list bool - isBool bool - free bool - value any + path []string + name string + list bool + typ Scalar + size int + free bool + value any } // the receiver must be addressable @@ -44,10 +57,11 @@ func (f Field) Name() string { return nameFromPath(f.path) } -func (f Field) List() bool { return f.list } -func (f Field) Bool() bool { return f.isBool } -func (f Field) Free() bool { return f.free } -func (f Field) Value() any { return f.value } +func (f Field) List() bool { return f.list } +func (f Field) Type() Scalar { return f.typ } +func (f Field) Size() int { return f.size } +func (f Field) Free() bool { return f.free } +func (f Field) Value() any { return f.value } // it does not return fields with free keys, however, this should be obvious // non-struct and non-named map values return unnamed fields diff --git a/notes.txt b/notes.txt index ddac99c..431101c 100644 --- a/notes.txt +++ b/notes.txt @@ -1 +1,7 @@ +add the kind to the field type but with time and duration support. Simplified for ints, uints and floats, with bit size. Skipping the unused ones. With different name, e.g. ScalarType track down the cases when reflect can panic +documentation: +- give a short description for every exported symbol +- start from collecting the docs from the test cases +- extrace the common doc items from the function doc items +doc/test/code triangle diff --git a/type.go b/type.go index b29e8a3..f0b7a5b 100644 --- a/type.go +++ b/type.go @@ -20,6 +20,69 @@ func (f unpackFlag) has(v unpackFlag) bool { return f&v > 0 } +func scalarType(k reflect.Kind) Scalar { + switch k { + case reflect.Bool: + return Bool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return Int + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return Uint + case reflect.Float32, reflect.Float64: + return Float + case reflect.String: + return String + default: + return Any + } +} + +func scalarSize(k reflect.Kind) int { + switch k { + case reflect.Bool: + return 1 + case reflect.Int: + return int(reflect.TypeFor[int]().Size()) * 8 + case reflect.Int8: + return 8 + case reflect.Int16: + return 16 + case reflect.Int32: + return 32 + case reflect.Int64: + return 64 + case reflect.Uint: + return int(reflect.TypeFor[uint]().Size()) * 8 + case reflect.Uint8: + return 8 + case reflect.Uint16: + return 16 + case reflect.Uint32: + return 32 + case reflect.Uint64: + return 64 + case reflect.Float32: + return 32 + case reflect.Float64: + return 64 + case reflect.String: + return -1 + default: + return -1 + } +} + +func valueSize(v reflect.Value) int { + switch v.Kind() { + case reflect.String: + return v.Len() * 8 + case reflect.Interface: + return valueSize(unpackValue(v, pointer|slice|iface|anytype)) + default: + return scalarSize(v.Kind()) + } +} + func setVisited[T comparable](visited map[T]bool, k T) map[T]bool { s := make(map[T]bool) for v := range visited {