add conf paladin (#5)
parent
7fc7de272c
commit
038b0ebb27
@ -0,0 +1,72 @@ |
||||
#### paladin |
||||
|
||||
##### 项目简介 |
||||
|
||||
paladin 是一个config SDK客户端,包括了file、mock几个抽象功能,方便使用本地文件或者sven配置中心,并且集成了对象自动reload功能。 |
||||
|
||||
|
||||
local files: |
||||
``` |
||||
demo -conf=/data/conf/app/msm-servie.toml |
||||
// or dir |
||||
demo -conf=/data/conf/app/ |
||||
|
||||
``` |
||||
example: |
||||
``` |
||||
type exampleConf struct { |
||||
Bool bool |
||||
Int int64 |
||||
Float float64 |
||||
String string |
||||
} |
||||
|
||||
func (e *exampleConf) Set(text string) error { |
||||
var ec exampleConf |
||||
if err := toml.Unmarshal([]byte(text), &ec); err != nil { |
||||
return err |
||||
} |
||||
*e = ec |
||||
return nil |
||||
} |
||||
|
||||
func ExampleClient() { |
||||
if err := paladin.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
var ( |
||||
ec exampleConf |
||||
eo exampleConf |
||||
m paladin.TOML |
||||
strs []string |
||||
) |
||||
// config unmarshal |
||||
if err := paladin.Get("example.toml").UnmarshalTOML(&ec); err != nil { |
||||
panic(err) |
||||
} |
||||
// config setter |
||||
if err := paladin.Watch("example.toml", &ec); err != nil { |
||||
panic(err) |
||||
} |
||||
// paladin map |
||||
if err := paladin.Watch("example.toml", &m); err != nil { |
||||
panic(err) |
||||
} |
||||
s, err := m.Value("key").String() |
||||
b, err := m.Value("key").Bool() |
||||
i, err := m.Value("key").Int64() |
||||
f, err := m.Value("key").Float64() |
||||
// value slice |
||||
err = m.Value("strings").Slice(&strs) |
||||
// watch key |
||||
for event := range paladin.WatchEvent(context.TODO(), "key") { |
||||
fmt.Println(event) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
##### 编译环境 |
||||
|
||||
- **请只用 Golang v1.12.x 以上版本编译执行** |
||||
|
||||
##### 依赖包 |
@ -0,0 +1,49 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
const ( |
||||
// EventAdd config add event.
|
||||
EventAdd EventType = iota |
||||
// EventUpdate config update event.
|
||||
EventUpdate |
||||
// EventRemove config remove event.
|
||||
EventRemove |
||||
) |
||||
|
||||
// EventType is config event.
|
||||
type EventType int |
||||
|
||||
// Event is watch event.
|
||||
type Event struct { |
||||
Event EventType |
||||
Key string |
||||
Value string |
||||
} |
||||
|
||||
// Watcher is config watcher.
|
||||
type Watcher interface { |
||||
WatchEvent(context.Context, ...string) <-chan Event |
||||
Close() error |
||||
} |
||||
|
||||
// Setter is value setter.
|
||||
type Setter interface { |
||||
Set(string) error |
||||
} |
||||
|
||||
// Getter is value getter.
|
||||
type Getter interface { |
||||
// Get a config value by a config key(may be a sven filename).
|
||||
Get(string) *Value |
||||
// GetAll return all config key->value map.
|
||||
GetAll() *Map |
||||
} |
||||
|
||||
// Client is config client.
|
||||
type Client interface { |
||||
Watcher |
||||
Getter |
||||
} |
@ -0,0 +1,86 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"context" |
||||
"flag" |
||||
|
||||
"github.com/bilibili/Kratos/pkg/log" |
||||
) |
||||
|
||||
var ( |
||||
// DefaultClient default client.
|
||||
DefaultClient Client |
||||
confPath string |
||||
vars = make(map[string][]Setter) // NOTE: no thread safe
|
||||
) |
||||
|
||||
func init() { |
||||
flag.StringVar(&confPath, "conf", "", "default config path") |
||||
} |
||||
|
||||
// Init init config client.
|
||||
func Init() (err error) { |
||||
if confPath != "" { |
||||
DefaultClient, err = NewFile(confPath) |
||||
} else { |
||||
// TODO: config service
|
||||
return |
||||
} |
||||
if err != nil { |
||||
return |
||||
} |
||||
go func() { |
||||
for event := range DefaultClient.WatchEvent(context.Background()) { |
||||
if event.Event != EventUpdate && event.Event != EventAdd { |
||||
continue |
||||
} |
||||
if sets, ok := vars[event.Key]; ok { |
||||
for _, s := range sets { |
||||
if err := s.Set(event.Value); err != nil { |
||||
log.Error("paladin: vars:%v event:%v error(%v)", s, event, err) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
return |
||||
} |
||||
|
||||
// Watch watch on a key. The configuration implements the setter interface, which is invoked when the configuration changes.
|
||||
func Watch(key string, s Setter) error { |
||||
v := DefaultClient.Get(key) |
||||
str, err := v.Raw() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := s.Set(str); err != nil { |
||||
return err |
||||
} |
||||
vars[key] = append(vars[key], s) |
||||
return nil |
||||
} |
||||
|
||||
// WatchEvent watch on multi keys. Events are returned when the configuration changes.
|
||||
func WatchEvent(ctx context.Context, keys ...string) <-chan Event { |
||||
return DefaultClient.WatchEvent(ctx, keys...) |
||||
} |
||||
|
||||
// Get return value by key.
|
||||
func Get(key string) *Value { |
||||
return DefaultClient.Get(key) |
||||
} |
||||
|
||||
// GetAll return all config map.
|
||||
func GetAll() *Map { |
||||
return DefaultClient.GetAll() |
||||
} |
||||
|
||||
// Keys return values key.
|
||||
func Keys() []string { |
||||
return DefaultClient.GetAll().Keys() |
||||
} |
||||
|
||||
// Close close watcher.
|
||||
func Close() error { |
||||
return DefaultClient.Close() |
||||
} |
@ -0,0 +1,112 @@ |
||||
package paladin_test |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/bilibili/Kratos/pkg/conf/paladin" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
) |
||||
|
||||
type exampleConf struct { |
||||
Bool bool |
||||
Int int64 |
||||
Float float64 |
||||
String string |
||||
Strings []string |
||||
} |
||||
|
||||
func (e *exampleConf) Set(text string) error { |
||||
var ec exampleConf |
||||
if err := toml.Unmarshal([]byte(text), &ec); err != nil { |
||||
return err |
||||
} |
||||
*e = ec |
||||
return nil |
||||
} |
||||
|
||||
// ExampleClient is a example client usage.
|
||||
// exmaple.toml:
|
||||
/* |
||||
bool = true |
||||
int = 100 |
||||
float = 100.1 |
||||
string = "text" |
||||
strings = ["a", "b", "c"] |
||||
*/ |
||||
func ExampleClient() { |
||||
if err := paladin.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
var ec exampleConf |
||||
// var setter
|
||||
if err := paladin.Watch("example.toml", &ec); err != nil { |
||||
panic(err) |
||||
} |
||||
if err := paladin.Get("example.toml").UnmarshalTOML(&ec); err != nil { |
||||
panic(err) |
||||
} |
||||
// use exampleConf
|
||||
// watch event key
|
||||
go func() { |
||||
for event := range paladin.WatchEvent(context.TODO(), "key") { |
||||
fmt.Println(event) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
// ExampleMap is a example map usage.
|
||||
// exmaple.toml:
|
||||
/* |
||||
bool = true |
||||
int = 100 |
||||
float = 100.1 |
||||
string = "text" |
||||
strings = ["a", "b", "c"] |
||||
|
||||
[object] |
||||
string = "text" |
||||
bool = true |
||||
int = 100 |
||||
float = 100.1 |
||||
strings = ["a", "b", "c"] |
||||
*/ |
||||
func ExampleMap() { |
||||
var ( |
||||
m paladin.TOML |
||||
strs []string |
||||
) |
||||
// paladin toml
|
||||
if err := paladin.Watch("example.toml", &m); err != nil { |
||||
panic(err) |
||||
} |
||||
// value string
|
||||
s, err := m.Get("string").String() |
||||
if err != nil { |
||||
s = "default" |
||||
} |
||||
fmt.Println(s) |
||||
// value bool
|
||||
b, err := m.Get("bool").Bool() |
||||
if err != nil { |
||||
b = false |
||||
} |
||||
fmt.Println(b) |
||||
// value int
|
||||
i, err := m.Get("int").Int64() |
||||
if err != nil { |
||||
i = 100 |
||||
} |
||||
fmt.Println(i) |
||||
// value float
|
||||
f, err := m.Get("float").Float64() |
||||
if err != nil { |
||||
f = 100.1 |
||||
} |
||||
fmt.Println(f) |
||||
// value slice
|
||||
if err = m.Get("strings").Slice(&strs); err == nil { |
||||
fmt.Println(strs) |
||||
} |
||||
} |
@ -0,0 +1,194 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/fsnotify/fsnotify" |
||||
) |
||||
|
||||
const ( |
||||
defaultChSize = 10 |
||||
) |
||||
|
||||
var _ Client = &file{} |
||||
|
||||
// file is file config client.
|
||||
type file struct { |
||||
values *Map |
||||
rawVal map[string]*Value |
||||
|
||||
watchChs map[string][]chan Event |
||||
mx sync.Mutex |
||||
wg sync.WaitGroup |
||||
|
||||
base string |
||||
done chan struct{} |
||||
} |
||||
|
||||
func readAllPaths(base string) ([]string, error) { |
||||
fi, err := os.Stat(base) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("check local config file fail! error: %s", err) |
||||
} |
||||
// dirs or file to paths
|
||||
var paths []string |
||||
if fi.IsDir() { |
||||
files, err := ioutil.ReadDir(base) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("read dir %s error: %s", base, err) |
||||
} |
||||
for _, file := range files { |
||||
if !file.IsDir() { |
||||
paths = append(paths, path.Join(base, file.Name())) |
||||
} |
||||
} |
||||
} else { |
||||
paths = append(paths, base) |
||||
} |
||||
return paths, nil |
||||
} |
||||
|
||||
func loadValuesFromPaths(paths []string) (map[string]*Value, error) { |
||||
// laod config file to values
|
||||
var err error |
||||
values := make(map[string]*Value, len(paths)) |
||||
for _, fpath := range paths { |
||||
if values[path.Base(fpath)], err = loadValue(fpath); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return values, nil |
||||
} |
||||
|
||||
func loadValue(fpath string) (*Value, error) { |
||||
data, err := ioutil.ReadFile(fpath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
content := string(data) |
||||
return &Value{val: content, raw: content}, nil |
||||
} |
||||
|
||||
// NewFile new a config file client.
|
||||
// conf = /data/conf/app/
|
||||
// conf = /data/conf/app/xxx.toml
|
||||
func NewFile(base string) (Client, error) { |
||||
// paltform slash
|
||||
base = filepath.FromSlash(base) |
||||
|
||||
paths, err := readAllPaths(base) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(paths) == 0 { |
||||
return nil, fmt.Errorf("empty config path") |
||||
} |
||||
|
||||
rawVal, err := loadValuesFromPaths(paths) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
valMap := &Map{} |
||||
valMap.Store(rawVal) |
||||
fc := &file{ |
||||
values: valMap, |
||||
rawVal: rawVal, |
||||
watchChs: make(map[string][]chan Event), |
||||
|
||||
base: base, |
||||
done: make(chan struct{}, 1), |
||||
} |
||||
|
||||
fc.wg.Add(1) |
||||
go fc.daemon() |
||||
|
||||
return fc, nil |
||||
} |
||||
|
||||
// Get return value by key.
|
||||
func (f *file) Get(key string) *Value { |
||||
return f.values.Get(key) |
||||
} |
||||
|
||||
// GetAll return value map.
|
||||
func (f *file) GetAll() *Map { |
||||
return f.values |
||||
} |
||||
|
||||
// WatchEvent watch multi key.
|
||||
func (f *file) WatchEvent(ctx context.Context, keys ...string) <-chan Event { |
||||
f.mx.Lock() |
||||
defer f.mx.Unlock() |
||||
ch := make(chan Event, defaultChSize) |
||||
for _, key := range keys { |
||||
f.watchChs[key] = append(f.watchChs[key], ch) |
||||
} |
||||
return ch |
||||
} |
||||
|
||||
// Close close watcher.
|
||||
func (f *file) Close() error { |
||||
f.done <- struct{}{} |
||||
f.wg.Wait() |
||||
return nil |
||||
} |
||||
|
||||
// file config daemon to watch file modification
|
||||
func (f *file) daemon() { |
||||
defer f.wg.Done() |
||||
fswatcher, err := fsnotify.NewWatcher() |
||||
if err != nil { |
||||
log.Printf("create file watcher fail! reload function will lose efficacy error: %s", err) |
||||
return |
||||
} |
||||
if err = fswatcher.Add(f.base); err != nil { |
||||
log.Printf("create fsnotify for base path %s fail %s, reload function will lose efficacy", f.base, err) |
||||
return |
||||
} |
||||
log.Printf("start watch filepath: %s", f.base) |
||||
for event := range fswatcher.Events { |
||||
switch event.Op { |
||||
// use vim edit config will trigger rename
|
||||
case fsnotify.Write, fsnotify.Create: |
||||
f.reloadFile(event.Name) |
||||
case fsnotify.Chmod: |
||||
default: |
||||
log.Printf("unsupport event %s ingored", event) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (f *file) reloadFile(name string) { |
||||
// NOTE: in some case immediately read file content after receive event
|
||||
// will get old content, sleep 100ms make sure get correct content.
|
||||
time.Sleep(100 * time.Millisecond) |
||||
key := filepath.Base(name) |
||||
val, err := loadValue(name) |
||||
if err != nil { |
||||
log.Printf("load file %s error: %s, skipped", name, err) |
||||
return |
||||
} |
||||
f.rawVal[key] = val |
||||
f.values.Store(f.rawVal) |
||||
|
||||
f.mx.Lock() |
||||
chs := f.watchChs[key] |
||||
f.mx.Unlock() |
||||
|
||||
for _, ch := range chs { |
||||
select { |
||||
case ch <- Event{Event: EventUpdate, Value: val.raw}: |
||||
default: |
||||
log.Printf("event channel full discard file %s update event", name) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,108 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"context" |
||||
"io/ioutil" |
||||
"os" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestNewFile(t *testing.T) { |
||||
// test data
|
||||
path := "/tmp/test_conf/" |
||||
assert.Nil(t, os.MkdirAll(path, 0700)) |
||||
assert.Nil(t, ioutil.WriteFile(path+"test.toml", []byte(` |
||||
text = "hello"
|
||||
number = 100 |
||||
slice = [1, 2, 3] |
||||
sliceStr = ["1", "2", "3"] |
||||
`), 0644)) |
||||
// test client
|
||||
cli, err := NewFile(path + "test.toml") |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, cli) |
||||
// test map
|
||||
m := Map{} |
||||
text, err := cli.Get("test.toml").String() |
||||
assert.Nil(t, err) |
||||
assert.Nil(t, m.Set(text), "text") |
||||
s, err := m.Get("text").String() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, s, "hello", "text") |
||||
n, err := m.Get("number").Int64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, n, int64(100), "number") |
||||
} |
||||
|
||||
func TestNewFilePath(t *testing.T) { |
||||
// test data
|
||||
path := "/tmp/test_conf/" |
||||
assert.Nil(t, os.MkdirAll(path, 0700)) |
||||
assert.Nil(t, ioutil.WriteFile(path+"test.toml", []byte(` |
||||
text = "hello"
|
||||
number = 100 |
||||
`), 0644)) |
||||
assert.Nil(t, ioutil.WriteFile(path+"abc.toml", []byte(` |
||||
text = "hello"
|
||||
number = 100 |
||||
`), 0644)) |
||||
// test client
|
||||
cli, err := NewFile(path) |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, cli) |
||||
// test map
|
||||
m := Map{} |
||||
text, err := cli.Get("test.toml").String() |
||||
assert.Nil(t, err) |
||||
assert.Nil(t, m.Set(text), "text") |
||||
s, err := m.Get("text").String() |
||||
assert.Nil(t, err, s) |
||||
assert.Equal(t, s, "hello", "text") |
||||
n, err := m.Get("number").Int64() |
||||
assert.Nil(t, err, s) |
||||
assert.Equal(t, n, int64(100), "number") |
||||
} |
||||
|
||||
func TestFileEvent(t *testing.T) { |
||||
// test data
|
||||
path := "/tmp/test_conf_event/" |
||||
assert.Nil(t, os.MkdirAll(path, 0700)) |
||||
assert.Nil(t, ioutil.WriteFile(path+"test.toml", []byte(` |
||||
text = "hello"
|
||||
number = 100 |
||||
`), 0644)) |
||||
assert.Nil(t, ioutil.WriteFile(path+"abc.toml", []byte(` |
||||
text = "hello"
|
||||
number = 100 |
||||
`), 0644)) |
||||
// test client
|
||||
cli, err := NewFile(path) |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, cli) |
||||
ch := cli.WatchEvent(context.Background(), "test.toml", "abc.toml") |
||||
time.Sleep(time.Millisecond) |
||||
ioutil.WriteFile(path+"test.toml", []byte(`hello`), 0644) |
||||
timeout := time.NewTimer(time.Second) |
||||
select { |
||||
case <-timeout.C: |
||||
t.Fatalf("run test timeout") |
||||
case ev := <-ch: |
||||
assert.Equal(t, EventUpdate, ev.Event) |
||||
assert.Equal(t, "hello", ev.Value) |
||||
} |
||||
ioutil.WriteFile(path+"abc.toml", []byte(`test`), 0644) |
||||
select { |
||||
case <-timeout.C: |
||||
t.Fatalf("run test timeout") |
||||
case ev := <-ch: |
||||
assert.Equal(t, EventUpdate, ev.Event) |
||||
assert.Equal(t, "test", ev.Value) |
||||
} |
||||
content1, _ := cli.Get("test.toml").String() |
||||
assert.Equal(t, "hello", content1) |
||||
content2, _ := cli.Get("abc.toml").String() |
||||
assert.Equal(t, "test", content2) |
||||
} |
@ -0,0 +1,76 @@ |
||||
package paladin |
||||
|
||||
import "time" |
||||
|
||||
// Bool return bool value.
|
||||
func Bool(v *Value, def bool) bool { |
||||
b, err := v.Bool() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return b |
||||
} |
||||
|
||||
// Int return int value.
|
||||
func Int(v *Value, def int) int { |
||||
i, err := v.Int() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return i |
||||
} |
||||
|
||||
// Int32 return int32 value.
|
||||
func Int32(v *Value, def int32) int32 { |
||||
i, err := v.Int32() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return i |
||||
} |
||||
|
||||
// Int64 return int64 value.
|
||||
func Int64(v *Value, def int64) int64 { |
||||
i, err := v.Int64() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return i |
||||
} |
||||
|
||||
// Float32 return float32 value.
|
||||
func Float32(v *Value, def float32) float32 { |
||||
f, err := v.Float32() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return f |
||||
} |
||||
|
||||
// Float64 return float32 value.
|
||||
func Float64(v *Value, def float64) float64 { |
||||
f, err := v.Float64() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return f |
||||
} |
||||
|
||||
// String return string value.
|
||||
func String(v *Value, def string) string { |
||||
s, err := v.String() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// Duration parses a duration string. A duration string is a possibly signed sequence of decimal numbers
|
||||
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
func Duration(v *Value, def time.Duration) time.Duration { |
||||
dur, err := v.Duration() |
||||
if err != nil { |
||||
return def |
||||
} |
||||
return dur |
||||
} |
@ -0,0 +1,286 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestBool(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def bool |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want bool |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: true}}, |
||||
want: true, |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: false, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: true}, |
||||
want: true, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Bool(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Bool() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestInt(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def int |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want int |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: int64(2233)}}, |
||||
want: 2233, |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: 2233}, |
||||
want: 2233, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Int(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Int() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestInt32(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def int32 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want int32 |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: int64(2233)}}, |
||||
want: 2233, |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: 2233}, |
||||
want: 2233, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Int32(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Int32() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestInt64(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def int64 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want int64 |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: int64(2233)}}, |
||||
want: 2233, |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: 2233}, |
||||
want: 2233, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Int64(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Int64() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestFloat32(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def float32 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want float32 |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: float64(2233)}}, |
||||
want: float32(2233), |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: float32(2233)}, |
||||
want: float32(2233), |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Float32(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Float32() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestFloat64(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def float64 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want float64 |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: float64(2233)}}, |
||||
want: float64(2233), |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: float64(2233)}, |
||||
want: float64(2233), |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Float64(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Float64() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestString(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want string |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: "test"}}, |
||||
want: "test", |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: "", |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: "test"}, |
||||
want: "test", |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := String(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("String() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestDuration(t *testing.T) { |
||||
type args struct { |
||||
v *Value |
||||
def time.Duration |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want time.Duration |
||||
}{ |
||||
{ |
||||
name: "ok", |
||||
args: args{v: &Value{val: "1s"}}, |
||||
want: time.Second, |
||||
}, |
||||
{ |
||||
name: "fail", |
||||
args: args{v: &Value{}}, |
||||
want: 0, |
||||
}, |
||||
{ |
||||
name: "default", |
||||
args: args{v: &Value{}, def: time.Second}, |
||||
want: time.Second, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := Duration(tt.args.v, tt.args.def); got != tt.want { |
||||
t.Errorf("Duration() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"strings" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// keyNamed key naming to lower case.
|
||||
func keyNamed(key string) string { |
||||
return strings.ToLower(key) |
||||
} |
||||
|
||||
// Map is config map, key(filename) -> value(file).
|
||||
type Map struct { |
||||
values atomic.Value |
||||
} |
||||
|
||||
// Store sets the value of the Value to values map.
|
||||
func (m *Map) Store(values map[string]*Value) { |
||||
dst := make(map[string]*Value, len(values)) |
||||
for k, v := range values { |
||||
dst[keyNamed(k)] = v |
||||
} |
||||
m.values.Store(dst) |
||||
} |
||||
|
||||
// Load returns the value set by the most recent Store.
|
||||
func (m *Map) Load() map[string]*Value { |
||||
return m.values.Load().(map[string]*Value) |
||||
} |
||||
|
||||
// Exist check if values map exist a key.
|
||||
func (m *Map) Exist(key string) bool { |
||||
_, ok := m.Load()[keyNamed(key)] |
||||
return ok |
||||
} |
||||
|
||||
// Get return get value by key.
|
||||
func (m *Map) Get(key string) *Value { |
||||
v, ok := m.Load()[keyNamed(key)] |
||||
if ok { |
||||
return v |
||||
} |
||||
return &Value{} |
||||
} |
||||
|
||||
// Keys return map keys.
|
||||
func (m *Map) Keys() []string { |
||||
values := m.Load() |
||||
keys := make([]string, 0, len(values)) |
||||
for key := range values { |
||||
keys = append(keys, key) |
||||
} |
||||
return keys |
||||
} |
@ -0,0 +1,94 @@ |
||||
package paladin_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/bilibili/Kratos/pkg/conf/paladin" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
type fruit struct { |
||||
Fruit []struct { |
||||
Name string |
||||
} |
||||
} |
||||
|
||||
func (f *fruit) Set(text string) error { |
||||
return toml.Unmarshal([]byte(text), f) |
||||
} |
||||
|
||||
func TestMap(t *testing.T) { |
||||
s := ` |
||||
# kv |
||||
text = "hello"
|
||||
number = 100 |
||||
point = 100.1 |
||||
boolean = true |
||||
KeyCase = "test" |
||||
|
||||
# slice |
||||
numbers = [1, 2, 3] |
||||
strings = ["a", "b", "c"] |
||||
empty = [] |
||||
[[fruit]] |
||||
name = "apple" |
||||
[[fruit]] |
||||
name = "banana" |
||||
|
||||
# table
|
||||
[database] |
||||
server = "192.168.1.1" |
||||
connection_max = 5000 |
||||
enabled = true |
||||
|
||||
[pool] |
||||
[pool.breaker] |
||||
xxx = "xxx" |
||||
` |
||||
m := paladin.Map{} |
||||
assert.Nil(t, m.Set(s), s) |
||||
str, err := m.Get("text").String() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, str, "hello", "text") |
||||
n, err := m.Get("number").Int64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, n, int64(100), "number") |
||||
p, err := m.Get("point").Float64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, p, 100.1, "point") |
||||
b, err := m.Get("boolean").Bool() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, b, true, "boolean") |
||||
// key lower case
|
||||
lb, err := m.Get("Boolean").Bool() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, lb, true, "boolean") |
||||
lt, err := m.Get("KeyCase").String() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, lt, "test", "key case") |
||||
var sliceInt []int64 |
||||
err = m.Get("numbers").Slice(&sliceInt) |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, sliceInt, []int64{1, 2, 3}) |
||||
var sliceStr []string |
||||
err = m.Get("strings").Slice(&sliceStr) |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, []string{"a", "b", "c"}, sliceStr) |
||||
err = m.Get("strings").Slice(&sliceStr) |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, []string{"a", "b", "c"}, sliceStr) |
||||
// errors
|
||||
err = m.Get("strings").Slice(sliceInt) |
||||
assert.NotNil(t, err) |
||||
err = m.Get("strings").Slice(&sliceInt) |
||||
assert.NotNil(t, err) |
||||
var obj struct { |
||||
Name string |
||||
} |
||||
err = m.Get("strings").Slice(obj) |
||||
assert.NotNil(t, err) |
||||
err = m.Get("strings").Slice(&obj) |
||||
assert.NotNil(t, err) |
||||
} |
@ -0,0 +1,40 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
var _ Client = &Mock{} |
||||
|
||||
// Mock is Mock config client.
|
||||
type Mock struct { |
||||
C chan Event |
||||
*Map |
||||
} |
||||
|
||||
// NewMock new a config mock client.
|
||||
func NewMock(vs map[string]string) *Mock { |
||||
values := make(map[string]*Value, len(vs)) |
||||
for k, v := range vs { |
||||
values[k] = &Value{val: v, raw: v} |
||||
} |
||||
m := new(Map) |
||||
m.Store(values) |
||||
return &Mock{Map: m, C: make(chan Event)} |
||||
} |
||||
|
||||
// GetAll return value map.
|
||||
func (m *Mock) GetAll() *Map { |
||||
return m.Map |
||||
} |
||||
|
||||
// WatchEvent watch multi key.
|
||||
func (m *Mock) WatchEvent(ctx context.Context, key ...string) <-chan Event { |
||||
return m.C |
||||
} |
||||
|
||||
// Close close watcher.
|
||||
func (m *Mock) Close() error { |
||||
close(m.C) |
||||
return nil |
||||
} |
@ -0,0 +1,37 @@ |
||||
package paladin_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/bilibili/Kratos/pkg/conf/paladin" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestMock(t *testing.T) { |
||||
cs := map[string]string{ |
||||
"key_toml": ` |
||||
key_bool = true |
||||
key_int = 100 |
||||
key_float = 100.1 |
||||
key_string = "text"
|
||||
`, |
||||
} |
||||
cli := paladin.NewMock(cs) |
||||
// test vlaue
|
||||
var m paladin.TOML |
||||
err := cli.Get("key_toml").Unmarshal(&m) |
||||
assert.Nil(t, err) |
||||
b, err := m.Get("key_bool").Bool() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, b, true) |
||||
i, err := m.Get("key_int").Int64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, i, int64(100)) |
||||
f, err := m.Get("key_float").Float64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, f, float64(100.1)) |
||||
s, err := m.Get("key_string").String() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, s, "text") |
||||
} |
@ -0,0 +1,73 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"bytes" |
||||
"reflect" |
||||
"strconv" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// TOML is toml map.
|
||||
type TOML = Map |
||||
|
||||
// Set set the map by value.
|
||||
func (m *TOML) Set(text string) error { |
||||
if err := m.UnmarshalText([]byte(text)); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// UnmarshalText implemented toml.
|
||||
func (m *TOML) UnmarshalText(text []byte) error { |
||||
raws := map[string]interface{}{} |
||||
if err := toml.Unmarshal(text, &raws); err != nil { |
||||
return err |
||||
} |
||||
values := map[string]*Value{} |
||||
for k, v := range raws { |
||||
k = keyNamed(k) |
||||
rv := reflect.ValueOf(v) |
||||
switch rv.Kind() { |
||||
case reflect.Map: |
||||
buf := bytes.NewBuffer(nil) |
||||
err := toml.NewEncoder(buf).Encode(v) |
||||
// b, err := toml.Marshal(v)
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
// NOTE: value is map[string]interface{}
|
||||
values[k] = &Value{val: v, raw: buf.String()} |
||||
case reflect.Slice: |
||||
raw := map[string]interface{}{ |
||||
k: v, |
||||
} |
||||
buf := bytes.NewBuffer(nil) |
||||
err := toml.NewEncoder(buf).Encode(raw) |
||||
// b, err := toml.Marshal(raw)
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
// NOTE: value is []interface{}
|
||||
values[k] = &Value{val: v, raw: buf.String()} |
||||
case reflect.Bool: |
||||
b := v.(bool) |
||||
values[k] = &Value{val: b, raw: strconv.FormatBool(b)} |
||||
case reflect.Int64: |
||||
i := v.(int64) |
||||
values[k] = &Value{val: i, raw: strconv.FormatInt(i, 10)} |
||||
case reflect.Float64: |
||||
f := v.(float64) |
||||
values[k] = &Value{val: f, raw: strconv.FormatFloat(f, 'f', -1, 64)} |
||||
case reflect.String: |
||||
s := v.(string) |
||||
values[k] = &Value{val: s, raw: s} |
||||
default: |
||||
return errors.Errorf("UnmarshalTOML: unknown kind(%v)", rv.Kind()) |
||||
} |
||||
} |
||||
m.Store(values) |
||||
return nil |
||||
} |
@ -0,0 +1,157 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"encoding" |
||||
"reflect" |
||||
"time" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// ErrNotExist value key not exist.
|
||||
var ( |
||||
ErrNotExist = errors.New("paladin: value key not exist") |
||||
ErrTypeAssertion = errors.New("paladin: value type assertion no match") |
||||
ErrDifferentTypes = errors.New("paladin: value different types") |
||||
) |
||||
|
||||
// Value is config value, maybe a json/toml/ini/string file.
|
||||
type Value struct { |
||||
val interface{} |
||||
slice interface{} |
||||
raw string |
||||
} |
||||
|
||||
// Bool return bool value.
|
||||
func (v *Value) Bool() (bool, error) { |
||||
if v.val == nil { |
||||
return false, ErrNotExist |
||||
} |
||||
b, ok := v.val.(bool) |
||||
if !ok { |
||||
return false, ErrTypeAssertion |
||||
} |
||||
return b, nil |
||||
} |
||||
|
||||
// Int return int value.
|
||||
func (v *Value) Int() (int, error) { |
||||
i, err := v.Int64() |
||||
return int(i), err |
||||
} |
||||
|
||||
// Int32 return int32 value.
|
||||
func (v *Value) Int32() (int32, error) { |
||||
i, err := v.Int64() |
||||
return int32(i), err |
||||
} |
||||
|
||||
// Int64 return int64 value.
|
||||
func (v *Value) Int64() (int64, error) { |
||||
if v.val == nil { |
||||
return 0, ErrNotExist |
||||
} |
||||
i, ok := v.val.(int64) |
||||
if !ok { |
||||
return 0, ErrTypeAssertion |
||||
} |
||||
return i, nil |
||||
} |
||||
|
||||
// Float32 return float32 value.
|
||||
func (v *Value) Float32() (float32, error) { |
||||
f, err := v.Float64() |
||||
if err != nil { |
||||
return 0.0, err |
||||
} |
||||
return float32(f), nil |
||||
} |
||||
|
||||
// Float64 return float64 value.
|
||||
func (v *Value) Float64() (float64, error) { |
||||
if v.val == nil { |
||||
return 0.0, ErrNotExist |
||||
} |
||||
f, ok := v.val.(float64) |
||||
if !ok { |
||||
return 0.0, ErrTypeAssertion |
||||
} |
||||
return f, nil |
||||
} |
||||
|
||||
// String return string value.
|
||||
func (v *Value) String() (string, error) { |
||||
if v.val == nil { |
||||
return "", ErrNotExist |
||||
} |
||||
s, ok := v.val.(string) |
||||
if !ok { |
||||
return "", ErrTypeAssertion |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
// Duration parses a duration string. A duration string is a possibly signed sequence of decimal numbers
|
||||
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
func (v *Value) Duration() (time.Duration, error) { |
||||
s, err := v.String() |
||||
if err != nil { |
||||
return time.Duration(0), err |
||||
} |
||||
return time.ParseDuration(s) |
||||
} |
||||
|
||||
// Raw return raw value.
|
||||
func (v *Value) Raw() (string, error) { |
||||
if v.val == nil { |
||||
return "", ErrNotExist |
||||
} |
||||
return v.raw, nil |
||||
} |
||||
|
||||
// Slice scan a slcie interface, if slice has element it will be discard.
|
||||
func (v *Value) Slice(dst interface{}) error { |
||||
// NOTE: val is []interface{}, slice is []type
|
||||
if v.val == nil { |
||||
return ErrNotExist |
||||
} |
||||
rv := reflect.ValueOf(dst) |
||||
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Slice { |
||||
return ErrDifferentTypes |
||||
} |
||||
el := rv.Elem() |
||||
// reset slice len to 0.
|
||||
el.SetLen(0) |
||||
kind := el.Type().Elem().Kind() |
||||
src, ok := v.val.([]interface{}) |
||||
if !ok { |
||||
return ErrDifferentTypes |
||||
} |
||||
for _, s := range src { |
||||
if reflect.TypeOf(s).Kind() != kind { |
||||
return ErrTypeAssertion |
||||
} |
||||
el = reflect.Append(el, reflect.ValueOf(s)) |
||||
} |
||||
rv.Elem().Set(el) |
||||
return nil |
||||
} |
||||
|
||||
// Unmarshal is the interface implemented by an object that can unmarshal a textual representation of itself.
|
||||
func (v *Value) Unmarshal(un encoding.TextUnmarshaler) error { |
||||
text, err := v.Raw() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return un.UnmarshalText([]byte(text)) |
||||
} |
||||
|
||||
// UnmarshalTOML unmarhsal toml to struct.
|
||||
func (v *Value) UnmarshalTOML(dst interface{}) error { |
||||
text, err := v.Raw() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return toml.Unmarshal([]byte(text), dst) |
||||
} |
@ -0,0 +1,206 @@ |
||||
package paladin |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
type testUnmarshler struct { |
||||
Text string |
||||
Int int |
||||
} |
||||
|
||||
func TestValueUnmarshal(t *testing.T) { |
||||
s := ` |
||||
int = 100 |
||||
text = "hello"
|
||||
` |
||||
v := Value{val: s, raw: s} |
||||
obj := new(testUnmarshler) |
||||
assert.Nil(t, v.UnmarshalTOML(obj)) |
||||
// error
|
||||
v = Value{val: nil, raw: ""} |
||||
assert.NotNil(t, v.UnmarshalTOML(obj)) |
||||
} |
||||
|
||||
func TestValue(t *testing.T) { |
||||
var tests = []struct { |
||||
in interface{} |
||||
out interface{} |
||||
}{ |
||||
{ |
||||
"text", |
||||
"text", |
||||
}, |
||||
{ |
||||
time.Duration(time.Second * 10), |
||||
"10s", |
||||
}, |
||||
{ |
||||
int64(100), |
||||
int64(100), |
||||
}, |
||||
{ |
||||
float64(100.1), |
||||
float64(100.1), |
||||
}, |
||||
{ |
||||
true, |
||||
true, |
||||
}, |
||||
{ |
||||
nil, |
||||
nil, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(fmt.Sprint(test.in), func(t *testing.T) { |
||||
v := Value{val: test.in, raw: fmt.Sprint(test.in)} |
||||
switch test.in.(type) { |
||||
case nil: |
||||
s, err := v.String() |
||||
assert.NotNil(t, err) |
||||
assert.Equal(t, s, "", test.in) |
||||
i, err := v.Int64() |
||||
assert.NotNil(t, err) |
||||
assert.Equal(t, i, int64(0), test.in) |
||||
f, err := v.Float64() |
||||
assert.NotNil(t, err) |
||||
assert.Equal(t, f, float64(0.0), test.in) |
||||
b, err := v.Bool() |
||||
assert.NotNil(t, err) |
||||
assert.Equal(t, b, false, test.in) |
||||
case string: |
||||
val, err := v.String() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val, test.out.(string), test.in) |
||||
case int64: |
||||
val, err := v.Int() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val, int(test.out.(int64)), test.in) |
||||
val32, err := v.Int32() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val32, int32(test.out.(int64)), test.in) |
||||
val64, err := v.Int64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val64, test.out.(int64), test.in) |
||||
case float64: |
||||
val32, err := v.Float32() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val32, float32(test.out.(float64)), test.in) |
||||
val64, err := v.Float64() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val64, test.out.(float64), test.in) |
||||
case bool: |
||||
val, err := v.Bool() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val, test.out.(bool), test.in) |
||||
case time.Duration: |
||||
v.val = test.out |
||||
val, err := v.Duration() |
||||
assert.Nil(t, err) |
||||
assert.Equal(t, val, test.in.(time.Duration), test.out) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestValueSlice(t *testing.T) { |
||||
var tests = []struct { |
||||
in interface{} |
||||
out interface{} |
||||
}{ |
||||
{ |
||||
nil, |
||||
nil, |
||||
}, |
||||
{ |
||||
[]interface{}{"a", "b", "c"}, |
||||
[]string{"a", "b", "c"}, |
||||
}, |
||||
{ |
||||
[]interface{}{1, 2, 3}, |
||||
[]int64{1, 2, 3}, |
||||
}, |
||||
{ |
||||
[]interface{}{1.1, 1.2, 1.3}, |
||||
[]float64{1.1, 1.2, 1.3}, |
||||
}, |
||||
{ |
||||
[]interface{}{true, false, true}, |
||||
[]bool{true, false, true}, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(fmt.Sprint(test.in), func(t *testing.T) { |
||||
v := Value{val: test.in, raw: fmt.Sprint(test.in)} |
||||
switch test.in.(type) { |
||||
case nil: |
||||
var s []string |
||||
assert.NotNil(t, v.Slice(&s)) |
||||
case []string: |
||||
var s []string |
||||
assert.Nil(t, v.Slice(&s)) |
||||
assert.Equal(t, s, test.out) |
||||
case []int64: |
||||
var s []int64 |
||||
assert.Nil(t, v.Slice(&s)) |
||||
assert.Equal(t, s, test.out) |
||||
case []float64: |
||||
var s []float64 |
||||
assert.Nil(t, v.Slice(&s)) |
||||
assert.Equal(t, s, test.out) |
||||
case []bool: |
||||
var s []bool |
||||
assert.Nil(t, v.Slice(&s)) |
||||
assert.Equal(t, s, test.out) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkValueInt(b *testing.B) { |
||||
v := &Value{val: int64(100), raw: "100"} |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
v.Int64() |
||||
} |
||||
}) |
||||
} |
||||
func BenchmarkValueFloat(b *testing.B) { |
||||
v := &Value{val: float64(100.1), raw: "100.1"} |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
v.Float64() |
||||
} |
||||
}) |
||||
} |
||||
func BenchmarkValueBool(b *testing.B) { |
||||
v := &Value{val: true, raw: "true"} |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
v.Bool() |
||||
} |
||||
}) |
||||
} |
||||
func BenchmarkValueString(b *testing.B) { |
||||
v := &Value{val: "text", raw: "text"} |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
v.String() |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func BenchmarkValueSlice(b *testing.B) { |
||||
v := &Value{val: []interface{}{1, 2, 3}, raw: "100"} |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
var slice []int64 |
||||
for pb.Next() { |
||||
v.Slice(&slice) |
||||
} |
||||
}) |
||||
} |
Loading…
Reference in new issue