Skip to content

Commit d4c8576

Browse files
authored
[#24] Provide FieldSetter interface to support custom implementations (#25)
1 parent 86fb681 commit d4c8576

File tree

5 files changed

+130
-9
lines changed

5 files changed

+130
-9
lines changed

README.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Supported types:
2020
- `uint`, `uint8`, `uint16`, `uint32`, `uint64` + slices of these types
2121
- `*uint`, `*uint8`, `*uint16`, `*uint32`, `*uint64` + slices of these types
2222
- `float32`, `float64` + slices of these types
23-
- `*float32`, `*float64`
23+
- `*float32`, `*float64` + slices of these types
2424
- `time.Duration` from strings like `12ms`, `2s` etc.
2525
- embedded structs and pointers to structs
2626

@@ -116,6 +116,30 @@ type Provider interface {
116116
}
117117
```
118118

119+
### FieldSetter interface
120+
You can define how to set fields with any custom types:
121+
```go
122+
type FieldSetter interface {
123+
SetField(field reflect.StructField, val reflect.Value, valStr string) error
124+
}
125+
```
126+
Example:
127+
```go
128+
type ipTest net.IP
129+
130+
func (it *ipTest) SetField(_ reflect.StructField, val reflect.Value, valStr string) error {
131+
i := ipTest(net.ParseIP(valStr))
132+
133+
if val.Kind() == reflect.Pointer {
134+
val.Set(reflect.ValueOf(&i))
135+
} else {
136+
val.Set(reflect.ValueOf(i))
137+
}
138+
139+
return nil
140+
}
141+
```
142+
119143
### Default provider
120144
Looks for `default` tag and set value from it:
121145
```go

configurator.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ func (c *Configurator) applyProviders(field reflect.StructField, v reflect.Value
106106
}
107107

108108
for _, provider := range c.providers {
109-
err := provider.Provide(field, v)
110-
if err == nil {
109+
if provider.Provide(field, v) == nil {
111110
return
112111
}
113112
}

configurator_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package configuration
22

33
import (
4+
"net"
45
"os"
56
"testing"
67
"time"
@@ -39,7 +40,8 @@ func TestConfigurator(t *testing.T) {
3940
IntSlice []int64 `default:"3; 4"`
4041
unexported string `xml:"ignored"`
4142
}
42-
URLs []*string `default:"http://localhost:3000;1.2.3.4:8080"`
43+
URLs []*string `default:"http://localhost:3000;1.2.3.4:8080"`
44+
HostIP ipTest `default:"127.0.0.3"`
4345
}{}
4446

4547
configurator := New(
@@ -73,6 +75,8 @@ func TestConfigurator(t *testing.T) {
7375
assert(t, []int64{3, 4}, cfg.Obj.IntSlice)
7476
assert(t, time.Millisecond*100, cfg.ObjPtr.HundredMS)
7577

78+
assert(t, net.ParseIP("127.0.0.3"), net.IP(cfg.HostIP))
79+
7680
for i := range expectedURLs {
7781
assert(t, expectedURLs[i], *cfg.URLs[i])
7882
}

fieldSetter.go

+20-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,27 @@ import (
1010

1111
const sliceSeparator = ";"
1212

13-
// SetField sets field with `valStr` value (converts to the proper type beforehand)
14-
func SetField(field reflect.StructField, v reflect.Value, valStr string) error {
15-
if v.Kind() == reflect.Ptr {
16-
return setPtrValue(field.Type, v, valStr)
13+
type FieldSetter interface {
14+
SetField(field reflect.StructField, val reflect.Value, valStr string) error
15+
}
16+
17+
// SetField sets field with `valStr` value (and converts it into the proper type beforehand)
18+
func SetField(field reflect.StructField, val reflect.Value, valStr string) error {
19+
if val.CanInterface() {
20+
if fs, ok := val.Addr().Interface().(FieldSetter); ok {
21+
return fs.SetField(field, val, valStr)
22+
}
23+
24+
if fs, ok := val.Interface().(FieldSetter); ok {
25+
return fs.SetField(field, val, valStr)
26+
}
27+
}
28+
29+
if val.Kind() == reflect.Pointer {
30+
return setPtrValue(field.Type, val, valStr)
1731
}
18-
return setValue(field.Type, v, valStr)
32+
33+
return setValue(field.Type, val, valStr)
1934
}
2035

2136
func setValue(t reflect.Type, v reflect.Value, val string) (err error) {

fieldStetter_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package configuration
22

33
import (
4+
"net"
45
"reflect"
56
"strconv"
7+
"strings"
68
"testing"
79
"time"
810
)
@@ -425,3 +427,80 @@ func TestSetValue_IntPtrSlice_Err(t *testing.T) {
425427
t.Fatalf("wrong error: %v", err)
426428
}
427429
}
430+
431+
type testCfgSetField struct {
432+
HostOne *ipTest `default:"127.0.0.1"`
433+
HostTwo ipTest `default:"127.0.0.2"`
434+
Hosts ipsTest `default:"10.0.0.1,10.0.0.2"`
435+
NameOne string `default:"one"`
436+
NameTwo *string `default:"two"`
437+
}
438+
439+
type ipTest net.IP
440+
441+
func (it *ipTest) SetField(_ reflect.StructField, val reflect.Value, valStr string) error {
442+
i := ipTest(net.ParseIP(valStr))
443+
444+
if val.Kind() == reflect.Pointer {
445+
val.Set(reflect.ValueOf(&i))
446+
} else {
447+
val.Set(reflect.ValueOf(i))
448+
}
449+
450+
return nil
451+
}
452+
453+
type ipsTest []ipTest
454+
455+
func (it *ipsTest) SetField(sf reflect.StructField, val reflect.Value, valStr string) error {
456+
var (
457+
strIPs = strings.Split(valStr, ",")
458+
ips = make(ipsTest, len(strIPs))
459+
)
460+
461+
for i, ip := range strIPs {
462+
if err := ips[i].SetField(sf, reflect.ValueOf(&ips[i]).Elem(), ip); err != nil {
463+
return err
464+
}
465+
}
466+
467+
if val.Kind() == reflect.Pointer {
468+
val.Set(reflect.ValueOf(&ips))
469+
} else {
470+
val.Set(reflect.ValueOf(ips))
471+
}
472+
473+
return nil
474+
}
475+
476+
type stringTest string
477+
478+
func (st *stringTest) SetField(_ reflect.StructField, val reflect.Value, valStr string) error {
479+
s := stringTest(valStr)
480+
481+
if val.Kind() == reflect.Pointer {
482+
val.Set(reflect.ValueOf(&s))
483+
} else {
484+
val.Set(reflect.ValueOf(s))
485+
}
486+
487+
return nil
488+
}
489+
490+
func Test_CustomFieldSetter(t *testing.T) {
491+
var cfg testCfgSetField
492+
493+
err := FromEnvAndDefault(&cfg)
494+
if err != nil {
495+
t.Fatal(err)
496+
}
497+
498+
assert(t, "127.0.0.1", net.IP(*cfg.HostOne).String())
499+
assert(t, "127.0.0.2", net.IP(cfg.HostTwo).String())
500+
assert(t, "one", cfg.NameOne)
501+
assert(t, "two", *cfg.NameTwo)
502+
assert(t, ipsTest([]ipTest{
503+
ipTest(net.ParseIP("10.0.0.1")),
504+
ipTest(net.ParseIP("10.0.0.2")),
505+
}), cfg.Hosts)
506+
}

0 commit comments

Comments
 (0)