pax_global_header00006660000000000000000000000064144036010460014510gustar00rootroot0000000000000052 comment=3f9883636d06d18f90d4fd82a0fdcab8be9ba57b defaults-1.7.0/000077500000000000000000000000001440360104600133245ustar00rootroot00000000000000defaults-1.7.0/.circleci/000077500000000000000000000000001440360104600151575ustar00rootroot00000000000000defaults-1.7.0/.circleci/config.yml000066400000000000000000000015041440360104600171470ustar00rootroot00000000000000version: 2.1 jobs: build: working_directory: ~/repo docker: - image: circleci/golang:1.15.8 steps: - checkout - restore_cache: keys: - go-mod-v4-{{ checksum "go.sum" }} - run: name: Install Dependencies command: go mod download - save_cache: key: go-mod-v4-{{ checksum "go.sum" }} paths: - "/go/pkg/mod" - run: name: Install golangci-lint command: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ | sh -s -- -b "$(go env GOPATH)/bin" v1.51.2 - run: name: Run tests command: make ci-test - run: name: Upload coverage report command: bash <(curl -s https://codecov.io/bash) defaults-1.7.0/.gitignore000066400000000000000000000000121440360104600153050ustar00rootroot00000000000000.DS_Store defaults-1.7.0/LICENSE000066400000000000000000000020651440360104600143340ustar00rootroot00000000000000Copyright (c) 2017-present Yuki Iwanaga MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. defaults-1.7.0/Makefile000066400000000000000000000013351440360104600147660ustar00rootroot00000000000000SHELL := /bin/bash -eu -o pipefail GO_TEST_FLAGS := -v PACKAGE_DIRS := $(shell go list ./... 2> /dev/null | grep -v /vendor/) SRC_FILES := $(shell find . -name '*.go' -not -path './vendor/*') # Tasks #----------------------------------------------- .PHONY: lint lint: @gofmt -e -d -s $(SRC_FILES) | awk '{ e = 1; print $0 } END { if (e) exit(1) }' @golangci-lint --disable errcheck,unused run .PHONY: test test: lint @go test $(GO_TEST_FLAGS) $(PACKAGE_DIRS) .PHONY: ci-test ci-test: lint @echo > coverage.txt @for d in $(PACKAGE_DIRS); do \ go test -coverprofile=profile.out -covermode=atomic -race -v $$d; \ if [ -f profile.out ]; then \ cat profile.out >> coverage.txt; \ rm profile.out; \ fi; \ done defaults-1.7.0/README.md000066400000000000000000000044571440360104600146150ustar00rootroot00000000000000defaults ======== [![CircleCI](https://circleci.com/gh/creasty/defaults/tree/master.svg?style=svg)](https://circleci.com/gh/creasty/defaults/tree/master) [![codecov](https://codecov.io/gh/creasty/defaults/branch/master/graph/badge.svg)](https://codecov.io/gh/creasty/defaults) [![GitHub release](https://img.shields.io/github/release/creasty/defaults.svg)](https://github.com/creasty/defaults/releases) [![License](https://img.shields.io/github/license/creasty/defaults.svg)](./LICENSE) Initialize structs with default values - Supports almost all kind of types - Scalar types - `int/8/16/32/64`, `uint/8/16/32/64`, `float32/64` - `uintptr`, `bool`, `string` - Complex types - `map`, `slice`, `struct` - Nested types - `map[K1]map[K2]Struct`, `[]map[K1]Struct[]` - Aliased types - `time.Duration` - e.g., `type Enum string` - Pointer types - e.g., `*SampleStruct`, `*int` - Recursively initializes fields in a struct - Dynamically sets default values by [`defaults.Setter`](./setter.go) interface - Preserves non-initial values from being reset with a default value Usage ----- ```go type Gender string type Sample struct { Name string `default:"John Smith"` Age int `default:"27"` Gender Gender `default:"m"` Slice []string `default:"[]"` SliceByJSON []int `default:"[1, 2, 3]"` // Supports JSON Map map[string]int `default:"{}"` MapByJSON map[string]int `default:"{\"foo\": 123}"` MapOfStruct map[string]OtherStruct MapOfPtrStruct map[string]*OtherStruct MapOfStructWithTag map[string]OtherStruct `default:"{\"Key1\": {\"Foo\":123}}"` Struct OtherStruct `default:"{}"` StructPtr *OtherStruct `default:"{\"Foo\": 123}"` NoTag OtherStruct // Recurses into a nested struct by default OptOut OtherStruct `default:"-"` // Opt-out } type OtherStruct struct { Hello string `default:"world"` // Tags in a nested struct also work Foo int `default:"-"` Random int `default:"-"` } // SetDefaults implements defaults.Setter interface func (s *OtherStruct) SetDefaults() { if defaults.CanUpdate(s.Random) { // Check if it's a zero value (recommended) s.Random = rand.Int() // Set a dynamic value } } ``` ```go obj := &Sample{} if err := defaults.Set(obj); err != nil { panic(err) } ``` defaults-1.7.0/defaults.go000066400000000000000000000155151440360104600154710ustar00rootroot00000000000000package defaults import ( "encoding" "encoding/json" "errors" "reflect" "strconv" "time" ) var ( errInvalidType = errors.New("not a struct pointer") ) const ( fieldName = "default" ) // Set initializes members in a struct referenced by a pointer. // Maps and slices are initialized by `make` and other primitive types are set with default values. // `ptr` should be a struct pointer func Set(ptr interface{}) error { if reflect.TypeOf(ptr).Kind() != reflect.Ptr { return errInvalidType } v := reflect.ValueOf(ptr).Elem() t := v.Type() if t.Kind() != reflect.Struct { return errInvalidType } for i := 0; i < t.NumField(); i++ { if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" { if err := setField(v.Field(i), defaultVal); err != nil { return err } } } callSetter(ptr) return nil } // MustSet function is a wrapper of Set function // It will call Set and panic if err not equals nil. func MustSet(ptr interface{}) { if err := Set(ptr); err != nil { panic(err) } } func setField(field reflect.Value, defaultVal string) error { if !field.CanSet() { return nil } if !shouldInitializeField(field, defaultVal) { return nil } isInitial := isInitialValue(field) if isInitial { if unmarshalByInterface(field, defaultVal) { return nil } switch field.Kind() { case reflect.Bool: if val, err := strconv.ParseBool(defaultVal); err == nil { field.Set(reflect.ValueOf(val).Convert(field.Type())) } case reflect.Int: if val, err := strconv.ParseInt(defaultVal, 0, strconv.IntSize); err == nil { field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) } case reflect.Int8: if val, err := strconv.ParseInt(defaultVal, 0, 8); err == nil { field.Set(reflect.ValueOf(int8(val)).Convert(field.Type())) } case reflect.Int16: if val, err := strconv.ParseInt(defaultVal, 0, 16); err == nil { field.Set(reflect.ValueOf(int16(val)).Convert(field.Type())) } case reflect.Int32: if val, err := strconv.ParseInt(defaultVal, 0, 32); err == nil { field.Set(reflect.ValueOf(int32(val)).Convert(field.Type())) } case reflect.Int64: if val, err := time.ParseDuration(defaultVal); err == nil { field.Set(reflect.ValueOf(val).Convert(field.Type())) } else if val, err := strconv.ParseInt(defaultVal, 0, 64); err == nil { field.Set(reflect.ValueOf(val).Convert(field.Type())) } case reflect.Uint: if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { field.Set(reflect.ValueOf(uint(val)).Convert(field.Type())) } case reflect.Uint8: if val, err := strconv.ParseUint(defaultVal, 0, 8); err == nil { field.Set(reflect.ValueOf(uint8(val)).Convert(field.Type())) } case reflect.Uint16: if val, err := strconv.ParseUint(defaultVal, 0, 16); err == nil { field.Set(reflect.ValueOf(uint16(val)).Convert(field.Type())) } case reflect.Uint32: if val, err := strconv.ParseUint(defaultVal, 0, 32); err == nil { field.Set(reflect.ValueOf(uint32(val)).Convert(field.Type())) } case reflect.Uint64: if val, err := strconv.ParseUint(defaultVal, 0, 64); err == nil { field.Set(reflect.ValueOf(val).Convert(field.Type())) } case reflect.Uintptr: if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { field.Set(reflect.ValueOf(uintptr(val)).Convert(field.Type())) } case reflect.Float32: if val, err := strconv.ParseFloat(defaultVal, 32); err == nil { field.Set(reflect.ValueOf(float32(val)).Convert(field.Type())) } case reflect.Float64: if val, err := strconv.ParseFloat(defaultVal, 64); err == nil { field.Set(reflect.ValueOf(val).Convert(field.Type())) } case reflect.String: field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) case reflect.Slice: ref := reflect.New(field.Type()) ref.Elem().Set(reflect.MakeSlice(field.Type(), 0, 0)) if defaultVal != "" && defaultVal != "[]" { if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { return err } } field.Set(ref.Elem().Convert(field.Type())) case reflect.Map: ref := reflect.New(field.Type()) ref.Elem().Set(reflect.MakeMap(field.Type())) if defaultVal != "" && defaultVal != "{}" { if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { return err } } field.Set(ref.Elem().Convert(field.Type())) case reflect.Struct: if defaultVal != "" && defaultVal != "{}" { if err := json.Unmarshal([]byte(defaultVal), field.Addr().Interface()); err != nil { return err } } case reflect.Ptr: field.Set(reflect.New(field.Type().Elem())) } } switch field.Kind() { case reflect.Ptr: if isInitial || field.Elem().Kind() == reflect.Struct { setField(field.Elem(), defaultVal) callSetter(field.Interface()) } case reflect.Struct: if err := Set(field.Addr().Interface()); err != nil { return err } case reflect.Slice: for j := 0; j < field.Len(); j++ { if err := setField(field.Index(j), defaultVal); err != nil { return err } } case reflect.Map: for _, e := range field.MapKeys() { var v = field.MapIndex(e) switch v.Kind() { case reflect.Ptr: switch v.Elem().Kind() { case reflect.Struct, reflect.Slice, reflect.Map: if err := setField(v.Elem(), ""); err != nil { return err } } case reflect.Struct, reflect.Slice, reflect.Map: ref := reflect.New(v.Type()) ref.Elem().Set(v) if err := setField(ref.Elem(), ""); err != nil { return err } field.SetMapIndex(e, ref.Elem().Convert(v.Type())) } } } return nil } func unmarshalByInterface(field reflect.Value, defaultVal string) bool { asText, ok := field.Addr().Interface().(encoding.TextUnmarshaler) if ok && defaultVal != "" { // if field implements encode.TextUnmarshaler, try to use it before decode by kind if err := asText.UnmarshalText([]byte(defaultVal)); err == nil { return true } } asJSON, ok := field.Addr().Interface().(json.Unmarshaler) if ok && defaultVal != "" && defaultVal != "{}" && defaultVal != "[]" { // if field implements json.Unmarshaler, try to use it before decode by kind if err := asJSON.UnmarshalJSON([]byte(defaultVal)); err == nil { return true } } return false } func isInitialValue(field reflect.Value) bool { return reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface()) } func shouldInitializeField(field reflect.Value, tag string) bool { switch field.Kind() { case reflect.Struct: return true case reflect.Ptr: if !field.IsNil() && field.Elem().Kind() == reflect.Struct { return true } case reflect.Slice: return field.Len() > 0 || tag != "" case reflect.Map: return field.Len() > 0 || tag != "" } return tag != "" } // CanUpdate returns true when the given value is an initial value of its type func CanUpdate(v interface{}) bool { return isInitialValue(reflect.ValueOf(v)) } defaults-1.7.0/defaults_test.go000066400000000000000000000512761440360104600165340ustar00rootroot00000000000000package defaults import ( "encoding/json" "errors" "net" "reflect" "strconv" "testing" "time" "github.com/creasty/defaults/internal/fixture" ) type ( MyInt int MyInt8 int8 MyInt16 int16 MyInt32 int32 MyInt64 int64 MyUint uint MyUint8 uint8 MyUint16 uint16 MyUint32 uint32 MyUint64 uint64 MyUintptr uintptr MyFloat32 float32 MyFloat64 float64 MyBool bool MyString string MyMap map[string]int MySlice []int ) type Sample struct { Int int `default:"1"` Int8 int8 `default:"8"` Int16 int16 `default:"16"` Int32 int32 `default:"32"` Int64 int64 `default:"64"` Uint uint `default:"1"` Uint8 uint8 `default:"8"` Uint16 uint16 `default:"16"` Uint32 uint32 `default:"32"` Uint64 uint64 `default:"64"` Uintptr uintptr `default:"1"` Float32 float32 `default:"1.32"` Float64 float64 `default:"1.64"` BoolTrue bool `default:"true"` BoolFalse bool `default:"false"` String string `default:"hello"` Duration time.Duration `default:"10s"` IntOct int `default:"0o1"` Int8Oct int8 `default:"0o10"` Int16Oct int16 `default:"0o20"` Int32Oct int32 `default:"0o40"` Int64Oct int64 `default:"0o100"` UintOct uint `default:"0o1"` Uint8Oct uint8 `default:"0o10"` Uint16Oct uint16 `default:"0o20"` Uint32Oct uint32 `default:"0o40"` Uint64Oct uint64 `default:"0o100"` IntHex int `default:"0x1"` Int8Hex int8 `default:"0x8"` Int16Hex int16 `default:"0x10"` Int32Hex int32 `default:"0x20"` Int64Hex int64 `default:"0x40"` UintHex uint `default:"0x1"` Uint8Hex uint8 `default:"0x8"` Uint16Hex uint16 `default:"0x10"` Uint32Hex uint32 `default:"0x20"` Uint64Hex uint64 `default:"0x40"` IntBin int `default:"0b1"` Int8Bin int8 `default:"0b1000"` Int16Bin int16 `default:"0b10000"` Int32Bin int32 `default:"0b100000"` Int64Bin int64 `default:"0b1000000"` UintBin uint `default:"0b1"` Uint8Bin uint8 `default:"0b1000"` Uint16Bin uint16 `default:"0b10000"` Uint32Bin uint32 `default:"0b100000"` Uint64Bin uint64 `default:"0b1000000"` Struct Struct `default:"{}"` Map map[string]int `default:"{}"` Slice []string `default:"[]"` IntPtr *int `default:"1"` UintPtr *uint `default:"1"` Float32Ptr *float32 `default:"1"` BoolPtr *bool `default:"true"` StringPtr *string `default:"hello"` StructPtr *Struct `default:"{}"` MapPtr *map[string]int `default:"{}"` SlicePtr *[]string `default:"[]"` MyInt MyInt `default:"1"` MyInt8 MyInt8 `default:"8"` MyInt16 MyInt16 `default:"16"` MyInt32 MyInt32 `default:"32"` MyInt64 MyInt64 `default:"64"` MyUint MyUint `default:"1"` MyUint8 MyUint8 `default:"8"` MyUint16 MyUint16 `default:"16"` MyUint32 MyUint32 `default:"32"` MyUint64 MyUint64 `default:"64"` MyUintptr MyUintptr `default:"1"` MyFloat32 MyFloat32 `default:"1.32"` MyFloat64 MyFloat64 `default:"1.64"` MyBoolTrue MyBool `default:"true"` MyBoolFalse MyBool `default:"false"` MyString MyString `default:"hello"` MyMap MyMap `default:"{}"` MySlice MySlice `default:"[]"` StructWithText net.IP `default:"10.0.0.1"` StructPtrWithText *net.IP `default:"10.0.0.1"` StructWithJSON Struct `default:"{\"Foo\": 123}"` StructPtrWithJSON *Struct `default:"{\"Foo\": 123}"` MapWithJSON map[string]int `default:"{\"foo\": 123}"` TypeWithUnmarshalJSON JSONOnlyType `default:"\"one\""` MapOfPtrStruct map[string]*Struct MapOfStruct map[string]Struct MapOfStructWithTag map[string]Struct `default:"{\"Struct3\": {\"Foo\":123}}"` SliceWithJSON []string `default:"[\"foo\"]"` Empty string `default:""` NoDefault *string `default:"-"` NoDefaultStruct Struct `default:"-"` MapWithNoTag map[string]int SliceWithNoTag []string StructPtrWithNoTag *Struct StructWithNoTag Struct DeepSliceOfStructWithNoTag [][][]Struct NonInitialString string `default:"foo"` NonInitialSlice []int `default:"[123]"` NonInitialStruct Struct `default:"{}"` NonInitialStructPtr *Struct `default:"{}"` } type Struct struct { Embedded `default:"{}"` Foo int Bar int WithDefault string `default:"foo"` } func (s *Struct) SetDefaults() { s.Bar = 456 } type Embedded struct { Int int `default:"1"` } type JSONOnlyType int func (j *JSONOnlyType) UnmarshalJSON(b []byte) error { var tmp string if err := json.Unmarshal(b, &tmp); err != nil { return err } if i, err := strconv.Atoi(tmp); err == nil { *j = JSONOnlyType(i) return nil } if tmp == "one" { *j = 1 return nil } return errors.New("cannot unmarshal") } func TestMustSet(t *testing.T) { t.Run("right way", func(t *testing.T) { defer func() { if err := recover(); err != nil { t.Fatalf("it should not panic error: %v", err) } }() sample := &Sample{ NonInitialString: "string", NonInitialSlice: []int{1, 2, 3}, NonInitialStruct: Struct{Foo: 123}, NonInitialStructPtr: &Struct{Foo: 123}, DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, } MustSet(sample) }) t.Run("not struct", func(t *testing.T) { defer func() { if err := recover(); err != nil { t.Logf("panic error: %v", err) } }() var a int MustSet(&a) }) t.Run("not pointer", func(t *testing.T) { defer func() { if err := recover(); err != nil { t.Logf("panic error: %v", err) } }() sample := Sample{ NonInitialString: "string", NonInitialSlice: []int{1, 2, 3}, NonInitialStruct: Struct{Foo: 123}, NonInitialStructPtr: &Struct{Foo: 123}, DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, } MustSet(sample) }) } func TestInit(t *testing.T) { sample := &Sample{ NonInitialString: "string", NonInitialSlice: []int{1, 2, 3}, NonInitialStruct: Struct{Foo: 123}, NonInitialStructPtr: &Struct{Foo: 123}, DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, MapOfStruct: map[string]Struct{ "Struct1": {Foo: 1}, }, MapOfPtrStruct: map[string]*Struct{ "Struct1": {Foo: 1}, "Struct2": {Bar: 5}, }, } if err := Set(sample); err != nil { t.Fatalf("it should not return an error: %v", err) } nonPtrVal := 1 if err := Set(nonPtrVal); err == nil { t.Fatalf("it should return an error when used for a non-pointer type") } if err := Set(&nonPtrVal); err == nil { t.Fatalf("it should return an error when used for a non-pointer type") } Set(&fixture.Sample{}) // should not panic t.Run("primitive types", func(t *testing.T) { if sample.Int != 1 { t.Errorf("it should initialize int") } if sample.Int8 != 8 { t.Errorf("it should initialize int8") } if sample.Int16 != 16 { t.Errorf("it should initialize int16") } if sample.Int32 != 32 { t.Errorf("it should initialize int32") } if sample.Int64 != 64 { t.Errorf("it should initialize int64") } if sample.Uint != 1 { t.Errorf("it should initialize uint") } if sample.Uint8 != 8 { t.Errorf("it should initialize uint8") } if sample.Uint16 != 16 { t.Errorf("it should initialize uint16") } if sample.Uint32 != 32 { t.Errorf("it should initialize uint32") } if sample.Uint64 != 64 { t.Errorf("it should initialize uint64") } if sample.Uintptr != 1 { t.Errorf("it should initialize uintptr") } if sample.Float32 != 1.32 { t.Errorf("it should initialize float32") } if sample.Float64 != 1.64 { t.Errorf("it should initialize float64") } if sample.BoolTrue != true { t.Errorf("it should initialize bool (true)") } if sample.BoolFalse != false { t.Errorf("it should initialize bool (false)") } if *sample.BoolPtr != true { t.Errorf("it should initialize bool (true)") } if sample.String != "hello" { t.Errorf("it should initialize string") } if sample.IntOct != 0o1 { t.Errorf("it should initialize int with octal literal") } if sample.Int8Oct != 0o10 { t.Errorf("it should initialize int8 with octal literal") } if sample.Int16Oct != 0o20 { t.Errorf("it should initialize int16 with octal literal") } if sample.Int32Oct != 0o40 { t.Errorf("it should initialize int32 with octal literal") } if sample.Int64Oct != 0o100 { t.Errorf("it should initialize int64 with octal literal") } if sample.UintOct != 0o1 { t.Errorf("it should initialize uint with octal literal") } if sample.Uint8Oct != 0o10 { t.Errorf("it should initialize uint8 with octal literal") } if sample.Uint16Oct != 0o20 { t.Errorf("it should initialize uint16 with octal literal") } if sample.Uint32Oct != 0o40 { t.Errorf("it should initialize uint32 with octal literal") } if sample.Uint64Oct != 0o100 { t.Errorf("it should initialize uint64 with octal literal") } if sample.IntHex != 0x1 { t.Errorf("it should initialize int with hexadecimal literal") } if sample.Int8Hex != 0x8 { t.Errorf("it should initialize int8 with hexadecimal literal") } if sample.Int16Hex != 0x10 { t.Errorf("it should initialize int16 with hexadecimal literal") } if sample.Int32Hex != 0x20 { t.Errorf("it should initialize int32 with hexadecimal literal") } if sample.Int64Hex != 0x40 { t.Errorf("it should initialize int64 with hexadecimal literal") } if sample.UintHex != 0x1 { t.Errorf("it should initialize uint with hexadecimal literal") } if sample.Uint8Hex != 0x8 { t.Errorf("it should initialize uint8 with hexadecimal literal") } if sample.Uint16Hex != 0x10 { t.Errorf("it should initialize uint16 with hexadecimal literal") } if sample.Uint32Hex != 0x20 { t.Errorf("it should initialize uint32 with hexadecimal literal") } if sample.Uint64Hex != 0x40 { t.Errorf("it should initialize uint64 with hexadecimal literal") } if sample.IntBin != 0b1 { t.Errorf("it should initialize int with binary literal") } if sample.Int8Bin != 0b1000 { t.Errorf("it should initialize int8 with binary literal") } if sample.Int16Bin != 0b10000 { t.Errorf("it should initialize int16 with binary literal") } if sample.Int32Bin != 0b100000 { t.Errorf("it should initialize int32 with binary literal") } if sample.Int64Bin != 0b1000000 { t.Errorf("it should initialize int64 with binary literal") } if sample.UintBin != 0b1 { t.Errorf("it should initialize uint with binary literal") } if sample.Uint8Bin != 0b1000 { t.Errorf("it should initialize uint8 with binary literal") } if sample.Uint16Bin != 0b10000 { t.Errorf("it should initialize uint16 with binary literal") } if sample.Uint32Bin != 0b100000 { t.Errorf("it should initialize uint32 with binary literal") } if sample.Uint64Bin != 0b1000000 { t.Errorf("it should initialize uint64 with binary literal") } }) t.Run("complex types", func(t *testing.T) { if sample.StructPtr == nil { t.Errorf("it should initialize struct pointer") } if sample.Map == nil { t.Errorf("it should initialize map") } if sample.Slice == nil { t.Errorf("it should initialize slice") } }) t.Run("pointer types", func(t *testing.T) { if sample.IntPtr == nil || *sample.IntPtr != 1 { t.Errorf("it should initialize int pointer") } if sample.UintPtr == nil || *sample.UintPtr != 1 { t.Errorf("it should initialize uint pointer") } if sample.Float32Ptr == nil || *sample.Float32Ptr != 1 { t.Errorf("it should initialize float32 pointer") } if sample.BoolPtr == nil || *sample.BoolPtr != true { t.Errorf("it should initialize bool pointer") } if sample.StringPtr == nil || *sample.StringPtr != "hello" { t.Errorf("it should initialize string pointer") } if sample.MapPtr == nil { t.Errorf("it should initialize map ptr") } if sample.SlicePtr == nil { t.Errorf("it should initialize slice ptr") } }) t.Run("aliased types", func(t *testing.T) { if sample.MyInt != 1 { t.Errorf("it should initialize int") } if sample.MyInt8 != 8 { t.Errorf("it should initialize int8") } if sample.MyInt16 != 16 { t.Errorf("it should initialize int16") } if sample.MyInt32 != 32 { t.Errorf("it should initialize int32") } if sample.MyInt64 != 64 { t.Errorf("it should initialize int64") } if sample.MyUint != 1 { t.Errorf("it should initialize uint") } if sample.MyUint8 != 8 { t.Errorf("it should initialize uint8") } if sample.MyUint16 != 16 { t.Errorf("it should initialize uint16") } if sample.MyUint32 != 32 { t.Errorf("it should initialize uint32") } if sample.MyUint64 != 64 { t.Errorf("it should initialize uint64") } if sample.MyUintptr != 1 { t.Errorf("it should initialize uintptr") } if sample.MyFloat32 != 1.32 { t.Errorf("it should initialize float32") } if sample.MyFloat64 != 1.64 { t.Errorf("it should initialize float64") } if sample.MyBoolTrue != true { t.Errorf("it should initialize bool (true)") } if sample.MyBoolFalse != false { t.Errorf("it should initialize bool (false)") } if sample.MyString != "hello" { t.Errorf("it should initialize string") } if sample.MyMap == nil { t.Errorf("it should initialize map") } if sample.MySlice == nil { t.Errorf("it should initialize slice") } }) t.Run("nested", func(t *testing.T) { if sample.Struct.WithDefault != "foo" { t.Errorf("it should set default on inner field in struct") } if sample.StructPtr == nil || sample.StructPtr.WithDefault != "foo" { t.Errorf("it should set default on inner field in struct pointer") } if sample.Struct.Embedded.Int != 1 { t.Errorf("it should set default on an Embedded struct") } }) t.Run("complex types with text unmarshal", func(t *testing.T) { if !sample.StructWithText.Equal(net.ParseIP("10.0.0.1")) { t.Errorf("it should initialize struct with text") } if !sample.StructPtrWithText.Equal(net.ParseIP("10.0.0.1")) { t.Errorf("it should initialize struct with text") } }) t.Run("complex types with json", func(t *testing.T) { if sample.StructWithJSON.Foo != 123 { t.Errorf("it should initialize struct with json") } if sample.StructPtrWithJSON == nil || sample.StructPtrWithJSON.Foo != 123 { t.Errorf("it should initialize struct pointer with json") } if sample.MapWithJSON["foo"] != 123 { t.Errorf("it should initialize map with json") } if len(sample.SliceWithJSON) == 0 || sample.SliceWithJSON[0] != "foo" { t.Errorf("it should initialize slice with json") } if int(sample.TypeWithUnmarshalJSON) != 1 { t.Errorf("it should initialize json unmarshaled value") } t.Run("invalid json", func(t *testing.T) { if err := Set(&struct { I []int `default:"[!]"` }{}); err == nil { t.Errorf("it should return error") } if err := Set(&struct { I map[string]int `default:"{1}"` }{}); err == nil { t.Errorf("it should return error") } if err := Set(&struct { S struct { I []int } `default:"{!}"` }{}); err == nil { t.Errorf("it should return error") } if err := Set(&struct { S struct { I []int `default:"[!]"` } }{}); err == nil { t.Errorf("it should return error") } }) }) t.Run("Setter interface", func(t *testing.T) { if sample.Struct.Bar != 456 { t.Errorf("it should initialize struct") } if sample.StructPtr == nil || sample.StructPtr.Bar != 456 { t.Errorf("it should initialize struct pointer") } }) t.Run("non-initial value", func(t *testing.T) { if sample.NonInitialString != "string" { t.Errorf("it should not override non-initial value") } if !reflect.DeepEqual(sample.NonInitialSlice, []int{1, 2, 3}) { t.Errorf("it should not override non-initial value") } if !reflect.DeepEqual(sample.NonInitialStruct, Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) { t.Errorf("it should not override non-initial value but set defaults for fields") } if !reflect.DeepEqual(sample.NonInitialStructPtr, &Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) { t.Errorf("it should not override non-initial value but set defaults for fields") } }) t.Run("no tag", func(t *testing.T) { if sample.MapWithNoTag != nil { t.Errorf("it should not initialize pointer type (map)") } if sample.SliceWithNoTag != nil { t.Errorf("it should not initialize pointer type (slice)") } if sample.StructPtrWithNoTag != nil { t.Errorf("it should not initialize pointer type (struct)") } if sample.StructWithNoTag.WithDefault != "foo" { t.Errorf("it should automatically recurse into a struct even without a tag") } if !reflect.DeepEqual(sample.DeepSliceOfStructWithNoTag, [][][]Struct{{{{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}}}}) { t.Errorf("it should automatically recurse into a slice of structs even without a tag") } }) t.Run("map of struct", func(t *testing.T) { if sample.MapOfStruct == nil { t.Errorf("it should not unset an initiated map") } if len(sample.MapOfStruct) != 1 { t.Errorf("it should not override an initiated map") } if sample.MapOfStruct["Struct1"].Foo != 1 { t.Errorf("it should not override Foo field in Struct1 item") } if sample.MapOfStruct["Struct1"].Bar != 456 { t.Errorf("it should set default for Bar field in Struct1 item") } if sample.MapOfStruct["Struct1"].WithDefault != "foo" { t.Errorf("it should set default for WithDefault field in Struct1 item") } }) t.Run("map of ptr struct", func(t *testing.T) { if sample.MapOfPtrStruct == nil { t.Errorf("it should not unset an initiated map") } if len(sample.MapOfPtrStruct) != 2 { t.Errorf("it should not override an initiated map") } if sample.MapOfPtrStruct["Struct1"].Foo != 1 { t.Errorf("it should not override Foo field in Struct1 item") } if sample.MapOfPtrStruct["Struct1"].Bar != 456 { t.Errorf("it should set default for Bar field in Struct1 item") } if sample.MapOfPtrStruct["Struct1"].WithDefault != "foo" { t.Errorf("it should set default for WithDefault field in Struct1 item") } if sample.MapOfPtrStruct["Struct2"].Foo != 0 { t.Errorf("it should not override Foo field in Struct2 item") } if sample.MapOfPtrStruct["Struct2"].Bar != 456 { t.Errorf("it should using setter to set default for Bar field in a Struct2 item") } if sample.MapOfPtrStruct["Struct2"].WithDefault != "foo" { t.Errorf("it should set default for WithDefault field in Struct2 item") } }) t.Run("map of struct with tag", func(t *testing.T) { if sample.MapOfStructWithTag == nil { t.Errorf("it should set default") } if len(sample.MapOfStructWithTag) != 1 { t.Errorf("it should set default with correct value") } if sample.MapOfStructWithTag["Struct3"].Foo != 123 { t.Errorf("it should set default with correct value (Foo)") } if sample.MapOfStructWithTag["Struct1"].Bar != 0 { t.Errorf("it should set default with correct value (Bar)") } if sample.MapOfStructWithTag["Struct1"].WithDefault != "" { t.Errorf("it should set default with correct value (WithDefault)") } }) t.Run("opt-out", func(t *testing.T) { if sample.NoDefault != nil { t.Errorf("it should not be set") } if sample.NoDefaultStruct.WithDefault != "" { t.Errorf("it should not initialize a struct with default values") } }) } func TestCanUpdate(t *testing.T) { type st struct{ Int int } var myStructPtr *st pairs := map[interface{}]bool{ 0: true, 123: false, float64(0): true, float64(123): false, "": true, "string": false, false: true, true: false, st{}: true, st{Int: 123}: false, myStructPtr: true, &st{}: false, } for input, expect := range pairs { output := CanUpdate(input) if output != expect { t.Errorf("CanUpdate(%v) returns %v, expected %v", input, output, expect) } } } type Child struct { Name string `default:"Tom"` Age int `default:"20"` } type Parent struct { Child *Child } func TestPointerStructMember(t *testing.T) { m := Parent{Child: &Child{Name: "Jim"}} Set(&m) if m.Child.Age != 20 { t.Errorf("20 is expected") } } type Main struct { MainInt int `default:"-"` *Other `default:"{}"` } type Other struct { OtherInt int `default:"-"` } func (s *Main) SetDefaults() { if CanUpdate(s.MainInt) { s.MainInt = 1 } } func (s *Other) SetDefaults() { if CanUpdate(s.OtherInt) { s.OtherInt = 1 } } func TestDefaultsSetter(t *testing.T) { main := &Main{} Set(main) if main.OtherInt != 1 { t.Errorf("expected 1 for OtherInt, got %d", main.OtherInt) } if main.MainInt != 1 { t.Errorf("expected 1 for MainInt, got %d", main.MainInt) } } defaults-1.7.0/go.mod000066400000000000000000000000541440360104600144310ustar00rootroot00000000000000module github.com/creasty/defaults go 1.14 defaults-1.7.0/go.sum000066400000000000000000000000001440360104600144450ustar00rootroot00000000000000defaults-1.7.0/internal/000077500000000000000000000000001440360104600151405ustar00rootroot00000000000000defaults-1.7.0/internal/fixture/000077500000000000000000000000001440360104600166265ustar00rootroot00000000000000defaults-1.7.0/internal/fixture/test.go000066400000000000000000000002361440360104600201350ustar00rootroot00000000000000package fixture // Sample is a struct that contains 1 exported field and 1 unexported field type Sample struct { ExportedFeild int unexportedFeild int } defaults-1.7.0/setter.go000066400000000000000000000003111440360104600151540ustar00rootroot00000000000000package defaults // Setter is an interface for setting default values type Setter interface { SetDefaults() } func callSetter(v interface{}) { if ds, ok := v.(Setter); ok { ds.SetDefaults() } }