From a7a3c679d1507536a17b17a8cb7b2e4cc9492944 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 13 Dec 2020 13:10:04 +0300 Subject: [PATCH] config: load defaults Signed-off-by: Vasiliy Tolstov --- config/config.go | 2 + config/default.go | 218 +++++++++++++++++++++++ config/{noop_test.go => default_test.go} | 28 +-- config/noop.go | 36 ---- 4 files changed, 238 insertions(+), 46 deletions(-) create mode 100644 config/default.go rename config/{noop_test.go => default_test.go} (69%) delete mode 100644 config/noop.go diff --git a/config/config.go b/config/config.go index 2337b847..d34514db 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,8 @@ var ( ) var ( + // ErrInvalidStruct is returned when the target struct is invalid + ErrInvalidStruct = errors.New("invalid struct specified") // ErrWatcherStopped is returned when source watcher has been stopped ErrWatcherStopped = errors.New("watcher stopped") ) diff --git a/config/default.go b/config/default.go new file mode 100644 index 00000000..a59be9fc --- /dev/null +++ b/config/default.go @@ -0,0 +1,218 @@ +package config + +import ( + "context" + "reflect" + "strconv" + "strings" +) + +type defaultConfig struct { + opts Options +} + +func (c *defaultConfig) Options() Options { + return c.opts +} + +func (c *defaultConfig) Init(opts ...Option) error { + for _, o := range opts { + o(&c.opts) + } + return nil +} + +func (c *defaultConfig) Load(ctx context.Context) error { + valueOf := reflect.ValueOf(c.opts.Struct) + + if err := c.fillValues(ctx, valueOf); err != nil { + return err + } + + return nil +} + +func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error { + switch value.Kind() { + case reflect.Map: + t := value.Type() + nvals := strings.FieldsFunc(val, func(c rune) bool { return c == ',' || c == ';' }) + if value.IsNil() { + value.Set(reflect.MakeMapWithSize(t, len(nvals))) + } + kt := t.Key() + et := t.Elem() + for _, nval := range nvals { + kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' }) + mkey := reflect.Indirect(reflect.New(kt)) + mval := reflect.Indirect(reflect.New(et)) + if err := c.fillValue(ctx, mkey, kv[0]); err != nil { + return err + } + if err := c.fillValue(ctx, mval, kv[1]); err != nil { + return err + } + value.SetMapIndex(mkey, mval) + } + case reflect.Slice, reflect.Array: + nvals := strings.FieldsFunc(val, func(c rune) bool { return c == ',' || c == ';' }) + value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals))) + for idx, nval := range nvals { + nvalue := reflect.Indirect(reflect.New(value.Type().Elem())) + if err := c.fillValue(ctx, nvalue, nval); err != nil { + return err + } + value.Index(idx).Set(nvalue) + } + case reflect.Bool: + v, err := strconv.ParseBool(val) + if err != nil { + return err + } + value.Set(reflect.ValueOf(v)) + case reflect.String: + value.Set(reflect.ValueOf(val)) + case reflect.Float32: + v, err := strconv.ParseFloat(val, 32) + if err != nil { + return err + } + value.Set(reflect.ValueOf(float32(v))) + case reflect.Float64: + v, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } + value.Set(reflect.ValueOf(float64(v))) + case reflect.Int: + v, err := strconv.ParseInt(val, 10, 0) + if err != nil { + return err + } + value.Set(reflect.ValueOf(int(v))) + case reflect.Int8: + v, err := strconv.ParseInt(val, 10, 8) + if err != nil { + return err + } + value.Set(reflect.ValueOf(v)) + case reflect.Int16: + v, err := strconv.ParseInt(val, 10, 16) + if err != nil { + return err + } + value.Set(reflect.ValueOf(int16(v))) + case reflect.Int32: + v, err := strconv.ParseInt(val, 10, 32) + if err != nil { + return err + } + value.Set(reflect.ValueOf(int32(v))) + case reflect.Int64: + v, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return err + } + value.Set(reflect.ValueOf(int64(v))) + case reflect.Uint: + v, err := strconv.ParseUint(val, 10, 0) + if err != nil { + return err + } + value.Set(reflect.ValueOf(uint(v))) + case reflect.Uint8: + v, err := strconv.ParseUint(val, 10, 8) + if err != nil { + return err + } + value.Set(reflect.ValueOf(uint8(v))) + case reflect.Uint16: + v, err := strconv.ParseUint(val, 10, 16) + if err != nil { + return err + } + value.Set(reflect.ValueOf(uint16(v))) + case reflect.Uint32: + v, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return err + } + value.Set(reflect.ValueOf(uint32(v))) + case reflect.Uint64: + v, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return err + } + value.Set(reflect.ValueOf(uint64(v))) + } + return nil +} + +func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) error { + var values reflect.Value + + if valueOf.Kind() == reflect.Ptr { + values = valueOf.Elem() + } else { + values = valueOf + } + + if values.Kind() == reflect.Invalid { + return ErrInvalidStruct + } + + fields := values.Type() + + for idx := 0; idx < fields.NumField(); idx++ { + field := fields.Field(idx) + value := values.Field(idx) + if !value.CanSet() { + continue + } + if len(field.PkgPath) != 0 { + continue + } + switch value.Kind() { + case reflect.Ptr: + if value.IsNil() { + if value.Type().Elem().Kind() != reflect.Struct { + // nil pointer to a non-struct: leave it alone + break + } + // nil pointer to struct: create a zero instance + value.Set(reflect.New(value.Type().Elem())) + } + value = value.Elem() + if err := c.fillValues(ctx, value); err != nil { + return err + } + continue + } + tag, ok := field.Tag.Lookup(c.opts.StructTag) + if !ok { + return nil + } + + if err := c.fillValue(ctx, value, tag); err != nil { + return err + } + } + + return nil +} + +func (c *defaultConfig) Save(ctx context.Context) error { + return nil +} + +func (c *defaultConfig) String() string { + return "default" +} + +func NewConfig(opts ...Option) Config { + options := NewOptions(opts...) + if len(options.StructTag) == 0 { + options.StructTag = "default" + } + return &defaultConfig{opts: options} +} diff --git a/config/noop_test.go b/config/default_test.go similarity index 69% rename from config/noop_test.go rename to config/default_test.go index 80b1f8cc..8bdfc5bb 100644 --- a/config/noop_test.go +++ b/config/default_test.go @@ -1,15 +1,19 @@ package config_test -import "testing" -import "context" -import "fmt" -import "github.com/unistack-org/micro/v3/config" +import ( + "context" + "fmt" + "testing" + + "github.com/unistack-org/micro/v3/config" +) type Cfg struct { - Value string + StringValue string `default:"string_value"` + IntValue int `default:"99"` } -func TestNoop(t *testing.T) { +func TestDefault(t *testing.T) { ctx := context.Background() conf := &Cfg{} blfn := func(ctx context.Context, cfg config.Config) error { @@ -17,7 +21,7 @@ func TestNoop(t *testing.T) { if !ok { return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) } - conf.Value = "before_load" + conf.StringValue = "before_load" return nil } alfn := func(ctx context.Context, cfg config.Config) error { @@ -25,7 +29,7 @@ func TestNoop(t *testing.T) { if !ok { return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) } - conf.Value = "after_load" + conf.StringValue = "after_load" return nil } @@ -38,7 +42,7 @@ func TestNoop(t *testing.T) { t.Fatal(err) } } - if conf.Value != "before_load" { + if conf.StringValue != "before_load" { t.Fatal("BeforeLoad option not working") } @@ -46,12 +50,16 @@ func TestNoop(t *testing.T) { t.Fatal(err) } + if conf.StringValue != "string_value" || conf.IntValue != 99 { + t.Fatalf("load failed: %#+v", conf) + } + for _, fn := range cfg.Options().AfterLoad { if err := fn(ctx, cfg); err != nil { t.Fatal(err) } } - if conf.Value != "after_load" { + if conf.StringValue != "after_load" { t.Fatal("AfterLoad option not working") } diff --git a/config/noop.go b/config/noop.go deleted file mode 100644 index 508421b3..00000000 --- a/config/noop.go +++ /dev/null @@ -1,36 +0,0 @@ -// Package config is an interface for dynamic configuration. -package config - -import "context" - -type noopConfig struct { - opts Options -} - -func (c *noopConfig) Init(opts ...Option) error { - for _, o := range opts { - o(&c.opts) - } - return nil -} - -func (c *noopConfig) Load(ctx context.Context) error { - return nil -} - -func (c *noopConfig) Save(ctx context.Context) error { - return nil -} - -func (c *noopConfig) Options() Options { - return c.opts -} - -func (c *noopConfig) String() string { - return "noop" -} - -// NewConfig returns new noop config -func NewConfig(opts ...Option) Config { - return &noopConfig{opts: NewOptions(opts...)} -}