Handle maps
This commit is contained in:
@@ -2,6 +2,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|||||||
@@ -15,6 +15,34 @@ type fieldGetter func(s reflect.Value) reflect.Value
|
|||||||
// collection of fieldGetters for a given struct type
|
// collection of fieldGetters for a given struct type
|
||||||
type structFieldGetters map[string]fieldGetter
|
type structFieldGetters map[string]fieldGetter
|
||||||
|
|
||||||
|
type target interface {
|
||||||
|
get() reflect.Value
|
||||||
|
set(value reflect.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type valueTarget reflect.Value
|
||||||
|
|
||||||
|
func (v valueTarget) get() reflect.Value {
|
||||||
|
return reflect.Value(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v valueTarget) set(value reflect.Value) {
|
||||||
|
reflect.Value(v).Set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapTarget struct {
|
||||||
|
index reflect.Value
|
||||||
|
m reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v mapTarget) get() reflect.Value {
|
||||||
|
return v.m.MapIndex(v.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v mapTarget) set(value reflect.Value) {
|
||||||
|
v.m.SetMapIndex(v.index, value)
|
||||||
|
}
|
||||||
|
|
||||||
// Builder wraps a value and provides method to modify its structure.
|
// Builder wraps a value and provides method to modify its structure.
|
||||||
// It is a stateful object that keeps a cursor of what part of the object is
|
// It is a stateful object that keeps a cursor of what part of the object is
|
||||||
// being modified.
|
// being modified.
|
||||||
@@ -23,7 +51,7 @@ type Builder struct {
|
|||||||
root reflect.Value
|
root reflect.Value
|
||||||
// Root is always a pointer to a non-nil value.
|
// Root is always a pointer to a non-nil value.
|
||||||
// Cursor is the top of the stack.
|
// Cursor is the top of the stack.
|
||||||
stack []reflect.Value
|
stack []target
|
||||||
// Struct field tag to use to retrieve name.
|
// Struct field tag to use to retrieve name.
|
||||||
nameTag string
|
nameTag string
|
||||||
// Cache of functions to access specific fields.
|
// Cache of functions to access specific fields.
|
||||||
@@ -117,17 +145,17 @@ func NewBuilder(tag string, v interface{}) (Builder, error) {
|
|||||||
|
|
||||||
return Builder{
|
return Builder{
|
||||||
root: rv.Elem(),
|
root: rv.Elem(),
|
||||||
stack: []reflect.Value{rv.Elem()},
|
stack: []target{valueTarget(rv.Elem())},
|
||||||
nameTag: tag,
|
nameTag: tag,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) top() reflect.Value {
|
func (b *Builder) top() target {
|
||||||
return b.stack[len(b.stack)-1]
|
return b.stack[len(b.stack)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) push(v reflect.Value) {
|
func (b *Builder) duplicate() {
|
||||||
b.stack = append(b.stack, v)
|
b.stack = append(b.stack, b.stack[len(b.stack)-1])
|
||||||
// TODO: remove me. just here to make sure the method is included in the
|
// TODO: remove me. just here to make sure the method is included in the
|
||||||
// binary for debug
|
// binary for debug
|
||||||
b.Dump()
|
b.Dump()
|
||||||
@@ -149,14 +177,14 @@ func (b *Builder) Dump() string {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
str.WriteString(" | ")
|
str.WriteString(" | ")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&str, "%s (%s)", x.Type(), x)
|
fmt.Fprintf(&str, "%s", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
str.WriteByte(']')
|
str.WriteByte(']')
|
||||||
return str.String()
|
return str.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) replace(v reflect.Value) {
|
func (b *Builder) replace(v target) {
|
||||||
b.stack[len(b.stack)-1] = v
|
b.stack[len(b.stack)-1] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,27 +193,47 @@ func (b *Builder) replace(v reflect.Value) {
|
|||||||
// Errors if the current value is not a struct, or the field does not exist.
|
// Errors if the current value is not a struct, or the field does not exist.
|
||||||
func (b *Builder) DigField(s string) error {
|
func (b *Builder) DigField(s string) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
for t.Kind() == reflect.Interface || t.Kind() == reflect.Ptr {
|
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
|
||||||
t = t.Elem()
|
if v.IsNil() {
|
||||||
|
thing := reflect.New(v.Type().Elem())
|
||||||
|
v.Set(thing)
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := checkKind(t.Type(), reflect.Struct)
|
if v.Kind() == reflect.Map {
|
||||||
if err != nil {
|
// if map is nil, allocate it
|
||||||
return err
|
if v.IsNil() {
|
||||||
}
|
v.Set(reflect.MakeMap(v.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
g, err := b.fieldGetter(t.Type(), s)
|
// TODO: handle error when map is not indexed by strings
|
||||||
if err != nil {
|
key := reflect.ValueOf(s)
|
||||||
return FieldNotFoundError{FieldName: s, Struct: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
f := g(t)
|
b.replace(mapTarget{
|
||||||
if !f.IsValid() {
|
index: key,
|
||||||
return FieldNotFoundError{FieldName: s, Struct: t}
|
m: v,
|
||||||
}
|
})
|
||||||
|
} else {
|
||||||
|
err := checkKind(v.Type(), reflect.Struct)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
b.replace(f)
|
g, err := b.fieldGetter(v.Type(), s)
|
||||||
|
if err != nil {
|
||||||
|
return FieldNotFoundError{FieldName: s, Struct: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := g(v)
|
||||||
|
if !f.IsValid() {
|
||||||
|
return FieldNotFoundError{FieldName: s, Struct: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.replace(valueTarget(f))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -194,13 +242,13 @@ func (b *Builder) DigField(s string) error {
|
|||||||
// It can be restored using Back().
|
// It can be restored using Back().
|
||||||
// Save points are stored as a stack.
|
// Save points are stored as a stack.
|
||||||
func (b *Builder) Save() {
|
func (b *Builder) Save() {
|
||||||
b.push(b.top())
|
b.duplicate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset brings the cursor back to the root object.
|
// Reset brings the cursor back to the root object.
|
||||||
func (b *Builder) Reset() {
|
func (b *Builder) Reset() {
|
||||||
b.stack = b.stack[:1]
|
b.stack = b.stack[:1]
|
||||||
b.stack[0] = b.root
|
b.stack[0] = valueTarget(b.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load is the opposite of Save. It discards the current cursor and loads the
|
// Load is the opposite of Save. It discards the current cursor and loads the
|
||||||
@@ -215,15 +263,15 @@ func (b *Builder) Load() {
|
|||||||
|
|
||||||
// Cursor returns the value pointed at by the cursor.
|
// Cursor returns the value pointed at by the cursor.
|
||||||
func (b *Builder) Cursor() reflect.Value {
|
func (b *Builder) Cursor() reflect.Value {
|
||||||
return b.top()
|
return b.top().get()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) IsSlice() bool {
|
func (b *Builder) IsSlice() bool {
|
||||||
return b.top().Kind() == reflect.Slice
|
return b.top().get().Kind() == reflect.Slice
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) IsSliceOrPtr() bool {
|
func (b *Builder) IsSliceOrPtr() bool {
|
||||||
return b.top().Kind() == reflect.Slice || (b.top().Kind() == reflect.Ptr && b.top().Type().Elem().Kind() == reflect.Slice)
|
return b.top().get().Kind() == reflect.Slice || (b.top().get().Kind() == reflect.Ptr && b.top().get().Type().Elem().Kind() == reflect.Slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last moves the cursor to the last value of the current value.
|
// Last moves the cursor to the last value of the current value.
|
||||||
@@ -235,7 +283,7 @@ func (b *Builder) Last() {
|
|||||||
length := b.Cursor().Len()
|
length := b.Cursor().Len()
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
x := b.Cursor().Index(length - 1)
|
x := b.Cursor().Index(length - 1)
|
||||||
b.replace(x)
|
b.replace(valueTarget(x)) // TODO: create a "sliceTarget" ?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,12 +292,13 @@ func (b *Builder) Last() {
|
|||||||
// Otherwise creates a new element in that slice and moves to it.
|
// Otherwise creates a new element in that slice and moves to it.
|
||||||
func (b *Builder) SliceLastOrCreate() error {
|
func (b *Builder) SliceLastOrCreate() error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
err := checkKind(t.Type(), reflect.Slice)
|
v := t.get()
|
||||||
|
err := checkKind(v.Type(), reflect.Slice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Len() == 0 {
|
if v.Len() == 0 {
|
||||||
return b.SliceNewElem()
|
return b.SliceNewElem()
|
||||||
}
|
}
|
||||||
b.Last()
|
b.Last()
|
||||||
@@ -261,14 +310,15 @@ func (b *Builder) SliceLastOrCreate() error {
|
|||||||
// object.
|
// object.
|
||||||
func (b *Builder) SliceNewElem() error {
|
func (b *Builder) SliceNewElem() error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
err := checkKind(t.Type(), reflect.Slice)
|
v := t.get()
|
||||||
|
err := checkKind(v.Type(), reflect.Slice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
elem := reflect.New(t.Type().Elem())
|
elem := reflect.New(v.Type().Elem())
|
||||||
newSlice := reflect.Append(t, elem.Elem())
|
newSlice := reflect.Append(v, elem.Elem())
|
||||||
t.Set(newSlice)
|
v.Set(newSlice)
|
||||||
b.replace(t.Index(t.Len() - 1))
|
b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget"?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,37 +328,38 @@ func assertPtr(v reflect.Value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) SliceAppend(v reflect.Value) error {
|
func (b *Builder) SliceAppend(value reflect.Value) error {
|
||||||
assertPtr(v)
|
assertPtr(value)
|
||||||
|
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
// pointer to a slice
|
// pointer to a slice
|
||||||
if t.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Ptr {
|
||||||
// if the pointer is nil we need to allocate the slice
|
// if the pointer is nil we need to allocate the slice
|
||||||
if t.IsNil() {
|
if v.IsNil() {
|
||||||
x := reflect.New(t.Type().Elem())
|
x := reflect.New(v.Type().Elem())
|
||||||
t.Set(x)
|
v.Set(x)
|
||||||
}
|
}
|
||||||
// target the slice itself
|
// target the slice itself
|
||||||
t = t.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := checkKind(t.Type(), reflect.Slice)
|
err := checkKind(v.Type(), reflect.Slice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Type().Elem().Kind() == reflect.Ptr {
|
if v.Type().Elem().Kind() == reflect.Ptr {
|
||||||
// if it is a slice of pointers, we can just append
|
// if it is a slice of pointers, we can just append
|
||||||
} else {
|
} else {
|
||||||
// otherwise we need to reference the value
|
// otherwise we need to reference the value
|
||||||
v = v.Elem()
|
value = value.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
newSlice := reflect.Append(t, v)
|
newSlice := reflect.Append(v, value)
|
||||||
t.Set(newSlice)
|
v.Set(newSlice)
|
||||||
b.replace(t.Index(t.Len() - 1))
|
b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,61 +367,65 @@ func (b *Builder) SliceAppend(v reflect.Value) error {
|
|||||||
// Errors if a string cannot be assigned to the current value.
|
// Errors if a string cannot be assigned to the current value.
|
||||||
func (b *Builder) SetString(s string) error {
|
func (b *Builder) SetString(s string) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
if t.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Ptr {
|
||||||
t.Set(reflect.ValueOf(&s))
|
v.Set(reflect.ValueOf(&s))
|
||||||
} else {
|
} else {
|
||||||
err := checkKind(t.Type(), reflect.String)
|
err := checkKind(v.Type(), reflect.String)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.SetString(s)
|
v.SetString(s)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the value at the cursor to the given boolean.
|
// Set the value at the cursor to the given boolean.
|
||||||
// Errors if a boolean cannot be assigned to the current value.
|
// Errors if a boolean cannot be assigned to the current value.
|
||||||
func (b *Builder) SetBool(v bool) error {
|
func (b *Builder) SetBool(value bool) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
err := checkKind(t.Type(), reflect.Bool)
|
err := checkKind(v.Type(), reflect.Bool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.SetBool(v)
|
v.SetBool(value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) SetFloat(n float64) error {
|
func (b *Builder) SetFloat(n float64) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
err := checkKindFloat(t.Type())
|
err := checkKindFloat(v.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.SetFloat(n)
|
v.SetFloat(n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) SetInt(n int64) error {
|
func (b *Builder) SetInt(n int64) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
|
v := t.get()
|
||||||
|
|
||||||
err := checkKindInt(t.Type())
|
err := checkKindInt(v.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.SetInt(n)
|
v.SetInt(n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Set(v reflect.Value) error {
|
func (b *Builder) Set(v reflect.Value) error {
|
||||||
t := b.top()
|
t := b.top()
|
||||||
t.Set(v)
|
t.set(v)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user