Compare commits
6 Commits
main
...
dev-amery-
Author | SHA1 | Date |
---|---|---|
Alejandro Mery | c94e5e74ad | 1 year ago |
Alejandro Mery | dd252fafae | 1 year ago |
Alejandro Mery | f68272eb84 | 1 year ago |
Alejandro Mery | 1e95acf21d | 1 year ago |
Alejandro Mery | 2b5dcec64d | 1 year ago |
Alejandro Mery | 4aa6233e4f | 1 year ago |
7 changed files with 296 additions and 8 deletions
@ -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() |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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()) |
||||||
|
} |
@ -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 |
||||||
|
} |
Loading…
Reference in new issue