config: load defaults
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
		| @@ -12,6 +12,8 @@ var ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| 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 is returned when source watcher has been stopped | ||||||
| 	ErrWatcherStopped = errors.New("watcher stopped") | 	ErrWatcherStopped = errors.New("watcher stopped") | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										218
									
								
								config/default.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								config/default.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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} | ||||||
|  | } | ||||||
| @@ -1,15 +1,19 @@ | |||||||
| package config_test | package config_test | ||||||
| 
 | 
 | ||||||
| import "testing" | import ( | ||||||
| import "context" | 	"context" | ||||||
| import "fmt" | 	"fmt" | ||||||
| import "github.com/unistack-org/micro/v3/config" | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/unistack-org/micro/v3/config" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| type Cfg struct { | 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() | 	ctx := context.Background() | ||||||
| 	conf := &Cfg{} | 	conf := &Cfg{} | ||||||
| 	blfn := func(ctx context.Context, cfg config.Config) error { | 	blfn := func(ctx context.Context, cfg config.Config) error { | ||||||
| @@ -17,7 +21,7 @@ func TestNoop(t *testing.T) { | |||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) | 			return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) | ||||||
| 		} | 		} | ||||||
| 		conf.Value = "before_load" | 		conf.StringValue = "before_load" | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	alfn := func(ctx context.Context, cfg config.Config) error { | 	alfn := func(ctx context.Context, cfg config.Config) error { | ||||||
| @@ -25,7 +29,7 @@ func TestNoop(t *testing.T) { | |||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) | 			return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) | ||||||
| 		} | 		} | ||||||
| 		conf.Value = "after_load" | 		conf.StringValue = "after_load" | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -38,7 +42,7 @@ func TestNoop(t *testing.T) { | |||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if conf.Value != "before_load" { | 	if conf.StringValue != "before_load" { | ||||||
| 		t.Fatal("BeforeLoad option not working") | 		t.Fatal("BeforeLoad option not working") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -46,12 +50,16 @@ func TestNoop(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if conf.StringValue != "string_value" || conf.IntValue != 99 { | ||||||
|  | 		t.Fatalf("load failed: %#+v", conf) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	for _, fn := range cfg.Options().AfterLoad { | 	for _, fn := range cfg.Options().AfterLoad { | ||||||
| 		if err := fn(ctx, cfg); err != nil { | 		if err := fn(ctx, cfg); err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if conf.Value != "after_load" { | 	if conf.StringValue != "after_load" { | ||||||
| 		t.Fatal("AfterLoad option not working") | 		t.Fatal("AfterLoad option not working") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -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...)} |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user