From ad538d97c951463bd94eb233c88fa6adb34ab1f5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 21:06:38 -0400 Subject: [PATCH] Delete reflectbuild --- internal/reflectbuild/reflectbuild.go | 837 --------------------- internal/reflectbuild/reflectbuild_test.go | 205 ----- 2 files changed, 1042 deletions(-) delete mode 100644 internal/reflectbuild/reflectbuild.go delete mode 100644 internal/reflectbuild/reflectbuild_test.go diff --git a/internal/reflectbuild/reflectbuild.go b/internal/reflectbuild/reflectbuild.go deleted file mode 100644 index 7bc8825..0000000 --- a/internal/reflectbuild/reflectbuild.go +++ /dev/null @@ -1,837 +0,0 @@ -// reflectbuild is a package that provides utility functions to build Go -// objects using reflection. -package reflectbuild - -import ( - "fmt" - "math" - "reflect" - "strings" -) - -// fieldGetters are functions that given a struct return a specific field -// (likely captured in their scope) -type fieldGetter func(s reflect.Value) reflect.Value - -// collection of fieldGetters for a given struct type -type structFieldGetters map[string]fieldGetter - -type target interface { - get() reflect.Value - set(value reflect.Value) error - - fmt.Stringer -} - -type valueTarget reflect.Value - -func (v valueTarget) get() reflect.Value { - return reflect.Value(v) -} - -func (v valueTarget) set(value reflect.Value) error { - rv := reflect.Value(v) - - // value is guaranteed to be a pointer - if value.Kind() != reflect.Ptr { - panic(fmt.Sprintf("set() should receive a pointer, not a '%s'", value.Kind())) - } - - if rv.Kind() != reflect.Ptr { - // TODO: check value is nil? - value = value.Elem() - } - - targetType := rv.Type() - value, err := convert(targetType, value) - if err != nil { - return err - } - - rv.Set(value) - return nil -} - -func (v valueTarget) String() string { - return fmt.Sprintf("valueTarget: '%s' (%s)", reflect.Value(v), reflect.Value(v).Type()) -} - -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) error { - // value is guaranteed to be a pointer - - if v.m.Type().Elem().Kind() != reflect.Ptr { - // TODO: check value is nil? - value = value.Elem() - } - - targetType := v.m.Type().Elem() - value, err := convert(targetType, value) - if err != nil { - return err - } - - v.m.SetMapIndex(v.index, value) - return nil -} - -func (v mapTarget) String() string { - return fmt.Sprintf("mapTarget: '%s'[%s]", v.m, v.index) -} - -// 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 -// being modified. -// Create a Builder with NewBuilder. -type Builder struct { - root reflect.Value - // Root is always a pointer to a non-nil value. - // Cursor is the top of the stack. - stack []target - // Struct field tag to use to retrieve name. - nameTag string - // Cache of functions to access specific fields. - fieldGettersCache map[reflect.Type]structFieldGetters -} - -func copyAndAppend(s []int, i int) []int { - ns := make([]int, len(s)+1) - copy(ns, s) - ns[len(ns)-1] = i - return ns -} - -func (b *Builder) getOrGenerateFieldGettersRecursive(m structFieldGetters, idx []int, s reflect.Type) { - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - if f.PkgPath != "" { - // only consider exported fields - continue - } - if f.Anonymous { - b.getOrGenerateFieldGettersRecursive(m, copyAndAppend(idx, i), f.Type) - } else { - fieldName, ok := f.Tag.Lookup(b.nameTag) - if !ok { - fieldName = f.Name - } - - if len(idx) == 0 { - m[fieldName] = makeFieldGetterByIndex(i) - } else { - m[fieldName] = makeFieldGetterByIndexes(copyAndAppend(idx, i)) - } - } - } - - if b.fieldGettersCache == nil { - b.fieldGettersCache = make(map[reflect.Type]structFieldGetters, 1) - } - - b.fieldGettersCache[s] = m -} - -func (b *Builder) getOrGenerateFieldGetters(s reflect.Type) structFieldGetters { - if s.Kind() != reflect.Struct { - panic("generateFieldGetters can only be called on a struct") - } - m, ok := b.fieldGettersCache[s] - if ok { - return m - } - - m = make(structFieldGetters, s.NumField()) - b.getOrGenerateFieldGettersRecursive(m, nil, s) - b.fieldGettersCache[s] = m - return m -} - -func makeFieldGetterByIndex(idx int) fieldGetter { - return func(s reflect.Value) reflect.Value { - return s.Field(idx) - } -} - -func makeFieldGetterByIndexes(idx []int) fieldGetter { - return func(s reflect.Value) reflect.Value { - return s.FieldByIndex(idx) - } -} - -func (b *Builder) fieldGetter(t reflect.Type, s string) (fieldGetter, error) { - m := b.getOrGenerateFieldGetters(t) - g, ok := m[s] - if !ok { - return nil, fmt.Errorf("field '%s' not accessible on '%s'", s, t) - } - return g, nil -} - -// NewBuilder creates a Builder to construct v. -// If v is nil or not a pointer, an error will be returned. -func NewBuilder(tag string, v interface{}) (Builder, error) { - if v == nil { - return Builder{}, fmt.Errorf("cannot build a nil value") - } - - rv := reflect.ValueOf(v) - if rv.Type().Kind() != reflect.Ptr { - return Builder{}, fmt.Errorf("cannot build a %s: need a pointer", rv.Type().Kind()) - } - - if rv.IsNil() { - return Builder{}, fmt.Errorf("cannot build a nil value") - } - - return Builder{ - root: rv.Elem(), - stack: []target{valueTarget(rv.Elem())}, - nameTag: tag, - }, nil -} - -func (b *Builder) top() target { - return b.stack[len(b.stack)-1] -} - -func (b *Builder) duplicate() { - 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 - // binary for debug - b.Dump() -} - -func (b *Builder) pop() { - b.stack = b.stack[:len(b.stack)-1] -} - -func (b *Builder) len() int { - return len(b.stack) -} - -func (b *Builder) Dump() string { - str := strings.Builder{} - str.WriteByte('[') - - for i, x := range b.stack { - if i > 0 { - str.WriteString(" | ") - } - fmt.Fprintf(&str, "%s", x) - } - - str.WriteByte(']') - return str.String() -} - -func (b *Builder) replace(v target) { - b.stack[len(b.stack)-1] = v -} - -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) - -// DigField pushes the cursor into a field of the current struct. -// Dereferences all pointers found along the way. -// Errors if the current value is not a struct, or the field does not exist. -func (b *Builder) DigField(s string) error { - t := b.top() - v := t.get() - - for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { - if v.IsNil() { - if v.Kind() == reflect.Ptr { - thing := reflect.New(v.Type().Elem()) - v.Set(thing) - } else { - v.Set(reflect.MakeMap(mapStringInterfaceType)) - } - } - v = v.Elem() - } - - if v.Kind() == reflect.Map { - // if map is nil, allocate it - if v.IsNil() { - v.Set(reflect.MakeMap(v.Type())) - } - - // TODO: handle error when map is not indexed by strings - key := reflect.ValueOf(s) - - key, err := convert(v.Type().Key(), key) - if err != nil { - return err - } - - b.replace(mapTarget{ - index: key, - m: v, - }) - } else { - err := checkKind(v.Type(), reflect.Struct) - if err != nil { - return err - } - - 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 -} - -// Save stores a copy of the current cursor position. -// It can be restored using Back(). -// Save points are stored as a stack. -func (b *Builder) Save() { - b.duplicate() -} - -// Reset brings the cursor back to the root object. -func (b *Builder) Reset() { - b.stack = b.stack[:1] - b.stack[0] = valueTarget(b.root) -} - -// Load is the opposite of Save. It discards the current cursor and loads the -// last saved cursor. -// Panics if no cursor has been saved. -func (b *Builder) Load() { - if b.len() < 2 { - panic(fmt.Errorf("tried to Back() when cursor was already at root")) - } - b.pop() -} - -// Cursor returns the value pointed at by the cursor. -func (b *Builder) Cursor() reflect.Value { - return b.top().get() -} - -func (b *Builder) IsSlice() bool { - return b.top().get().Kind() == reflect.Slice -} - -func (b *Builder) IsSliceOrPtr() bool { - t := b.top().get() - if t.Kind() == reflect.Slice { - return true - } - - if t.Kind() == reflect.Ptr && t.Type().Elem().Kind() == reflect.Slice { - return true - } - - if t.Kind() == reflect.Interface && !t.IsNil() && t.Elem().Type().Kind() == reflect.Slice { - return true - } - - return false -} - -// Last moves the cursor to the last value of the current value. -// For a slice or an array, it is the last element they contain, if any. -// For anything else, it's a no-op. -func (b *Builder) Last() { - switch b.Cursor().Kind() { - case reflect.Slice, reflect.Array: - length := b.Cursor().Len() - if length > 0 { - x := b.Cursor().Index(length - 1) - b.replace(valueTarget(x)) // TODO: create a "sliceTarget" ? - } - } -} - -// SliceLastOrCreate moves the cursor to the last element of the slice if any. -// Otherwise creates a new element in that slice and moves to it. -func (b *Builder) SliceLastOrCreate() error { - t := b.top() - v := t.get() - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - - if v.Len() == 0 { - return b.SliceNewElem() - } - b.Last() - return nil -} - -// SliceNewElem operates on a slice. It creates a new object (of type contained -// by the slice), append it to the slice, and moves the cursor to the new -// object. -func (b *Builder) SliceNewElem() error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - // if the pointer is nil we need to allocate the slice - if v.IsNil() { - x := reflect.New(v.Type().Elem()) - v.Set(x) - } - // target the slice itself - v = v.Elem() - } - - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - elem := reflect.New(v.Type().Elem()) - newSlice := reflect.Append(v, elem.Elem()) - v.Set(newSlice) - b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget"? - return nil -} - -func assertPtr(v reflect.Value) { - if v.Kind() != reflect.Ptr { - panic(fmt.Sprintf("value '%s' should be a ptr, not '%s'", v, v.Kind())) - } -} - -func (b *Builder) SliceAppend(value reflect.Value) error { - assertPtr(value) - - t := b.top() - v := t.get() - - // pointer to a slice - if v.Kind() == reflect.Ptr { - // if the pointer is nil we need to allocate the slice - if v.IsNil() { - x := reflect.New(v.Type().Elem()) - v.Set(x) - } - // target the slice itself - v = v.Elem() - } - - err := checkKind(v.Type(), reflect.Slice) - if err != nil { - return err - } - - if v.Type().Elem().Kind() == reflect.Ptr { - // if it is a slice of pointers, we can just append - } else { - // otherwise we need to reference the value - value = value.Elem() - } - - value, err = convert(v.Type().Elem(), value) - if err != nil { - return err - } - - newSlice := reflect.Append(v, value) - v.Set(newSlice) - b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ? - return nil -} - -// convert value so that it can be assigned to t. -// -// Conversion rules: -// -// * Pointers are de-referenced as needed. -// * Integer types are converted between each other as long as they don't -// overflow. -// * Float types are converted between each other as long as they don't -// overflow. -// -// TODO: this function acts as a switchboard. Runtime has enough information to -// generate per-type functions avoiding the double type switches. -func convert(t reflect.Type, value reflect.Value) (reflect.Value, error) { - if value.Type().AssignableTo(t) { - return value, nil - } - - returnPtr := false - if value.Kind() == reflect.Ptr { - if t.Kind() != reflect.Ptr { - return reflect.Value{}, fmt.Errorf("cannot convert pointer to non-pointer") - } - - value = value.Elem() - t = t.Elem() - returnPtr = true - } - - if t.Kind() == value.Kind() { - return value.Convert(t), nil - } - - var err error - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - value, err = convertInt(t, value) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - value, err = convertUint(t, value) - case reflect.Float32, reflect.Float64: - value, err = convertFloat(t, value) - default: - err = fmt.Errorf("not converting a %s into a %s", value.Kind(), t.Kind()) - } - - if err != nil { - return value, err - } - - result := reflect.New(t) // TODO: remove alloc - result.Elem().Set(value.Convert(t)) - if returnPtr { - return result, nil - } - return result.Elem(), nil -} - -type IntegerOverflowError struct { - value int64 - min int64 - max int64 - kind reflect.Kind -} - -func (e IntegerOverflowError) Error() string { - return fmt.Sprintf("integer overflow: cannot store %d in %s [%d, %d]", e.value, e.kind, e.min, e.max) -} - -const maxInt = int64(^uint(0) >> 1) -const minInt = -maxInt - 1 - -func convertInt(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x := value.Int() - - switch t.Kind() { - case reflect.Int: - if x > maxInt || x < minInt { - return value, IntegerOverflowError{ - value: x, - min: minInt, - max: maxInt, - kind: t.Kind(), - } - } - case reflect.Int8: - if x > math.MaxInt8 || x < math.MinInt8 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt8, - max: math.MaxInt8, - kind: t.Kind(), - } - } - case reflect.Int16: - if x > math.MaxInt16 || x < math.MinInt16 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt16, - max: math.MaxInt16, - kind: t.Kind(), - } - } - case reflect.Int32: - if x > math.MaxInt32 || x < math.MinInt32 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt32, - max: math.MaxInt32, - kind: t.Kind(), - } - } - case reflect.Int64: - if x > math.MaxInt64 || x < math.MinInt64 { - return value, IntegerOverflowError{ - value: x, - min: math.MinInt64, - max: math.MaxInt64, - kind: t.Kind(), - } - } - } - - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) - } -} - -type UnsignedIntegerOverflowError struct { - value uint64 - max uint64 - kind reflect.Kind -} - -func (e UnsignedIntegerOverflowError) Error() string { - return fmt.Sprintf("unsigned integer overflow: cannot store %d in %s [max %d]", e.value, e.kind, e.max) -} - -func convertUint(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - err := convertUintOverflowCheck(t.Kind(), value.Uint()) - if err != nil { - return value, err - } - return value.Convert(t), nil // reflect.TypeOf(int64(0)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - signed := value.Int() - if signed < 0 { - return value, fmt.Errorf("cannot store negative integer '%d' into %s", signed, t.Kind()) - } - - x := uint64(signed) - err := convertUintOverflowCheck(t.Kind(), x) - if err != nil { - return value, err - } - - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to unsigned integer (%s)", value.Kind(), t.Kind()) - } -} - -const maxUint = uint64(^uint(0)) - -func convertUintOverflowCheck(t reflect.Kind, x uint64) error { - switch t { - case reflect.Uint: - if x > maxUint { - return UnsignedIntegerOverflowError{ - value: x, - max: maxUint, - kind: t, - } - } - case reflect.Uint8: - if x > math.MaxUint8 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint8, - kind: t, - } - } - case reflect.Uint16: - if x > math.MaxUint16 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint16, - kind: t, - } - } - case reflect.Uint32: - if x > math.MaxUint32 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint32, - kind: t, - } - } - case reflect.Uint64: - if x > math.MaxUint64 { - return UnsignedIntegerOverflowError{ - value: x, - max: math.MaxUint64, - kind: t, - } - } - } - return nil -} - -func convertFloat(t reflect.Type, value reflect.Value) (reflect.Value, error) { - switch value.Kind() { - case reflect.Float32, reflect.Float64: - if t.Kind() == reflect.Float32 { - f := value.Float() - if f > math.MaxFloat32 { - return value, fmt.Errorf("float overflow: %f does not fit in %s [max %f]", f, t, math.MaxFloat32) - } - } - return value.Convert(t), nil - default: - return value, fmt.Errorf("cannot convert %s to integer (%s)", value.Kind(), t.Kind()) - } -} - -// Set the value at the cursor to the given string. -// Errors if a string cannot be assigned to the current value. -func (b *Builder) SetString(s string) error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - v.Set(reflect.ValueOf(&s)) - return nil - } - return t.set(reflect.ValueOf(&s)) -} - -// Set the value at the cursor to the given boolean. -// Errors if a boolean cannot be assigned to the current value. -func (b *Builder) SetBool(value bool) error { - t := b.top() - v := t.get() - - err := checkKind(v.Type(), reflect.Bool) - if err != nil { - return err - } - - v.SetBool(value) - return nil -} - -func (b *Builder) SetFloat(n float64) error { - t := b.top() - v := t.get() - - err := checkKindFloat(v.Type()) - if err != nil { - return err - } - - v.SetFloat(n) - return nil -} - -func (b *Builder) Set(v reflect.Value) error { - assertPtr(v) - t := b.top() - return t.set(v) -} - -// EnsureSlice makes sure that the cursor points to a non-nil slice. -func (b *Builder) EnsureSlice() error { - t := b.top() - v := t.get() - - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - - if v.Kind() != reflect.Slice { - return IncorrectKindError{ - Reason: "EnsureSlice", - Actual: v.Kind(), - Expected: []reflect.Kind{reflect.Slice}, - } - } - - if v.IsNil() { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) - } - - return nil -} - -// EnsureStructOrMap makes sure that the cursor points to an initialized -// struct or map. -func (b *Builder) EnsureStructOrMap() error { - t := b.top() - v := t.get() - - switch v.Kind() { - case reflect.Struct: - case reflect.Map: - if v.IsNil() { - x := reflect.New(v.Type()) - x.Elem().Set(reflect.MakeMap(v.Type())) - return t.set(x) - } - case reflect.Interface: - // TODO: ? - default: - return IncorrectKindError{ - Reason: "EnsureStructOrMap", - Actual: v.Kind(), - Expected: []reflect.Kind{reflect.Struct, reflect.Map}, - } - } - return nil -} - -func checkKindFloat(rt reflect.Type) error { - switch rt.Kind() { - case reflect.Float32, reflect.Float64: - return nil - } - - return IncorrectKindError{ - Reason: "CheckKindFloat", - Actual: rt.Kind(), - Expected: []reflect.Kind{reflect.Float64}, - } -} - -func checkKind(rt reflect.Type, expected reflect.Kind) error { - if rt.Kind() != expected { - return IncorrectKindError{ - Reason: "CheckKind", - Actual: rt.Kind(), - Expected: []reflect.Kind{expected}, - } - } - return nil -} - -type IncorrectKindError struct { - Reason string - Actual reflect.Kind - Expected []reflect.Kind -} - -func (e IncorrectKindError) Error() string { - b := strings.Builder{} - b.WriteString("incorrect kind: ") - - if len(e.Expected) < 2 { - b.WriteString(fmt.Sprintf("expected '%s', got '%s'", e.Expected[0], e.Actual)) - } else { - b.WriteString(fmt.Sprintf("expected any of '%s', got '%s'", e.Expected, e.Actual)) - } - - if e.Reason != "" { - b.WriteString(": ") - b.WriteString(e.Reason) - } - - return b.String() -} - -type FieldNotFoundError struct { - Struct reflect.Value - FieldName string -} - -func (e FieldNotFoundError) Error() string { - return fmt.Sprintf("field not found: '%s' on '%s'", e.FieldName, e.Struct.Type()) -} diff --git a/internal/reflectbuild/reflectbuild_test.go b/internal/reflectbuild/reflectbuild_test.go deleted file mode 100644 index 692e0c2..0000000 --- a/internal/reflectbuild/reflectbuild_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package reflectbuild_test - -import ( - "reflect" - "testing" - - "github.com/pelletier/go-toml/v2/internal/reflectbuild" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewBuilderSuccess(t *testing.T) { - x := struct{}{} - _, err := reflectbuild.NewBuilder("", &x) - assert.NoError(t, err) -} - -func TestNewBuilderNil(t *testing.T) { - _, err := reflectbuild.NewBuilder("", nil) - assert.Error(t, err) -} - -func TestNewBuilderNonPtr(t *testing.T) { - _, err := reflectbuild.NewBuilder("", struct{}{}) - assert.Error(t, err) -} - -func TestDigField(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Error(t, b.DigField("oops")) - assert.NoError(t, b.DigField("Field")) - assert.Error(t, b.DigField("does not work on strings")) -} - -func TestBack(t *testing.T) { - x := struct { - A string - B string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - b.Save() - assert.NoError(t, b.DigField("A")) - assert.NoError(t, b.SetString("A")) - b.Load() - b.Save() - assert.NoError(t, b.DigField("B")) - assert.NoError(t, b.SetString("B")) - assert.Equal(t, "A", x.A) - assert.Equal(t, "B", x.B) - b.Load() // back to root - assert.Panics(t, func() { - b.Load() // oops - }) -} - -func TestReset(t *testing.T) { - x := struct { - A []string - B string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("A")) - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("hello")) - b.Reset() - require.NoError(t, b.DigField("B")) - require.NoError(t, b.SetString("world")) - - assert.Equal(t, []string{"hello"}, x.A) - assert.Equal(t, "world", x.B) -} - -func TestSetString(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Error(t, b.SetString("oops")) - require.NoError(t, b.DigField("Field")) - require.NoError(t, b.SetString("hello!")) - assert.Equal(t, "hello!", x.Field) -} - -func TestSliceNewElem(t *testing.T) { - x := struct { - Field []string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("Field")) - b.Save() - - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val1")) - b.Load() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2")) - - require.Error(t, b.SliceNewElem()) - - assert.Equal(t, []string{"Val1", "Val2"}, x.Field) -} - -func TestSliceNewElemNested(t *testing.T) { - x := struct { - Field [][]string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - require.NoError(t, b.DigField("Field")) - - b.Save() - - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val1.1")) - b.Load() - b.Save() - - require.NoError(t, b.SliceNewElem()) - b.Save() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2.1")) - b.Load() - require.NoError(t, b.SliceNewElem()) - require.NoError(t, b.SetString("Val2.2")) - b.Load() - require.NoError(t, b.SliceNewElem()) - - assert.Equal(t, [][]string{{"Val1.1"}, {"Val2.1", "Val2.2"}, nil}, x.Field) -} - -func TestIncorrectKindError(t *testing.T) { - err := reflectbuild.IncorrectKindError{ - Actual: reflect.String, - Expected: []reflect.Kind{reflect.Struct}, - } - assert.NotEmpty(t, err.Error()) -} - -func TestFieldNotFoundError(t *testing.T) { - err := reflectbuild.FieldNotFoundError{ - Struct: reflect.ValueOf(struct { - Blah string - }{}), - FieldName: "Foo", - } - assert.NotEmpty(t, err.Error()) -} - -func TestCursor(t *testing.T) { - x := struct { - Field string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - assert.Equal(t, b.Cursor().Kind(), reflect.String) -} - -func TestStringPtr(t *testing.T) { - x := struct { - Field *string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - assert.NoError(t, b.SetString("A")) - assert.Equal(t, "A", *x.Field) -} - -func TestAppendSlicePtr(t *testing.T) { - x := struct { - Field *[]string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - v := "A" - assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) - assert.Equal(t, []string{"A"}, *x.Field) -} - -func TestAppendPtrSlicePtr(t *testing.T) { - x := struct { - Field *[]*string - }{} - b, err := reflectbuild.NewBuilder("", &x) - require.NoError(t, err) - assert.Equal(t, b.Cursor().Kind(), reflect.Struct) - require.NoError(t, b.DigField("Field")) - v := "A" - assert.NoError(t, b.SliceAppend(reflect.ValueOf(&v))) - assert.Equal(t, "A", *(*x.Field)[0]) -}