4 Commits

Author SHA1 Message Date
amery c94e5e74ad reflection: WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-02 05:07:58 +00:00
amery dd252fafae reflection: add initial New() validating type and triggering a scan
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-02 05:07:58 +00:00
amery f68272eb84 reflection: introduce UnmarshalTypeError{}
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-02 05:07:58 +00:00
amery 1e95acf21d reflection: introduce InvalidUnmarshalError{}
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-01 23:54:14 +00:00
6 changed files with 275 additions and 3 deletions
+5 -3
View File
@@ -2,20 +2,22 @@ module asciigoat.org/core
go 1.19
require github.com/mgechev/revive v1.3.3
require (
github.com/fatih/structtag v1.2.0
github.com/mgechev/revive v1.3.3
github.com/pkg/errors v0.9.1
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/tools v0.12.0 // indirect
)
+69
View File
@@ -0,0 +1,69 @@
package reflection
import (
"bytes"
"fmt"
"reflect"
)
// An InvalidUnmarshalError describes an invalid argument passed to New.
// (It must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Method string
Prefix string
reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
var buf bytes.Buffer
if e.Prefix != "" {
_, _ = fmt.Fprintf(&buf, "%s: ", e.Prefix)
}
method := e.Method
if method == "" {
method = "New"
}
_, _ = fmt.Fprintf(&buf, "%s(", method)
switch {
case e.Type == nil:
_, _ = buf.WriteString("nil")
case e.Type.Kind() != reflect.Pointer:
_, _ = fmt.Fprintf(&buf, "%s %s", "non-pointer", e.Type.String())
default:
_, _ = fmt.Fprintf(&buf, "%s %s", "nil", e.Type.String())
}
_, _ = buf.WriteString(")")
return buf.String()
}
// An UnmarshalTypeError tells something went wrong while processing
// a type.
type UnmarshalTypeError struct {
Prefix string
Err error
reflect.Type
}
func (e UnmarshalTypeError) Unwrap() error {
return e.Err
}
func (e UnmarshalTypeError) Error() string {
var buf bytes.Buffer
if e.Prefix != "" {
_, _ = fmt.Fprintf(&buf, "%s: ", e.Prefix)
}
_, _ = fmt.Fprintf(&buf, "%s: %s", e.Type.String(), e.Err)
return buf.String()
}
+16
View File
@@ -0,0 +1,16 @@
package reflection
import (
"fmt"
"reflect"
)
func structFieldName(sf reflect.StructField) string {
if sf.Anonymous {
idx := sf.Index[len(sf.Index)-1]
return fmt.Sprintf("%s-%v", "anonymous", idx)
}
return sf.Name
}
+40
View File
@@ -0,0 +1,40 @@
// Package reflection helps Marshalling/Unmarshalling data to structs
package reflection
import (
"reflect"
)
// Reflection provides Marshalling/Unmarshalling oriented view
// of a value
type Reflection struct {
v reflect.Value
types map[reflect.Type]TypeInfo
}
// Value returns the object it reflects
func (r *Reflection) Value() any {
return r.v.Interface()
}
// New creates a Reflection of the given pointer
func New(v any) (*Reflection, error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
err := &InvalidUnmarshalError{Type: rv.Type()}
return nil, err
}
r := &Reflection{
v: rv,
types: make(map[reflect.Type]TypeInfo),
}
if err := r.scan(); err != nil {
return nil, err
}
return r, nil
}
+37
View File
@@ -0,0 +1,37 @@
package reflection
import (
"reflect"
)
func (r *Reflection) scanType(rt reflect.Type) error {
switch rt.Kind() {
case reflect.Pointer, reflect.Slice:
rt = rt.Elem()
}
if _, ok := r.types[rt]; ok {
// cached
return nil
}
ti, err := NewTypeInfo(rt)
if err != nil {
return &UnmarshalTypeError{Type: rt, Err: err}
}
r.types[rt] = ti
for i := range ti.fields {
err := r.scanType(ti.fields[i].sf.Type)
if err != nil {
return err
}
}
return nil
}
func (r *Reflection) scan() error {
return r.scanType(r.v.Type())
}
+108
View File
@@ -0,0 +1,108 @@
package reflection
import (
"log"
"reflect"
"github.com/fatih/structtag"
"github.com/pkg/errors"
)
// TypeInfo ...
type TypeInfo struct {
rt reflect.Type
fields []TypeInfoField
}
// Type ...
func (ti *TypeInfo) Type() reflect.Type {
return ti.rt
}
// Kind ...
func (ti *TypeInfo) Kind() reflect.Kind {
return ti.rt.Kind()
}
// TypeInfoField ...
type TypeInfoField struct {
sf reflect.StructField
tags *structtag.Tags
Name string
}
// Type ...
func (tif *TypeInfoField) Type() reflect.Type {
return tif.sf.Type
}
// Kind ...
func (tif *TypeInfoField) Kind() reflect.Kind {
return tif.sf.Type.Kind()
}
// Tag ...
func (tif *TypeInfoField) Tag(key string) (*structtag.Tag, bool) {
if tif.tags != nil {
tag, _ := tif.tags.Get(key)
if tag != nil {
return tag, true
}
}
return nil, false
}
// NewTypeInfo ...
func NewTypeInfo(rt reflect.Type) (TypeInfo, error) {
log.Printf("%s.%s: %s (%s)", "reflection", "NewTypeInfo", rt, rt.Kind())
ti := TypeInfo{
rt: rt,
}
err := ti.init()
return ti, err
}
func (ti *TypeInfo) init() error {
switch ti.Kind() {
case reflect.Struct:
return ti.initStruct()
default:
return nil
}
}
func (ti *TypeInfo) initStruct() error {
// load fields
n := ti.rt.NumField()
fields := make([]TypeInfoField, 0, n)
for i := 0; i < n; i++ {
sf := ti.rt.Field(i)
tags, err := structtag.Parse(string(sf.Tag))
if err != nil {
// tag parse error
err = &UnmarshalTypeError{
Type: ti.rt,
Err: errors.Wrap(err, structFieldName(sf)),
}
return err
}
if sf.IsExported() {
log.Printf("%s.%s: [%v]: %q %s (%s)",
"reflection", "NewTypeInfo",
i, structFieldName(sf), sf.Type, sf.Type.Kind())
fields = append(fields, TypeInfoField{
sf: sf,
tags: tags,
})
}
}
ti.fields = fields
return nil
}