├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── defaults.go ├── defaults_test.go ├── doc.go ├── factory.go ├── factory_test.go ├── filler.go ├── filler_test.go ├── go.mod └── go.sum /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Install Go 8 | uses: actions/setup-go@v1 9 | with: 10 | go-version: 1.14.x 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Test 14 | run: go test ./... -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Máximo Cuadros 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-defaults [![Build Status](https://img.shields.io/github/workflow/status/mcuadros/go-defaults/Test.svg)](https://github.com/mcuadros/go-defaults/actions) [![GoDoc](http://godoc.org/github.com/mcuadros/go-defaults?status.png)](https://pkg.go.dev/github.com/mcuadros/go-defaults) [![GitHub release](https://img.shields.io/github/release/mcuadros/go-defaults.svg)](https://github.com/mcuadros/go-defaults/releases) 2 | ============================== 3 | 4 | Enabling stuctures with defaults values using [struct tags](http://golang.org/pkg/reflect/#StructTag). 5 | 6 | Installation 7 | ------------ 8 | 9 | The recommended way to install go-defaults 10 | 11 | ``` 12 | go get github.com/mcuadros/go-defaults 13 | ``` 14 | 15 | Examples 16 | -------- 17 | 18 | A basic example: 19 | 20 | ```go 21 | import ( 22 | "fmt" 23 | "github.com/mcuadros/go-defaults" 24 | "time" 25 | ) 26 | 27 | type ExampleBasic struct { 28 | Foo bool `default:"true"` //<-- StructTag with a default key 29 | Bar string `default:"33"` 30 | Qux int8 31 | Dur time.Duration `default:"1m"` 32 | } 33 | 34 | func NewExampleBasic() *ExampleBasic { 35 | example := new(ExampleBasic) 36 | defaults.SetDefaults(example) //<-- This set the defaults values 37 | 38 | return example 39 | } 40 | 41 | ... 42 | 43 | test := NewExampleBasic() 44 | fmt.Println(test.Foo) //Prints: true 45 | fmt.Println(test.Bar) //Prints: 33 46 | fmt.Println(test.Qux) //Prints: 47 | fmt.Println(test.Dur) //Prints: 1m0s 48 | ``` 49 | 50 | Caveats 51 | ------- 52 | 53 | At the moment, the way the default filler checks whether it should fill a struct field or not is by comparing the current field value with the corresponding zero value of that type. This has a subtle implication: the zero value set explicitly by you will get overriden by default value during `SetDefaults()` call. So if you need to set the field to container zero value, you need to set it explicitly AFTER setting the defaults. 54 | 55 | Take the basic example in the above section and change it slightly: 56 | ```go 57 | 58 | example := ExampleBasic{ 59 | Bar: 0, 60 | } 61 | defaults.SetDefaults(example) 62 | fmt.Println(example.Bar) //Prints: 33 instead of 0 (which is zero value for int) 63 | 64 | example.Bar = 0 // set needed zero value AFTER applying defaults 65 | fmt.Println(example.Bar) //Prints: 0 66 | 67 | ``` 68 | 69 | License 70 | ------- 71 | 72 | MIT, see [LICENSE](LICENSE) 73 | -------------------------------------------------------------------------------- /defaults.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Applies the default values to the struct object, the struct type must have 12 | // the StructTag with name "default" and the directed value. 13 | // 14 | // Usage 15 | // type ExampleBasic struct { 16 | // Foo bool `default:"true"` 17 | // Bar string `default:"33"` 18 | // Qux int8 19 | // Dur time.Duration `default:"2m3s"` 20 | // } 21 | // 22 | // foo := &ExampleBasic{} 23 | // SetDefaults(foo) 24 | func SetDefaults(variable interface{}) { 25 | getDefaultFiller().Fill(variable) 26 | } 27 | 28 | var defaultFiller *Filler = nil 29 | 30 | func getDefaultFiller() *Filler { 31 | if defaultFiller == nil { 32 | defaultFiller = newDefaultFiller() 33 | } 34 | 35 | return defaultFiller 36 | } 37 | 38 | func newDefaultFiller() *Filler { 39 | funcs := make(map[reflect.Kind]FillerFunc, 0) 40 | funcs[reflect.Bool] = func(field *FieldData) { 41 | value, _ := strconv.ParseBool(field.TagValue) 42 | field.Value.SetBool(value) 43 | } 44 | 45 | funcs[reflect.Int] = func(field *FieldData) { 46 | value, _ := strconv.ParseInt(field.TagValue, 10, 64) 47 | field.Value.SetInt(value) 48 | } 49 | 50 | funcs[reflect.Int8] = funcs[reflect.Int] 51 | funcs[reflect.Int16] = funcs[reflect.Int] 52 | funcs[reflect.Int32] = funcs[reflect.Int] 53 | funcs[reflect.Int64] = func(field *FieldData) { 54 | if field.Field.Type == reflect.TypeOf(time.Second) { 55 | value, _ := time.ParseDuration(field.TagValue) 56 | field.Value.Set(reflect.ValueOf(value)) 57 | } else { 58 | value, _ := strconv.ParseInt(field.TagValue, 10, 64) 59 | field.Value.SetInt(value) 60 | } 61 | } 62 | 63 | funcs[reflect.Float32] = func(field *FieldData) { 64 | value, _ := strconv.ParseFloat(field.TagValue, 64) 65 | field.Value.SetFloat(value) 66 | } 67 | 68 | funcs[reflect.Float64] = funcs[reflect.Float32] 69 | 70 | funcs[reflect.Uint] = func(field *FieldData) { 71 | value, _ := strconv.ParseUint(field.TagValue, 10, 64) 72 | field.Value.SetUint(value) 73 | } 74 | 75 | funcs[reflect.Uint8] = funcs[reflect.Uint] 76 | funcs[reflect.Uint16] = funcs[reflect.Uint] 77 | funcs[reflect.Uint32] = funcs[reflect.Uint] 78 | funcs[reflect.Uint64] = funcs[reflect.Uint] 79 | 80 | funcs[reflect.String] = func(field *FieldData) { 81 | tagValue := parseDateTimeString(field.TagValue) 82 | field.Value.SetString(tagValue) 83 | } 84 | 85 | funcs[reflect.Struct] = func(field *FieldData) { 86 | fields := getDefaultFiller().GetFieldsFromValue(field.Value, nil) 87 | getDefaultFiller().SetDefaultValues(fields) 88 | } 89 | 90 | types := make(map[TypeHash]FillerFunc, 1) 91 | types["time.Duration"] = func(field *FieldData) { 92 | d, _ := time.ParseDuration(field.TagValue) 93 | field.Value.Set(reflect.ValueOf(d)) 94 | } 95 | 96 | funcs[reflect.Slice] = func(field *FieldData) { 97 | k := field.Value.Type().Elem().Kind() 98 | switch k { 99 | case reflect.Uint8: 100 | if field.Value.Bytes() != nil { 101 | return 102 | } 103 | field.Value.SetBytes([]byte(field.TagValue)) 104 | case reflect.Struct: 105 | count := field.Value.Len() 106 | for i := 0; i < count; i++ { 107 | fields := getDefaultFiller().GetFieldsFromValue(field.Value.Index(i), nil) 108 | getDefaultFiller().SetDefaultValues(fields) 109 | } 110 | default: 111 | //处理形如 [1,2,3,4] 112 | reg := regexp.MustCompile(`^\[(.*)\]$`) 113 | matchs := reg.FindStringSubmatch(field.TagValue) 114 | if len(matchs) != 2 { 115 | return 116 | } 117 | if matchs[1] == "" { 118 | field.Value.Set(reflect.MakeSlice(field.Value.Type(), 0, 0)) 119 | } else { 120 | defaultValue := strings.Split(matchs[1], ",") 121 | result := reflect.MakeSlice(field.Value.Type(), len(defaultValue), len(defaultValue)) 122 | for i := 0; i < len(defaultValue); i++ { 123 | itemValue := result.Index(i) 124 | item := &FieldData{ 125 | Value: itemValue, 126 | Field: reflect.StructField{}, 127 | TagValue: defaultValue[i], 128 | Parent: nil, 129 | } 130 | funcs[k](item) 131 | } 132 | field.Value.Set(result) 133 | } 134 | } 135 | } 136 | 137 | return &Filler{FuncByKind: funcs, FuncByType: types, Tag: "default"} 138 | } 139 | 140 | func parseDateTimeString(data string) string { 141 | 142 | pattern := regexp.MustCompile(`\{\{(\w+\:(?:-|)\d*,(?:-|)\d*,(?:-|)\d*)\}\}`) 143 | matches := pattern.FindAllStringSubmatch(data, -1) // matches is [][]string 144 | for _, match := range matches { 145 | 146 | tags := strings.Split(match[1], ":") 147 | if len(tags) == 2 { 148 | 149 | valueStrings := strings.Split(tags[1], ",") 150 | if len(valueStrings) == 3 { 151 | var values [3]int 152 | for key, valueString := range valueStrings { 153 | num, _ := strconv.ParseInt(valueString, 10, 64) 154 | values[key] = int(num) 155 | } 156 | 157 | switch tags[0] { 158 | 159 | case "date": 160 | str := time.Now().AddDate(values[0], values[1], values[2]).Format("2006-01-02") 161 | data = strings.Replace(data, match[0], str, -1) 162 | break 163 | case "time": 164 | str := time.Now().Add((time.Duration(values[0]) * time.Hour) + 165 | (time.Duration(values[1]) * time.Minute) + 166 | (time.Duration(values[2]) * time.Second)).Format("15:04:05") 167 | data = strings.Replace(data, match[0], str, -1) 168 | break 169 | } 170 | } 171 | } 172 | 173 | } 174 | return data 175 | } 176 | -------------------------------------------------------------------------------- /defaults_test.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "bou.ke/monkey" 8 | . "gopkg.in/check.v1" 9 | ) 10 | 11 | // Hook up gocheck into the "go test" runner. 12 | 13 | func Test(t *testing.T) { 14 | monkey.Patch(time.Now, func() time.Time { 15 | t, _ := time.Parse("2006-01-02 15:04:05", "2020-06-10 12:00:00") 16 | return t 17 | }) 18 | 19 | TestingT(t) 20 | } 21 | 22 | type DefaultsSuite struct{} 23 | 24 | var _ = Suite(&DefaultsSuite{}) 25 | 26 | type Parent struct { 27 | Children []Child 28 | } 29 | 30 | type Child struct { 31 | Name string 32 | Age int `default:"10"` 33 | } 34 | 35 | type ExampleBasic struct { 36 | Bool bool `default:"true"` 37 | Integer int `default:"33"` 38 | Integer8 int8 `default:"8"` 39 | Integer16 int16 `default:"16"` 40 | Integer32 int32 `default:"32"` 41 | Integer64 int64 `default:"64"` 42 | UInteger uint `default:"11"` 43 | UInteger8 uint8 `default:"18"` 44 | UInteger16 uint16 `default:"116"` 45 | UInteger32 uint32 `default:"132"` 46 | UInteger64 uint64 `default:"164"` 47 | String string `default:"foo"` 48 | Bytes []byte `default:"bar"` 49 | Float32 float32 `default:"3.2"` 50 | Float64 float64 `default:"6.4"` 51 | Struct struct { 52 | Bool bool `default:"true"` 53 | Integer int `default:"33"` 54 | } 55 | Duration time.Duration `default:"1s"` 56 | Children []Child 57 | Second time.Duration `default:"1s"` 58 | StringSlice []string `default:"[1,2,3,4]"` 59 | IntSlice []int `default:"[1,2,3,4]"` 60 | IntSliceSlice [][]int `default:"[[1],[2],[3],[4]]"` 61 | StringSliceSlice [][]string `default:"[[1],[]]"` 62 | 63 | DateTime string `default:"{{date:1,-10,0}} {{time:1,-5,10}}"` 64 | } 65 | 66 | func (s *DefaultsSuite) TestSetDefaultsBasic(c *C) { 67 | foo := &ExampleBasic{} 68 | SetDefaults(foo) 69 | 70 | s.assertTypes(c, foo) 71 | } 72 | 73 | type ExampleNested struct { 74 | Struct ExampleBasic 75 | } 76 | 77 | func (s *DefaultsSuite) TestSetDefaultsNested(c *C) { 78 | foo := &ExampleNested{} 79 | SetDefaults(foo) 80 | 81 | s.assertTypes(c, &foo.Struct) 82 | } 83 | 84 | func (s *DefaultsSuite) assertTypes(c *C, foo *ExampleBasic) { 85 | c.Assert(foo.Bool, Equals, true) 86 | c.Assert(foo.Integer, Equals, 33) 87 | c.Assert(foo.Integer8, Equals, int8(8)) 88 | c.Assert(foo.Integer16, Equals, int16(16)) 89 | c.Assert(foo.Integer32, Equals, int32(32)) 90 | c.Assert(foo.Integer64, Equals, int64(64)) 91 | c.Assert(foo.UInteger, Equals, uint(11)) 92 | c.Assert(foo.UInteger8, Equals, uint8(18)) 93 | c.Assert(foo.UInteger16, Equals, uint16(116)) 94 | c.Assert(foo.UInteger32, Equals, uint32(132)) 95 | c.Assert(foo.UInteger64, Equals, uint64(164)) 96 | c.Assert(foo.String, Equals, "foo") 97 | c.Assert(string(foo.Bytes), Equals, "bar") 98 | c.Assert(foo.Float32, Equals, float32(3.2)) 99 | c.Assert(foo.Float64, Equals, 6.4) 100 | c.Assert(foo.Struct.Bool, Equals, true) 101 | c.Assert(foo.Duration, Equals, time.Second) 102 | c.Assert(foo.Children, IsNil) 103 | c.Assert(foo.Second, Equals, time.Second) 104 | c.Assert(foo.StringSlice, DeepEquals, []string{"1", "2", "3", "4"}) 105 | c.Assert(foo.IntSlice, DeepEquals, []int{1, 2, 3, 4}) 106 | c.Assert(foo.IntSliceSlice, DeepEquals, [][]int{[]int{1}, []int{2}, []int{3}, []int{4}}) 107 | c.Assert(foo.StringSliceSlice, DeepEquals, [][]string{[]string{"1"}, []string{}}) 108 | c.Assert(foo.DateTime, Equals, "2020-08-10 12:55:10") 109 | } 110 | 111 | func (s *DefaultsSuite) TestSetDefaultsWithValues(c *C) { 112 | foo := &ExampleBasic{ 113 | Integer: 55, 114 | UInteger: 22, 115 | Float32: 9.9, 116 | String: "bar", 117 | Bytes: []byte("foo"), 118 | Children: []Child{{Name: "alice"}, {Name: "bob", Age: 2}}, 119 | } 120 | 121 | SetDefaults(foo) 122 | 123 | c.Assert(foo.Integer, Equals, 55) 124 | c.Assert(foo.UInteger, Equals, uint(22)) 125 | c.Assert(foo.Float32, Equals, float32(9.9)) 126 | c.Assert(foo.String, Equals, "bar") 127 | c.Assert(string(foo.Bytes), Equals, "foo") 128 | c.Assert(foo.Children[0].Age, Equals, 10) 129 | c.Assert(foo.Children[1].Age, Equals, 2) 130 | } 131 | 132 | func (s *DefaultsSuite) BenchmarkLogic(c *C) { 133 | for i := 0; i < c.N; i++ { 134 | foo := &ExampleBasic{} 135 | SetDefaults(foo) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This library allow to define a default value to any struct, this is made thanks 3 | to struct tags. 4 | */ 5 | package defaults 6 | -------------------------------------------------------------------------------- /factory.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "math/rand" 7 | "reflect" 8 | "time" 9 | ) 10 | 11 | func Factory(variable interface{}) { 12 | getFactoryFiller().Fill(variable) 13 | } 14 | 15 | var factoryFiller *Filler = nil 16 | 17 | func getFactoryFiller() *Filler { 18 | if factoryFiller == nil { 19 | factoryFiller = newFactoryFiller() 20 | } 21 | 22 | return factoryFiller 23 | } 24 | 25 | func newFactoryFiller() *Filler { 26 | rand.Seed(time.Now().UTC().UnixNano()) 27 | 28 | funcs := make(map[reflect.Kind]FillerFunc, 0) 29 | 30 | funcs[reflect.Bool] = func(field *FieldData) { 31 | if rand.Intn(1) == 1 { 32 | field.Value.SetBool(true) 33 | } else { 34 | field.Value.SetBool(false) 35 | } 36 | } 37 | 38 | funcs[reflect.Int] = func(field *FieldData) { 39 | field.Value.SetInt(int64(rand.Int())) 40 | } 41 | 42 | funcs[reflect.Int8] = funcs[reflect.Int] 43 | funcs[reflect.Int16] = funcs[reflect.Int] 44 | funcs[reflect.Int32] = funcs[reflect.Int] 45 | funcs[reflect.Int64] = funcs[reflect.Int] 46 | 47 | funcs[reflect.Float32] = func(field *FieldData) { 48 | field.Value.SetFloat(rand.Float64()) 49 | } 50 | 51 | funcs[reflect.Float64] = funcs[reflect.Float32] 52 | 53 | funcs[reflect.Uint] = func(field *FieldData) { 54 | field.Value.SetUint(uint64(rand.Uint32())) 55 | } 56 | 57 | funcs[reflect.Uint8] = funcs[reflect.Uint] 58 | funcs[reflect.Uint16] = funcs[reflect.Uint] 59 | funcs[reflect.Uint32] = funcs[reflect.Uint] 60 | funcs[reflect.Uint64] = funcs[reflect.Uint] 61 | 62 | funcs[reflect.String] = func(field *FieldData) { 63 | field.Value.SetString(randomString()) 64 | } 65 | 66 | funcs[reflect.Slice] = func(field *FieldData) { 67 | if field.Value.Type().Elem().Kind() == reflect.Uint8 { 68 | if field.Value.Bytes() != nil { 69 | return 70 | } 71 | 72 | field.Value.SetBytes([]byte(randomString())) 73 | } 74 | } 75 | 76 | funcs[reflect.Struct] = func(field *FieldData) { 77 | fields := getFactoryFiller().GetFieldsFromValue(field.Value, nil) 78 | getFactoryFiller().SetDefaultValues(fields) 79 | } 80 | 81 | return &Filler{FuncByKind: funcs, Tag: "factory"} 82 | } 83 | 84 | func randomString() string { 85 | hash := md5.Sum([]byte(time.Now().UTC().String())) 86 | return hex.EncodeToString(hash[:]) 87 | } 88 | -------------------------------------------------------------------------------- /factory_test.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import . "gopkg.in/check.v1" 4 | 5 | type FactorySuite struct{} 6 | 7 | var _ = Suite(&FactorySuite{}) 8 | 9 | func (s *FactorySuite) TestSetDefaultsBasic(c *C) { 10 | foo := &ExampleBasic{} 11 | Factory(foo) 12 | 13 | s.assertTypes(c, foo) 14 | } 15 | 16 | func (s *FactorySuite) assertTypes(c *C, foo *ExampleBasic) { 17 | c.Assert(foo.String, HasLen, 32) 18 | c.Assert(foo.Integer, Not(Equals), 0) 19 | c.Assert(foo.Integer8, Not(Equals), int8(0)) 20 | c.Assert(foo.Integer16, Not(Equals), int16(0)) 21 | c.Assert(foo.Integer32, Not(Equals), int32(0)) 22 | c.Assert(foo.Integer64, Not(Equals), int64(0)) 23 | c.Assert(foo.UInteger, Not(Equals), uint(0)) 24 | c.Assert(foo.UInteger8, Not(Equals), uint8(0)) 25 | c.Assert(foo.UInteger16, Not(Equals), uint16(0)) 26 | c.Assert(foo.UInteger32, Not(Equals), uint32(0)) 27 | c.Assert(foo.UInteger64, Not(Equals), uint64(0)) 28 | c.Assert(foo.String, Not(Equals), "") 29 | c.Assert(string(foo.Bytes), HasLen, 32) 30 | c.Assert(foo.Float32, Not(Equals), float32(0)) 31 | c.Assert(foo.Float64, Not(Equals), float64(0)) 32 | } 33 | -------------------------------------------------------------------------------- /filler.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type FieldData struct { 9 | Field reflect.StructField 10 | Value reflect.Value 11 | TagValue string 12 | Parent *FieldData 13 | } 14 | 15 | type FillerFunc func(field *FieldData) 16 | 17 | // Filler contains all the functions to fill any struct field with any type 18 | // allowing to define function by Kind, Type of field name 19 | type Filler struct { 20 | FuncByName map[string]FillerFunc 21 | FuncByType map[TypeHash]FillerFunc 22 | FuncByKind map[reflect.Kind]FillerFunc 23 | Tag string 24 | } 25 | 26 | // Fill apply all the functions contained on Filler, setting all the possible 27 | // values 28 | func (f *Filler) Fill(variable interface{}) { 29 | fields := f.getFields(variable) 30 | f.SetDefaultValues(fields) 31 | } 32 | 33 | func (f *Filler) getFields(variable interface{}) []*FieldData { 34 | valueObject := reflect.ValueOf(variable).Elem() 35 | 36 | return f.GetFieldsFromValue(valueObject, nil) 37 | } 38 | 39 | func (f *Filler) GetFieldsFromValue(valueObject reflect.Value, parent *FieldData) []*FieldData { 40 | typeObject := valueObject.Type() 41 | 42 | count := valueObject.NumField() 43 | var results []*FieldData 44 | for i := 0; i < count; i++ { 45 | value := valueObject.Field(i) 46 | field := typeObject.Field(i) 47 | 48 | if value.CanSet() { 49 | results = append(results, &FieldData{ 50 | Value: value, 51 | Field: field, 52 | TagValue: field.Tag.Get(f.Tag), 53 | Parent: parent, 54 | }) 55 | } 56 | } 57 | 58 | return results 59 | } 60 | 61 | func (f *Filler) SetDefaultValues(fields []*FieldData) { 62 | for _, field := range fields { 63 | if f.isEmpty(field) { 64 | f.SetDefaultValue(field) 65 | } 66 | } 67 | } 68 | 69 | func (f *Filler) isEmpty(field *FieldData) bool { 70 | switch field.Value.Kind() { 71 | case reflect.Bool: 72 | return !field.Value.Bool() 73 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 74 | return field.Value.Int() == 0 75 | case reflect.Float32, reflect.Float64: 76 | return field.Value.Float() == .0 77 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 78 | return field.Value.Uint() == 0 79 | case reflect.Slice: 80 | switch field.Value.Type().Elem().Kind() { 81 | case reflect.Struct: 82 | // always assume the structs in the slice is empty and can be filled 83 | // the actually struct filling logic should take care of the rest 84 | return true 85 | default: 86 | return field.Value.Len() == 0 87 | } 88 | case reflect.String: 89 | return field.Value.String() == "" 90 | } 91 | return true 92 | } 93 | 94 | func (f *Filler) SetDefaultValue(field *FieldData) { 95 | getters := []func(field *FieldData) FillerFunc{ 96 | f.getFunctionByName, 97 | f.getFunctionByType, 98 | f.getFunctionByKind, 99 | } 100 | 101 | for _, getter := range getters { 102 | filler := getter(field) 103 | if filler != nil { 104 | filler(field) 105 | return 106 | } 107 | } 108 | 109 | return 110 | } 111 | 112 | func (f *Filler) getFunctionByName(field *FieldData) FillerFunc { 113 | if f, ok := f.FuncByName[field.Field.Name]; ok == true { 114 | return f 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (f *Filler) getFunctionByType(field *FieldData) FillerFunc { 121 | if f, ok := f.FuncByType[GetTypeHash(field.Field.Type)]; ok == true { 122 | return f 123 | } 124 | 125 | return nil 126 | } 127 | 128 | func (f *Filler) getFunctionByKind(field *FieldData) FillerFunc { 129 | if f, ok := f.FuncByKind[field.Field.Type.Kind()]; ok == true { 130 | return f 131 | } 132 | 133 | return nil 134 | } 135 | 136 | // TypeHash is a string representing a reflect.Type following the next pattern: 137 | // . 138 | type TypeHash string 139 | 140 | // GetTypeHash returns the TypeHash for a given reflect.Type 141 | func GetTypeHash(t reflect.Type) TypeHash { 142 | if t.Kind() == reflect.Ptr { 143 | t = t.Elem() 144 | } 145 | 146 | return TypeHash(fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())) 147 | } 148 | -------------------------------------------------------------------------------- /filler_test.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | . "gopkg.in/check.v1" 8 | ) 9 | 10 | type FillerSuite struct{} 11 | 12 | var _ = Suite(&FillerSuite{}) 13 | 14 | type FixtureTypeInt int 15 | 16 | func (s *FillerSuite) TestFuncByNameIsEmpty(c *C) { 17 | calledA := false 18 | calledB := false 19 | 20 | f := &Filler{ 21 | FuncByName: map[string]FillerFunc{ 22 | "Foo": func(field *FieldData) { 23 | calledA = true 24 | }, 25 | }, 26 | FuncByKind: map[reflect.Kind]FillerFunc{ 27 | reflect.Int: func(field *FieldData) { 28 | calledB = true 29 | }, 30 | }, 31 | } 32 | 33 | f.Fill(&struct{ Foo int }{}) 34 | c.Assert(calledA, Equals, true) 35 | c.Assert(calledB, Equals, false) 36 | } 37 | 38 | func (s *FillerSuite) TestFuncByTypeIsEmpty(c *C) { 39 | calledA := false 40 | calledB := false 41 | 42 | t := GetTypeHash(reflect.TypeOf(new(FixtureTypeInt))) 43 | f := &Filler{ 44 | FuncByType: map[TypeHash]FillerFunc{ 45 | t: func(field *FieldData) { 46 | calledA = true 47 | }, 48 | }, 49 | FuncByKind: map[reflect.Kind]FillerFunc{ 50 | reflect.Int: func(field *FieldData) { 51 | calledB = true 52 | }, 53 | }, 54 | } 55 | 56 | f.Fill(&struct{ Foo FixtureTypeInt }{}) 57 | c.Assert(calledA, Equals, true) 58 | c.Assert(calledB, Equals, false) 59 | } 60 | 61 | func (s *FillerSuite) TestFuncByKindIsNotEmpty(c *C) { 62 | called := false 63 | f := &Filler{FuncByKind: map[reflect.Kind]FillerFunc{ 64 | reflect.Int: func(field *FieldData) { 65 | called = true 66 | }, 67 | }} 68 | 69 | f.Fill(&struct{ Foo int }{Foo: 42}) 70 | c.Assert(called, Equals, false) 71 | } 72 | 73 | func (s *FillerSuite) TestFuncByKindSlice(c *C) { 74 | fmt.Println(GetTypeHash(reflect.TypeOf(new([]string)))) 75 | } 76 | 77 | func (s *FillerSuite) TestFuncByKindTag(c *C) { 78 | var called string 79 | f := &Filler{Tag: "foo", FuncByKind: map[reflect.Kind]FillerFunc{ 80 | reflect.Int: func(field *FieldData) { 81 | called = field.TagValue 82 | }, 83 | }} 84 | 85 | f.Fill(&struct { 86 | Foo int `foo:"qux"` 87 | }{}) 88 | c.Assert(called, Equals, "qux") 89 | } 90 | 91 | func (s *FillerSuite) TestFuncByKindIsEmpty(c *C) { 92 | called := false 93 | f := &Filler{FuncByKind: map[reflect.Kind]FillerFunc{ 94 | reflect.Int: func(field *FieldData) { 95 | called = true 96 | }, 97 | }} 98 | 99 | f.Fill(&struct{ Foo int }{}) 100 | c.Assert(called, Equals, true) 101 | } 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mcuadros/go-defaults 2 | 3 | go 1.14 4 | 5 | require ( 6 | bou.ke/monkey v1.0.2 // indirect 7 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 8 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= 2 | bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= 3 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 4 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 7 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 8 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 9 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | --------------------------------------------------------------------------------