From 410e5eae8366eae4cbd1e009ce04ba1ef3bb6ef0 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 6 Mar 2022 17:11:56 +0200 Subject: [PATCH] add a new x/reflex sub-package for reflection common functions - may be improved in the near future --- HISTORY.md | 2 ++ x/reflex/error.go | 8 ++++++ x/reflex/func.go | 44 ++++++++++++++++++++++++++++++ x/reflex/reflectx.go | 19 +++++++++++++ x/reflex/struct.go | 56 ++++++++++++++++++++++++++++++++++++++ x/reflex/types.go | 30 ++++++++++++++++++++ x/reflex/zero.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 224 insertions(+) create mode 100644 x/reflex/error.go create mode 100644 x/reflex/func.go create mode 100644 x/reflex/reflectx.go create mode 100644 x/reflex/struct.go create mode 100644 x/reflex/types.go create mode 100644 x/reflex/zero.go diff --git a/HISTORY.md b/HISTORY.md index ce84760d..63cb2985 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,8 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- Add a new [x/reflex](/x/reflex) sub-package. + - Add `Context.ReadMultipartRelated` as requested at: [issues/#1787](https://github.com/kataras/iris/issues/1787). - Add `Container.DependencyMatcher` and `Dependency.Match` to implement the feature requested at [issues/#1842](https://github.com/kataras/iris/issues/1842). diff --git a/x/reflex/error.go b/x/reflex/error.go new file mode 100644 index 00000000..9939a78c --- /dev/null +++ b/x/reflex/error.go @@ -0,0 +1,8 @@ +package reflex + +import "reflect" + +// IsError reports whether "typ" is an error type. +func IsError(typ interface{ Implements(reflect.Type) bool }) bool { + return typ.Implements(ErrTyp) +} diff --git a/x/reflex/func.go b/x/reflex/func.go new file mode 100644 index 00000000..84a7ae4e --- /dev/null +++ b/x/reflex/func.go @@ -0,0 +1,44 @@ +package reflex + +import "reflect" + +// IsFunc reports whether the "kindable" is a type of function. +func IsFunc(typ interface{ Kind() reflect.Kind }) bool { + return typ.Kind() == reflect.Func +} + +// FuncParam holds the properties of function input or output. +type FuncParam struct { + Index int + Type reflect.Type +} + +// LookupInputs returns the index and type of each function's input argument. +// Panics if "fn" is not a type of Func. +func LookupInputs(fn reflect.Type) []FuncParam { + n := fn.NumIn() + params := make([]FuncParam, 0, n) + for i := 0; i < n; i++ { + in := fn.In(i) + params = append(params, FuncParam{ + Index: i, + Type: in, + }) + } + return params +} + +// LookupOutputs returns the index and type of each function's output argument. +// Panics if "fn" is not a type of Func. +func LookupOutputs(fn reflect.Type) []FuncParam { + n := fn.NumOut() + params := make([]FuncParam, 0, n) + for i := 0; i < n; i++ { + out := fn.Out(i) + params = append(params, FuncParam{ + Index: i, + Type: out, + }) + } + return params +} diff --git a/x/reflex/reflectx.go b/x/reflex/reflectx.go new file mode 100644 index 00000000..44ff24e3 --- /dev/null +++ b/x/reflex/reflectx.go @@ -0,0 +1,19 @@ +package reflex + +import "reflect" + +// IndirectType returns the value of a pointer-type "typ". +// If "IndirectType" is a pointer, array, chan, map or slice it returns its Elem, +// otherwise returns the "typ" as it is. +func IndirectType(typ reflect.Type) reflect.Type { + switch typ.Kind() { + case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return typ.Elem() + } + return typ +} + +// IndirectValue returns the element type (e.g. if pointer of *User it will return the User type). +func IndirectValue(val reflect.Value) reflect.Value { + return reflect.Indirect(val) +} diff --git a/x/reflex/struct.go b/x/reflex/struct.go new file mode 100644 index 00000000..ec901f41 --- /dev/null +++ b/x/reflex/struct.go @@ -0,0 +1,56 @@ +package reflex + +import "reflect" + +// LookupFields returns a slice of all fields containg a struct field +// of the given "fieldTag" of the "typ" struct. The fields returned +// are flatted and reclusive over fields with value of struct. +// Panics if "typ" is not a type of Struct. +func LookupFields(typ reflect.Type, fieldTag string) []reflect.StructField { + fields := lookupFields(typ, fieldTag, nil) + return fields[0:len(fields):len(fields)] +} + +func lookupFields(typ reflect.Type, fieldTag string, parentIndex []int) []reflect.StructField { + n := typ.NumField() + fields := make([]reflect.StructField, 0, n) + checkTag := fieldTag != "" + for i := 0; i < n; i++ { + field := typ.Field(i) + if field.PkgPath != "" { // skip unexported fields. + continue + } + + if checkTag { + if v := field.Tag.Get(fieldTag); v == "" || v == "-" { + // Skip fields that don't contain the 'fieldTag' tag or has '-'. + continue + } + } + + fieldType := IndirectType(field.Type) + + if fieldType.Kind() == reflect.Struct { // It's a struct inside a struct and it's not time, flat it. + if fieldType != TimeType { + structFields := lookupFields(fieldType, fieldTag, append(parentIndex, i)) + if nn := len(structFields); nn > 0 { + fields = append(fields, structFields...) + continue + } + } + } + + index := []int{i} + if len(parentIndex) > 0 { + index = append(parentIndex, i) + } + + tmp := make([]int, len(index)) + copy(tmp, index) + field.Index = tmp + + fields = append(fields, field) + } + + return fields +} diff --git a/x/reflex/types.go b/x/reflex/types.go new file mode 100644 index 00000000..78a66f4c --- /dev/null +++ b/x/reflex/types.go @@ -0,0 +1,30 @@ +package reflex + +import ( + "encoding/json" + "fmt" + "net" + "reflect" + "time" +) + +// Common reflect types for go standard data types. +var ( + StringType = reflect.TypeOf("") + BytesType = reflect.TypeOf([]byte{}) + IntType = reflect.TypeOf(int(0)) + Int16Type = reflect.TypeOf(int16(0)) + Int32Type = reflect.TypeOf(int32(0)) + Int64Type = reflect.TypeOf(int64(0)) + Float32Type = reflect.TypeOf(float32(0)) + Float64Type = reflect.TypeOf(float64(0)) + TimeType = reflect.TypeOf(time.Time{}) + IpTyp = reflect.TypeOf(net.IP{}) + JSONNumberTyp = reflect.TypeOf(json.Number("")) + StringerTyp = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + ArrayIntegerTyp = reflect.TypeOf([]int{}) + ArrayStringTyp = reflect.TypeOf([]string{}) + DoubleArrayIntegerTyp = reflect.TypeOf([][]int{}) + DoubleArrayStringTyp = reflect.TypeOf([][]string{}) + ErrTyp = reflect.TypeOf((*error)(nil)).Elem() +) diff --git a/x/reflex/zero.go b/x/reflex/zero.go new file mode 100644 index 00000000..e7a287cb --- /dev/null +++ b/x/reflex/zero.go @@ -0,0 +1,65 @@ +package reflex + +import ( + "encoding/json" + "net" +) + +// Zeroer can be implemented by custom types +// to report whether its current value is zero. +// Standard Time also implements that. +type Zeroer interface { + IsZero() bool +} + +// IsZero reports whether "v" is zero value or no. +// The given "v" value can complete the Zeroer interface +// which can be used to customize the behavior for each type of "v". +func IsZero(v interface{}) bool { + switch t := v.(type) { + case Zeroer: // completes the time.Time as well. + return t.IsZero() + case string: + return t == "" + case int: + return t == 0 + case int8: + return t == 0 + case int16: + return t == 0 + case int32: + return t == 0 + case int64: + return t == 0 + case uint: + return t == 0 + case uint8: + return t == 0 + case uint16: + return t == 0 + case uint32: + return t == 0 + case uint64: + return t == 0 + case float32: + return t == 0 + case float64: + return t == 0 + case bool: + return !t + case []int: + return len(t) == 0 + case []string: + return len(t) == 0 + case [][]int: + return len(t) == 0 + case [][]string: + return len(t) == 0 + case json.Number: + return t.String() == "" + case net.IP: + return len(t) == 0 + default: + return false + } +}