From f9bb8b392a3d620f198aed202e9e06845b7c1d06 Mon Sep 17 00:00:00 2001 From: Milad Abbasi Date: Tue, 19 Jan 2021 18:01:33 +0330 Subject: [PATCH] Add tests for SetValue method --- errors.go | 18 +- input.go | 175 +++++------ input_test.go | 854 ++++++++++++++++++++++++++++++++++++-------------- utils.go | 29 +- 4 files changed, 717 insertions(+), 359 deletions(-) diff --git a/errors.go b/errors.go index 9b5855f..0ad2b28 100644 --- a/errors.go +++ b/errors.go @@ -13,6 +13,9 @@ var ( // Only ".json", ".yml", ".yaml" and ".env" file types are supported ErrUnsupportedFileExt = errors.New("unsupported file extension") + // Struct field is unexported + ErrUnSettableField = errors.New("unSettable field") + // Provider could not find value with specified key ErrKeyNotFound = errors.New("key not found") @@ -27,13 +30,14 @@ var ( ) const ( - unsupportedTypeErrFormat = `%w: cannot handle type "%v" at "%v"` - unsupportedFileExtErrFormat = `%w: %v` - decodeFailedErrFormat = `failed to decode: %w` - requiredFieldErrFormat = `%w: no value found for "%v"` - unsupportedElementTypeErrFormat = `%w: cannot handle slice/array of "%v" at "%v"` - parseErrFormat = `%w at "%v": %v` - overflowErrFormat = `%w: "%v" overflows type "%v" at "%v"` + unsupportedTypeErrFormat = `%w: %v` + badFieldErrFormat = `bad field "%v": %w` + unsupportedFileExtErrFormat = `%w: %v` + unSettableFieldErrFormat = `%w: %v` + decodeFailedErrFormat = `failed to decode: %w` + requiredFieldErrFormat = `%w: no value found for "%v"` + parseErrFormat = `%w at "%v": %v` + overflowErrFormat = `%w: "%v" overflows type "%v" at "%v"` ) // An InvalidInputError describes an invalid argument passed to Into function diff --git a/input.go b/input.go index 0427e1c..4cacf13 100644 --- a/input.go +++ b/input.go @@ -49,7 +49,7 @@ func NewInput(i interface{}) (*Input, error) { f := Field{ Value: v.Elem(), - Tags: &ConfigTags{}, + Tags: new(ConfigTags), } if err := in.traverseField(&f); err != nil { @@ -80,14 +80,11 @@ func (in *Input) traverseField(f *Field) error { return nil } - switch f.Value.Kind() { - case reflect.Struct: - if isTime(f.Value) || isURL(f.Value) { - in.collectField(f) - - return nil - } + if err := in.isSupportedType(f.Value.Type()); err != nil { + return fmt.Errorf(badFieldErrFormat, in.getPath(f.Path), err) + } + if isStruct(f.Value.Type()) { for i := 0; i < f.Value.NumField(); i++ { nestedField := Field{ Value: f.Value.Field(i), @@ -100,59 +97,70 @@ func (in *Input) traverseField(f *Field) error { } } - case reflect.Ptr: - pv := reflect.New(f.Value.Type().Elem()) - f.Value.Set(pv) + return nil + } + + if f.Value.Kind() == reflect.Ptr && isStruct(f.Value.Type().Elem()) { + if f.Value.IsNil() { + initPtr(f.Value) + } pointedField := Field{ - Value: pv.Elem(), + Value: f.Value.Elem(), Tags: f.Tags, Path: f.Path, } return in.traverseField(&pointedField) + } - case reflect.Slice, reflect.Array: - switch f.Value.Type().Elem().Kind() { - case reflect.Slice, - reflect.Array, - reflect.Uintptr, - reflect.Chan, - reflect.Func, - reflect.Interface, - reflect.UnsafePointer: - return fmt.Errorf( - unsupportedElementTypeErrFormat, - ErrUnsupportedType, f.Value.Type().Elem().Kind(), in.getPath(f.Path), - ) + in.collectField(f) + return nil +} - default: - in.collectField(f) - } +func (in *Input) collectField(f *Field) { + in.Fields = append(in.Fields, f) +} - case reflect.Uintptr, +func (in *Input) isSupportedType(t reflect.Type) error { + switch t.Kind() { + case reflect.Invalid, + reflect.Uintptr, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer: - return fmt.Errorf( - unsupportedTypeErrFormat, - ErrUnsupportedType, f.Value.Kind(), in.getPath(f.Path), - ) + return fmt.Errorf(unsupportedTypeErrFormat, ErrUnsupportedType, t.Kind()) + + case reflect.Slice, reflect.Array: + switch t.Elem().Kind() { + case reflect.Slice, reflect.Array: + return fmt.Errorf(unsupportedTypeErrFormat, ErrUnsupportedType, "multi-dimensional slice/array") - default: - in.collectField(f) + default: + return in.isSupportedType(t.Elem()) + } + + case reflect.Ptr: + return in.isSupportedType(t.Elem()) } return nil } -func (in *Input) collectField(f *Field) { - in.Fields = append(in.Fields, f) -} - // SetValue validates and sets the value of a struct field +// returns error in case of unSettable field or unsupported type func (in *Input) SetValue(f *Field, value string) error { + if !f.Value.CanSet() { + return fmt.Errorf( + unSettableFieldErrFormat, + ErrUnSettableField, in.getPath(f.Path), + ) + } + if err := in.isSupportedType(f.Value.Type()); err != nil { + return fmt.Errorf(badFieldErrFormat, in.getPath(f.Path), err) + } + if f.Tags.Expand { value = os.ExpandEnv(value) } @@ -165,7 +173,7 @@ func (in *Input) SetValue(f *Field, value string) error { return in.setBool(f, value) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if isDuration(f.Value) { + if isDuration(f.Value.Type()) { return in.setDuration(f, value) } @@ -180,8 +188,11 @@ func (in *Input) SetValue(f *Field, value string) error { case reflect.Complex64, reflect.Complex128: return in.setComplex(f, value) - case reflect.Slice, reflect.Array: - return in.setList(f, value) + case reflect.Slice: + return in.setSlice(f, value) + + case reflect.Array: + return in.setArray(f, value) case reflect.Map: return in.setMap(f, value) @@ -190,19 +201,13 @@ func (in *Input) SetValue(f *Field, value string) error { return in.setPointer(f, value) case reflect.Struct: - if isTime(f.Value) { + if isTime(f.Value.Type()) { return in.setTime(f, value) } - if isURL(f.Value) { + if isURL(f.Value.Type()) { return in.setUrl(f, value) } - - default: - return fmt.Errorf( - unsupportedTypeErrFormat, - ErrUnsupportedType, f.Value.Kind(), in.getPath(f.Path), - ) } return nil @@ -284,7 +289,7 @@ func (in *Input) setFloat(f *Field, value string) error { } func (in *Input) setComplex(f *Field, value string) error { - c, err := strconv.ParseComplex(value, 64) + c, err := strconv.ParseComplex(value, 128) if err != nil { return fmt.Errorf( parseErrFormat, @@ -302,44 +307,8 @@ func (in *Input) setComplex(f *Field, value string) error { return nil } -func (in *Input) setList(f *Field, value string) error { - switch f.Value.Type().Elem().Kind() { - case reflect.Slice, - reflect.Array, - reflect.Uintptr, - reflect.Chan, - reflect.Func, - reflect.Interface, - reflect.UnsafePointer: - return fmt.Errorf( - unsupportedElementTypeErrFormat, - ErrUnsupportedType, f.Value.Type().Elem().Kind(), in.getPath(f.Path), - ) - } - - var items []string - for _, v := range strings.Split(value, f.Tags.Separator) { - item := strings.TrimSpace(v) - if len(item) > 0 { - items = append(items, item) - } - } - if len(items) == 0 { - return nil - } - - switch f.Value.Kind() { - case reflect.Slice: - return in.setSlice(f, items) - - case reflect.Array: - return in.setArray(f, items) - } - - return nil -} - -func (in *Input) setSlice(f *Field, items []string) error { +func (in *Input) setSlice(f *Field, value string) error { + items := extractItems(value, f.Tags.Separator) size := len(items) if size == 0 { return nil @@ -362,18 +331,18 @@ func (in *Input) setSlice(f *Field, items []string) error { return nil } -func (in *Input) setArray(f *Field, items []string) error { +func (in *Input) setArray(f *Field, value string) error { + items := extractItems(value, f.Tags.Separator) size := f.Value.Len() if size == 0 || len(items) == 0 { return nil } - at := reflect.ArrayOf(size, f.Value.Type().Elem()) - av := reflect.New(at).Elem() + a := reflect.New(reflect.ArrayOf(size, f.Value.Type().Elem())).Elem() for i := 0; i < size; i++ { nestedField := Field{ - Value: av.Index(i), + Value: a.Index(i), Tags: f.Tags, Path: f.Path, } @@ -383,7 +352,7 @@ func (in *Input) setArray(f *Field, items []string) error { } } - f.Value.Set(av) + f.Value.Set(a) return nil } @@ -393,10 +362,12 @@ func (in *Input) setMap(f *Field, value string) error { } func (in *Input) setPointer(f *Field, value string) error { - p := reflect.New(f.Value.Type().Elem()) - f.Value.Set(p) + if f.Value.IsNil() { + initPtr(f.Value) + } + pointedField := Field{ - Value: p.Elem(), + Value: f.Value.Elem(), Tags: f.Tags, Path: f.Path, } @@ -412,12 +383,6 @@ func (in *Input) setDuration(f *Field, value string) error { ErrParsing, in.getPath(f.Path), err, ) } - if f.Value.OverflowInt(int64(d)) { - return fmt.Errorf( - overflowErrFormat, - ErrValueOverflow, d, f.Value.Kind(), in.getPath(f.Path), - ) - } f.Value.SetInt(int64(d)) return nil @@ -449,6 +414,10 @@ func (in *Input) setUrl(f *Field, value string) error { return nil } +func initPtr(v reflect.Value) { + v.Set(reflect.New(v.Type().Elem())) +} + // getPath returns a dot separated string prefixed with struct name func (in *Input) getPath(paths []string) string { return in.Name + "." + strings.Join(paths, ".") diff --git a/input_test.go b/input_test.go index bf00c38..7f56cd4 100644 --- a/input_test.go +++ b/input_test.go @@ -3,8 +3,10 @@ package gonfig import ( "errors" "fmt" + "math" "net/url" "reflect" + "strings" "testing" "time" "unsafe" @@ -13,6 +15,258 @@ import ( "github.com/stretchr/testify/require" ) +type badTypes struct { + Ui uintptr + Uip *uintptr + C chan int + Cp *chan int + F func() + Fp *func() + I interface{} + Ip *interface{} + Up unsafe.Pointer + Upp *unsafe.Pointer + Ss [][]int + Ssp *[][]int + Sa [][0]int + Sap *[][0]int + Aa [0][0]int + Aap *[0][0]int + As [0][]int + Asp *[0][]int + Sui []uintptr + PSui *[]uintptr + SuiP []*uintptr + Sc []chan int + PScp *[]chan int + Scp []*chan int + Sf []func() + PSf *[]func() + Sfp []*func() + Si []interface{} + PSi *[]interface{} + Sip []*interface{} + Su []unsafe.Pointer + PSu *[]unsafe.Pointer + Sup []*unsafe.Pointer + Aui [0]uintptr + AuiP *[0]uintptr + Ac [0]chan int + Acp *[0]chan int + Af [0]func() + Afp *[0]func() + Ai [0]interface{} + Aip *[0]interface{} + Au [0]unsafe.Pointer + Aup *[0]unsafe.Pointer +} + +type supportedTypes struct { + Boolean bool + BooleanPtr *bool + BooleanArray [2]bool + BooleanArrayPtr *[2]bool + BooleanPtrArray [2]*bool + BooleanSlice []bool + BooleanPtrSlice []*bool + + Uint8 uint8 + Uint8Ptr *uint8 + Uint8Array [2]uint8 + Uint8ArrayPtr *[2]uint8 + Uint8PtrArray [2]*uint8 + Uint8Slice []uint8 + Uint8PtrSlice []*uint8 + + Uint16 uint16 + Uint16Ptr *uint16 + Uint16Array [2]uint16 + Uint16ArrayPtr *[2]uint16 + Uint16PtrArray [2]*uint16 + Uint16Slice []uint16 + Uint16PtrSlice []*uint16 + + Uint32 uint32 + Uint32Ptr *uint32 + Uint32Array [2]uint32 + Uint32ArrayPtr *[2]uint32 + Uint32PtrArray [2]*uint32 + Uint32Slice []uint32 + Uint32PtrSlice []*uint32 + + Uint64 uint64 + Uint64Ptr *uint64 + Uint64Array [2]uint64 + Uint64ArrayPtr *[2]uint64 + Uint64PtrArray [2]*uint64 + Uint64Slice []uint64 + Uint64PtrSlice []*uint64 + + Uint uint + UintPtr *uint + UintArray [2]uint + UintArrayPtr *[2]uint + UintPtrArray [2]*uint + UintSlice []uint + UintPtrSlice []*uint + + Int8 int8 + Int8Ptr *int8 + Int8Array [2]int8 + Int8ArrayPtr *[2]int8 + Int8PtrArray [2]*int8 + Int8Slice []int8 + Int8PtrSlice []*int8 + + Int16 int16 + Int16Ptr *int16 + Int16Array [2]int16 + Int16ArrayPtr *[2]int16 + Int16PtrArray [2]*int16 + Int16Slice []int16 + Int16PtrSlice []*int16 + + Int32 int32 + Int32Ptr *int32 + Int32Array [2]int32 + Int32ArrayPtr *[2]int32 + Int32PtrArray [2]*int32 + Int32Slice []int32 + Int32PtrSlice []*int32 + + Int64 int64 + Int64Ptr *int64 + Int64Array [2]int64 + Int64ArrayPtr *[2]int64 + Int64PtrArray [2]*int64 + Int64Slice []int64 + Int64PtrSlice []*int64 + + Int int + IntPtr *int + IntArray [2]int + IntArrayPtr *[2]int + IntPtrArray [2]*int + IntSlice []int + IntPtrSlice []*int + + Float32 float32 + Float32Ptr *float32 + Float32Array [2]float32 + Float32ArrayPtr *[2]float32 + Float32PtrArray [2]*float32 + Float32Slice []float32 + Float32PtrSlice []*float32 + + Float64 float64 + Float64Ptr *float64 + Float64Array [2]float64 + Float64ArrayPtr *[2]float64 + Float64PtrArray [2]*float64 + Float64Slice []float64 + Float64PtrSlice []*float64 + + Complex64 complex64 + Complex64Ptr *complex64 + Complex64Array [2]complex64 + Complex64ArrayPtr *[2]complex64 + Complex64PtrArray [2]*complex64 + Complex64Slice []complex64 + Complex64PtrSlice []*complex64 + + Complex128 complex128 + Complex128Ptr *complex128 + Complex128Array [2]complex128 + Complex128ArrayPtr *[2]complex128 + Complex128PtrArray [2]*complex128 + Complex128Slice []complex128 + Complex128PtrSlice []*complex128 + + // uint8 + Byte byte + BytePtr *byte + ByteArray [2]byte + ByteArrayPtr *[2]byte + BytePtrArray [2]*byte + ByteSlice []byte + BytePtrSlice []*byte + + // int32 + Rune rune + RunePtr *rune + RuneArray [2]rune + RuneArrayPtr *[2]rune + RunePtrArray [2]*rune + RuneSlice []rune + RunePtrSlice []*rune + + String string + StringPtr *string + StringArray [2]string + StringArrayPtr *[2]string + StringPtrArray [2]*string + StringSlice []string + StringPtrSlice []*string + + // int64 + Duration time.Duration + DurationPtr *time.Duration + DurationArray [2]time.Duration + DurationArrayPtr *[2]time.Duration + DurationPtrArray [2]*time.Duration + DurationSlice []time.Duration + DurationPtrSlice []*time.Duration + + // Map map[string]string + // MapPtr *map[string]string + // MapArray [2]map[string]string + // MapArrayPtr *[2]map[string]string + // MapPtrArray [2]*map[string]string + // MapSlice []map[string]string + // MapPtrSlice []*map[string]string + + Time time.Time + TimePtr *time.Time + TimeArray [2]time.Time + TimeArrayPtr *[2]time.Time + TimePtrArray [2]*time.Time + TimeSlice []time.Time + TimePtrSlice []*time.Time + + Url url.URL + UrlPtr *url.URL + UrlArray [2]url.URL + UrlArrayPtr *[2]url.URL + UrlPtrArray [2]*url.URL + UrlSlice []url.URL + UrlPtrSlice []*url.URL + + Struct struct { + Int int + String string + Embed + *EmbedP + } + StructPtr *struct { + Int int + String string + Embed + *EmbedP + } + Embed + *EmbedP +} + +type Embed struct { + Int int + String string +} + +type EmbedP struct { + Int int + String string +} + func TestNewInput(t *testing.T) { t.Run("bad inputs", func(t *testing.T) { t.Parallel() @@ -74,7 +328,7 @@ func TestNewInput(t *testing.T) { } }) - t.Run("bad fields", func(t *testing.T) { + t.Run("bad types", func(t *testing.T) { t.Parallel() t.Run("unexported fields", func(t *testing.T) { @@ -87,47 +341,7 @@ func TestNewInput(t *testing.T) { assert.Empty(t, in.Fields) }) - badFields := struct { - Ui uintptr - Uip *uintptr - C chan int - Cp *chan int - F func() - Fp *func() - I interface{} - Ip *interface{} - Up unsafe.Pointer - Upp *unsafe.Pointer - Ss [][]int - Ssp *[][]int - Sa [][0]int - Sap *[][0]int - Aa [0][0]int - Aap *[0][0]int - As [0][]int - Asp *[0][]int - Sui []uintptr - SuiP *[]uintptr - Sc []chan int - Scp *[]chan int - Sf []func() - Sfp *[]func() - Si []interface{} - Sip *[]interface{} - Su []unsafe.Pointer - Sup *[]unsafe.Pointer - Aui [0]uintptr - AuiP *[0]uintptr - Ac [0]chan int - Acp *[0]chan int - Af [0]func() - Afp *[0]func() - Ai [0]interface{} - Aip *[0]interface{} - Au [0]unsafe.Pointer - Aup *[0]unsafe.Pointer - }{} - v := reflect.ValueOf(badFields) + v := reflect.ValueOf(badTypes{}) for i := 0; i < v.NumField(); i++ { i := i @@ -216,222 +430,376 @@ func TestNewInput(t *testing.T) { ass.Equal([]string{"First", "SecondSibling"}, in.Fields[2].Path) }) - t.Run("pointer field", func(t *testing.T) { + t.Run("supported types", func(t *testing.T) { t.Parallel() - pf := struct { - Ip *int - }{} + in, err := NewInput(new(supportedTypes)) + require.NoError(t, err) + require.NotNil(t, in) + require.Len(t, in.Fields, 163) + }) +} + +func TestInput_SetValue(t *testing.T) { + t.Run("bad types", func(t *testing.T) { + t.Parallel() + + in := Input{} + + t.Run("unexported fields", func(t *testing.T) { + t.Parallel() + + f := Field{ + Value: reflect.Zero(reflect.TypeOf(0)), + } + + err := in.SetValue(&f, "") + require.Error(t, err) + assert.Truef( + t, + errors.Is(err, ErrUnSettableField), + "Error must wrap ErrUnSettableField error", + ) + }) + + v := reflect.ValueOf(badTypes{}) + for i := 0; i < v.NumField(); i++ { + i := i + t.Run(fmt.Sprint(v.Field(i).Type()), func(t *testing.T) { + t.Parallel() + + f := Field{ + Value: reflect.New(v.Field(i).Type()).Elem(), + Tags: new(ConfigTags), + } + + err := in.SetValue(&f, "") + require.Error(t, err) + assert.Truef( + t, + errors.Is(err, ErrUnsupportedType), + "Error must wrap ErrUnsupportedType error", + ) + }) + } + }) - in, err := NewInput(&pf) + t.Run("supported types", func(t *testing.T) { + t.Parallel() + + var ( + b bool = true + ui8 uint8 = 1 + ui16 uint16 = 1 + ui32 uint32 = 1 + ui64 uint64 = 1 + ui uint = 1 + i8 int8 = 1 + i16 int16 = 1 + i32 int32 = 1 + i64 int64 = 1 + i int = 1 + f32 float32 = 3.14 + f64 float64 = 3.14 + c64 complex64 = 2 + 3i + c128 complex128 = 2 + 3i + by byte = 1 + r rune = 1 + s string = "nice" + d time.Duration = 60000000000 + ) + ti, _ := time.Parse(time.RFC3339, "2020-11-26T18:26:14+03:30") + ti2, _ := time.Parse(time.RFC3339, "2021-11-26T18:26:49+03:30") + ur, _ := url.Parse("golang.org") + ur2, _ := url.Parse("google.com") + + tests := []struct { + input string + expected interface{} + }{ + {"true", true}, + {"true", &b}, + {"true false", [2]bool{true, false}}, + {"true false", &[2]bool{true, false}}, + {"true true", [2]*bool{&b, &b}}, + {"true false", []bool{true, false}}, + {"true true", []*bool{&b, &b}}, + + {"1", uint8(1)}, + {"1", &ui8}, + {"1 2", [2]uint8{1, 2}}, + {"1 2", &[2]uint8{1, 2}}, + {"1 1", [2]*uint8{&ui8, &ui8}}, + {"1 2", []uint8{1, 2}}, + {"1 1", []*uint8{&ui8, &ui8}}, + + {"1", uint16(1)}, + {"1", &ui16}, + {"1 2", [2]uint16{1, 2}}, + {"1 2", &[2]uint16{1, 2}}, + {"1 1", [2]*uint16{&ui16, &ui16}}, + {"1 2", []uint16{1, 2}}, + {"1 1", []*uint16{&ui16, &ui16}}, + + {"1", uint32(1)}, + {"1", &ui32}, + {"1 2", [2]uint32{1, 2}}, + {"1 2", &[2]uint32{1, 2}}, + {"1 1", [2]*uint32{&ui32, &ui32}}, + {"1 2", []uint32{1, 2}}, + {"1 1", []*uint32{&ui32, &ui32}}, + + {"1", uint64(1)}, + {"1", &ui64}, + {"1 2", [2]uint64{1, 2}}, + {"1 2", &[2]uint64{1, 2}}, + {"1 1", [2]*uint64{&ui64, &ui64}}, + {"1 2", []uint64{1, 2}}, + {"1 1", []*uint64{&ui64, &ui64}}, + + {"1", uint(1)}, + {"1", &ui}, + {"1 2", [2]uint{1, 2}}, + {"1 2", &[2]uint{1, 2}}, + {"1 1", [2]*uint{&ui, &ui}}, + {"1 2", []uint{1, 2}}, + {"1 1", []*uint{&ui, &ui}}, + + {"1", int8(1)}, + {"1", &i8}, + {"1 2", [2]int8{1, 2}}, + {"1 2", &[2]int8{1, 2}}, + {"1 1", [2]*int8{&i8, &i8}}, + {"1 2", []int8{1, 2}}, + {"1 1", []*int8{&i8, &i8}}, + + {"1", int16(1)}, + {"1", &i16}, + {"1 2", [2]int16{1, 2}}, + {"1 2", &[2]int16{1, 2}}, + {"1 1", [2]*int16{&i16, &i16}}, + {"1 2", []int16{1, 2}}, + {"1 1", []*int16{&i16, &i16}}, + + {"1", int32(1)}, + {"1", &i32}, + {"1 2", [2]int32{1, 2}}, + {"1 2", &[2]int32{1, 2}}, + {"1 1", [2]*int32{&i32, &i32}}, + {"1 2", []int32{1, 2}}, + {"1 1", []*int32{&i32, &i32}}, + + {"1", int64(1)}, + {"1", &i64}, + {"1 2", [2]int64{1, 2}}, + {"1 2", &[2]int64{1, 2}}, + {"1 1", [2]*int64{&i64, &i64}}, + {"1 2", []int64{1, 2}}, + {"1 1", []*int64{&i64, &i64}}, + + {"1", int(1)}, + {"1", &i}, + {"1 2", [2]int{1, 2}}, + {"1 2", &[2]int{1, 2}}, + {"1 1", [2]*int{&i, &i}}, + {"1 2", []int{1, 2}}, + {"1 1", []*int{&i, &i}}, + + {"3.14", float32(3.14)}, + {"3.14", &f32}, + {"3.14 0.1", [2]float32{3.14, 0.1}}, + {"3.14 0.1", &[2]float32{3.14, 0.1}}, + {"3.14 3.14", [2]*float32{&f32, &f32}}, + {"3.14 0.1", []float32{3.14, 0.1}}, + {"3.14 3.14", []*float32{&f32, &f32}}, + + {"3.14", float64(3.14)}, + {"3.14", &f64}, + {"3.14 0.1", [2]float64{3.14, 0.1}}, + {"3.14 0.1", &[2]float64{3.14, 0.1}}, + {"3.14 3.14", [2]*float64{&f64, &f64}}, + {"3.14 0.1", []float64{3.14, 0.1}}, + {"3.14 3.14", []*float64{&f64, &f64}}, + + {"2+3i", complex64(2 + 3i)}, + {"2+3i", &c64}, + {"2+3i 4-1i", [2]complex64{2 + 3i, 4 - 1i}}, + {"2+3i 4-1i", &[2]complex64{2 + 3i, 4 - 1i}}, + {"2+3i 2+3i", [2]*complex64{&c64, &c64}}, + {"2+3i 4-1i", []complex64{2 + 3i, 4 - 1i}}, + {"2+3i 2+3i", []*complex64{&c64, &c64}}, + + {"2+3i", complex128(2 + 3i)}, + {"2+3i", &c128}, + {"2+3i 4-1i", [2]complex128{2 + 3i, 4 - 1i}}, + {"2+3i 4-1i", &[2]complex128{2 + 3i, 4 - 1i}}, + {"2+3i 2+3i", [2]*complex128{&c128, &c128}}, + {"2+3i 4-1i", []complex128{2 + 3i, 4 - 1i}}, + {"2+3i 2+3i", []*complex128{&c128, &c128}}, + + {"1", byte(1)}, + {"1", &by}, + {"1 2", [2]byte{1, 2}}, + {"1 2", &[2]byte{1, 2}}, + {"1 1", [2]*byte{&by, &by}}, + {"1 2", []byte{1, 2}}, + {"1 1", []*byte{&by, &by}}, + + {"1", rune(1)}, + {"1", &r}, + {"1 2", [2]rune{1, 2}}, + {"1 2", &[2]rune{1, 2}}, + {"1 1", [2]*rune{&r, &r}}, + {"1 2", []rune{1, 2}}, + {"1 1", []*rune{&r, &r}}, + + {"config", "config"}, + {"nice", &s}, + {"nice config", [2]string{"nice", "config"}}, + {"nice config", &[2]string{"nice", "config"}}, + {"nice nice", [2]*string{&s, &s}}, + {"nice config", []string{"nice", "config"}}, + {"nice nice", []*string{&s, &s}}, + + {"1m", time.Duration(60000000000)}, + {"1m", &d}, + {"1m 1s", [2]time.Duration{60000000000, 1000000000}}, + {"1m 1s", &[2]time.Duration{60000000000, 1000000000}}, + {"1m 1m", [2]*time.Duration{&d, &d}}, + {"1m 1s", []time.Duration{60000000000, 1000000000}}, + {"1m 1m", []*time.Duration{&d, &d}}, + + {"2020-11-26T18:26:14+03:30", ti}, + {"2020-11-26T18:26:14+03:30", &ti}, + {"2020-11-26T18:26:14+03:30 2021-11-26T18:26:49+03:30", [2]time.Time{ti, ti2}}, + {"2020-11-26T18:26:14+03:30 2021-11-26T18:26:49+03:30", &[2]time.Time{ti, ti2}}, + {"2020-11-26T18:26:14+03:30 2021-11-26T18:26:49+03:30", [2]*time.Time{&ti, &ti2}}, + {"2020-11-26T18:26:14+03:30 2021-11-26T18:26:49+03:30", []time.Time{ti, ti2}}, + {"2020-11-26T18:26:14+03:30 2021-11-26T18:26:49+03:30", []*time.Time{&ti, &ti2}}, + + {"golang.org", *ur}, + {"golang.org", ur}, + {"golang.org google.com", [2]url.URL{*ur, *ur2}}, + {"golang.org google.com", &[2]url.URL{*ur, *ur2}}, + {"golang.org google.com", [2]*url.URL{ur, ur2}}, + {"golang.org google.com", []url.URL{*ur, *ur2}}, + {"golang.org google.com", []*url.URL{ur, ur2}}, + + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + {"1", int(1)}, + {"config", "config"}, + } + + in, err := NewInput(new(supportedTypes)) require.NoError(t, err) require.NotNil(t, in) - require.Len(t, in.Fields, 1) + require.Equal(t, len(in.Fields), len(tests)) + + for i, tc := range tests { + f := in.Fields[i] + tc := tc - require.NotNil(t, pf.Ip) - assert.Zero(t, *pf.Ip) + t.Run(strings.Join(f.Path, "."), func(t *testing.T) { + t.Parallel() + err := in.SetValue(f, tc.input) + require.NoError(t, err) + require.Equal(t, tc.expected, f.Value.Interface()) + }) + } }) - t.Run("all possible types", func(t *testing.T) { + t.Run("parse error", func(t *testing.T) { t.Parallel() - type Embed struct { - Int int - String string + input := struct { + B bool + I int + U uint + F float64 + C complex128 + D time.Duration + T time.Time + Ur url.URL + }{} + tests := []string{ + "bool", + "int", + "uint", + "float", + "complex", + "duration", + "time", + "!@#$%^&*()-=+", } - type possibleTypes struct { - Boolean bool - BooleanPtr *bool - BooleanArray [2]bool - BooleanArrayPtr *[2]bool - BooleanPtrArray [2]*bool - BooleanSlice []bool - BooleanPtrSlice []*bool - - Uint8 uint8 - Uint8Ptr *uint8 - Uint8Array [2]uint8 - Uint8ArrayPtr *[2]uint8 - Uint8PtrArray [2]*uint8 - Uint8Slice []uint8 - Uint8PtrSlice []*uint8 - - Uint16 uint16 - Uint16Ptr *uint16 - Uint16Array [2]uint16 - Uint16ArrayPtr *[2]uint16 - Uint16PtrArray [2]*uint16 - Uint16Slice []uint16 - Uint16PtrSlice []*uint16 - - Uint32 uint32 - Uint32Ptr *uint32 - Uint32Array [2]uint32 - Uint32ArrayPtr *[2]uint32 - Uint32PtrArray [2]*uint32 - Uint32Slice []uint32 - Uint32PtrSlice []*uint32 - - Uint64 uint64 - Uint64Ptr *uint64 - Uint64Array [2]uint64 - Uint64ArrayPtr *[2]uint64 - Uint64PtrArray [2]*uint64 - Uint64Slice []uint64 - Uint64PtrSlice []*uint64 - - Uint uint - UintPtr *uint - UintArray [2]uint - UintArrayPtr *[2]uint - UintPtrArray [2]*uint - UintSlice []uint - UintPtrSlice []*uint - - Int8 int8 - Int8Ptr *int8 - Int8Array [2]int8 - Int8ArrayPtr *[2]int8 - Int8PtrArray [2]*int8 - Int8Slice []int8 - Int8PtrSlice []*int8 - - Int16 int16 - Int16Ptr *int16 - Int16Array [2]int16 - Int16ArrayPtr *[2]int16 - Int16PtrArray [2]*int16 - Int16Slice []int16 - Int16PtrSlice []*int16 - - Int32 int32 - Int32Ptr *int32 - Int32Array [2]int32 - Int32ArrayPtr *[2]int32 - Int32PtrArray [2]*int32 - Int32Slice []int32 - Int32PtrSlice []*int32 - - Int64 int64 - Int64Ptr *int64 - Int64Array [2]int64 - Int64ArrayPtr *[2]int64 - Int64PtrArray [2]*int64 - Int64Slice []int64 - Int64PtrSlice []*int64 - - Int int - IntPtr *int - IntArray [2]int - IntArrayPtr *[2]int - IntPtrArray [2]*int - IntSlice []int - IntPtrSlice []*int - - Float32 float32 - Float32Ptr *float32 - Float32Array [2]float32 - Float32ArrayPtr *[2]float32 - Float32PtrArray [2]*float32 - Float32Slice []float32 - Float32PtrSlice []*float32 - - Float64 float64 - Float64Ptr *float64 - Float64Array [2]float64 - Float64ArrayPtr *[2]float64 - Float64PtrArray [2]*float64 - Float64Slice []float64 - Float64PtrSlice []*float64 - - Complex64 complex64 - Complex64Ptr *complex64 - Complex64Array [2]complex64 - Complex64ArrayPtr *[2]complex64 - Complex64PtrArray [2]*complex64 - Complex64Slice []complex64 - Complex64PtrSlice []*complex64 - - Complex128 complex128 - Complex128Ptr *complex128 - Complex128Array [2]complex128 - Complex128ArrayPtr *[2]complex128 - Complex128PtrArray [2]*complex128 - Complex128Slice []complex128 - Complex128PtrSlice []*complex128 - - // uint8 - Byte byte - BytePtr *byte - ByteArray [2]byte - ByteArrayPtr *[2]byte - BytePtrArray [2]*byte - ByteSlice []byte - BytePtrSlice []*byte - - // int32 - Rune rune - RunePtr *rune - RuneArray [2]rune - RuneArrayPtr *[2]rune - RunePtrArray [2]*rune - RuneSlice []rune - RunePtrSlice []*rune - - String string - StringPtr *string - StringArray [2]string - StringArrayPtr *[2]string - StringPtrArray [2]*string - StringSlice []string - StringPtrSlice []*string - - // int64 - Duration time.Duration - DurationPtr *time.Duration - DurationArray [2]time.Duration - DurationArrayPtr *[2]time.Duration - DurationPtrArray [2]*time.Duration - DurationSlice []time.Duration - DurationPtrSlice []*time.Duration - - Map map[string]string - MapPtr *map[string]string - MapArray [2]map[string]string - MapArrayPtr *[2]map[string]string - MapPtrArray [2]*map[string]string - MapSlice []map[string]string - MapPtrSlice []*map[string]string - - Time time.Time - TimePtr *time.Time - TimeArray [2]time.Time - TimeArrayPtr *[2]time.Time - TimePtrArray [2]*time.Time - TimeSlice []time.Time - TimePtrSlice []*time.Time - - Url url.URL - UrlPtr *url.URL - UrlArray [2]url.URL - UrlArrayPtr *[2]url.URL - UrlPtrArray [2]*url.URL - UrlSlice []url.URL - UrlPtrSlice []*url.URL - - Struct struct { - Int int - } - StructPtr *struct { - Int int - } - Embed + in, err := NewInput(&input) + require.NoError(t, err) + require.NotNil(t, in) + require.Equal(t, len(tests), len(in.Fields)) + + for i, tc := range tests { + f := in.Fields[i] + tc := tc + + t.Run(tc, func(t *testing.T) { + err := in.SetValue(f, tc) + require.Error(t, err) + assert.Truef( + t, + errors.Is(err, ErrParsing), + "Error must wrap ErrParsing error", + ) + }) + } + }) + + t.Run("value overflow", func(t *testing.T) { + t.Parallel() + + input := struct { + I int8 + U uint8 + F float32 + C complex64 + }{} + tests := []string{ + "128", + "256", + fmt.Sprint(math.MaxFloat64), + fmt.Sprint(complex(math.MaxFloat64, math.MaxFloat64)), } - in, err := NewInput(new(possibleTypes)) + in, err := NewInput(&input) require.NoError(t, err) require.NotNil(t, in) - require.Len(t, in.Fields, 158) + require.Equal(t, len(tests), len(in.Fields)) + + for i, tc := range tests { + f := in.Fields[i] + tc := tc + + t.Run(tc, func(t *testing.T) { + err := in.SetValue(f, tc) + require.Error(t, err) + assert.Truef( + t, + errors.Is(err, ErrValueOverflow), + "Error must wrap ErrValueOverflow error", + ) + }) + } }) } diff --git a/utils.go b/utils.go index 09b8420..5ed2850 100644 --- a/utils.go +++ b/utils.go @@ -21,16 +21,20 @@ func toSnakeCase(s string) string { return out } -func isDuration(v reflect.Value) bool { - return v.Type().PkgPath() == "time" && v.Type().Name() == "Duration" +func isStruct(t reflect.Type) bool { + return t.Kind() == reflect.Struct && !isTime(t) && !isURL(t) } -func isTime(v reflect.Value) bool { - return v.Type().PkgPath() == "time" && v.Type().Name() == "Time" +func isDuration(t reflect.Type) bool { + return t.PkgPath() == "time" && t.Name() == "Duration" } -func isURL(v reflect.Value) bool { - return v.Type().PkgPath() == "net/url" && v.Type().Name() == "URL" +func isTime(t reflect.Type) bool { + return t.PkgPath() == "time" && t.Name() == "Time" +} + +func isURL(t reflect.Type) bool { + return t.PkgPath() == "net/url" && t.Name() == "URL" } // traverseMap finds a value in a map based on provided path @@ -59,3 +63,16 @@ func traverseMap(m map[string]interface{}, path []string) (string, bool) { return traverseMap(nestedMap, path) } + +// extractItems splits and trims input string based on separator +func extractItems(str string, sep string) []string { + var items []string + for _, v := range strings.Split(str, sep) { + item := strings.TrimSpace(v) + if len(item) > 0 { + items = append(items, item) + } + } + + return items +}