Skip to content

Commit

Permalink
support apollo_namespace (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
scorix authored Jul 13, 2023
1 parent e6951ed commit f784f6c
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 23 deletions.
102 changes: 82 additions & 20 deletions oap.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
"gopkg.in/yaml.v3"
)

const (
apolloTag = "apollo"
apolloNamespaceTag = "apollo_namespace"
)

type UnmarshalFunc func([]byte, interface{}) error

var registryForUnmarshal = map[string]UnmarshalFunc{
Expand All @@ -25,32 +30,89 @@ func SetUnmarshalFunc(name string, f UnmarshalFunc) {

func Decode(ptr interface{}, client agollo.Client, keyOpts map[string][]agollo.OpOption) error {
v := reflect.ValueOf(ptr).Elem()
if v.Kind() != reflect.Struct {
return nil
}

return decodeStruct(ptr, client, nil, keyOpts)
}

func decodeStruct(ptr interface{}, client agollo.Client, opts []agollo.OpOption, keyOpts map[string][]agollo.OpOption) error {
v := reflect.ValueOf(ptr).Elem()
if v.Kind() != reflect.Struct {
return nil
}

for i := 0; i < v.NumField(); i++ {
structField := v.Type().Field(i)
tag := structField.Tag
apolloRawKey := tag.Get("apollo")
apolloKeyParts := strings.Split(apolloRawKey, ",")
apolloKey := apolloKeyParts[0]

apolloVal := client.GetString(apolloKey, keyOpts[apolloKey]...)
val := reflect.New(structField.Type)

// use unmarshaller function
if len(apolloKeyParts) > 1 {
if unmarshallerFunc, ok := registryForUnmarshal[apolloKeyParts[1]]; ok {
if err := unmarshallerFunc([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("%s unmarshal %s error: %w", apolloKeyParts[1], apolloKey, err)
}

v.FieldByName(structField.Name).Set(val.Elem())
}
field := v.FieldByName(structField.Name)

if err := decode(ptr, structField, field, client, opts, keyOpts); err != nil {
return err
}
}

return nil
}

func decode(ptr interface{}, structField reflect.StructField, field reflect.Value, client agollo.Client, opts []agollo.OpOption, keyOpts map[string][]agollo.OpOption) error {
tag := structField.Tag
apolloRawKey := tag.Get(apolloTag)
apolloKeyParts := strings.Split(apolloRawKey, ",")
apolloKey := apolloKeyParts[0]

// OpOptions
kopts := keyOpts[apolloKey]
newOpts := make([]agollo.OpOption, len(opts)+len(kopts))

copy(newOpts, opts)
copy(newOpts[len(opts):], kopts)

if err := yaml.Unmarshal([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("unmarshal %s error: %w", apolloVal, err)
// using namespace
if ns := tag.Get(apolloNamespaceTag); ns != "" {
newOpts = append(newOpts, agollo.WithNamespace(ns))
}

val := reflect.New(structField.Type)

// nested struct fields
if apolloKey == "" {
if err := decodeStruct(val.Interface(), client, newOpts, keyOpts); err != nil {
return fmt.Errorf("Decode %s error: %w", structField.Name, err)
}

if field.CanSet() {
field.Set(val.Elem())
}

v.FieldByName(structField.Name).Set(val.Elem())
return nil
}

// get config content
apolloVal := client.GetString(apolloKey, newOpts...)

// use unmarshaller function
if len(apolloKeyParts) > 1 {
if unmarshallerFunc, ok := registryForUnmarshal[apolloKeyParts[1]]; ok {
if err := unmarshallerFunc([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("%s unmarshal %s error: %w", apolloKeyParts[1], apolloKey, err)
}

if field.CanSet() {
field.Set(val.Elem())
}

return nil
}
}

// parse value via yaml
if err := yaml.Unmarshal([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("unmarshal %s error: %w", apolloVal, err)
}

if field.CanSet() {
field.Set(val.Elem())
}

return nil
Expand Down
71 changes: 68 additions & 3 deletions oap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,80 @@ func TestDecode_StructSlice(t *testing.T) {
Age int `yaml:"age"`
}

t.Run("using yaml", func(t *testing.T) {
config := struct {
Users []user `apollo:"users,yaml"`
}{}

client := NewMockClient(ctrl)
client.EXPECT().GetString(gomock.Eq("users")).Return("- name: Alice\n age: 18")

err := oap.Decode(&config, client, make(map[string][]agollo.OpOption))
require.NoError(t, err)

assert.Equal(t, []user{{Name: "Alice", Age: 18}}, config.Users)
})

t.Run("raw", func(t *testing.T) {
config := struct {
Users []user `apollo:"users"`
}{}

client := NewMockClient(ctrl)
client.EXPECT().GetString(gomock.Eq("users")).Return("- name: Alice\n age: 18")

err := oap.Decode(&config, client, make(map[string][]agollo.OpOption))
require.NoError(t, err)

assert.Equal(t, []user{{Name: "Alice", Age: 18}}, config.Users)
})
}

func TestDecode_NonTag(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

config := struct {
Users []user `apollo:"users"`
Foo string
unexported interface{}
}{}

client := NewMockClient(ctrl)
client.EXPECT().GetString(gomock.Eq("users")).Return("- name: Alice\n age: 18")

err := oap.Decode(&config, client, make(map[string][]agollo.OpOption))
require.NoError(t, err)
}

func TestDecode_WithNamespace(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

config := struct {
Foo string `apollo:"foo" apollo_namespace:"ns"`
}{}

assert.Equal(t, []user{{Name: "Alice", Age: 18}}, config.Users)
client := NewMockClient(ctrl)
client.EXPECT().GetString(gomock.Eq("foo"), gomock.Any()).Return("bar")

err := oap.Decode(&config, client, make(map[string][]agollo.OpOption))
require.NoError(t, err)
}

func TestDecode_NestedWithNamespace(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

type FooConfig struct {
Foo string `apollo:"foo"`
}

config := struct {
FooConfig `apollo_namespace:"ns"`
}{}

client := NewMockClient(ctrl)
client.EXPECT().GetString(gomock.Eq("foo"), gomock.Any()).Return("bar")

err := oap.Decode(&config, client, make(map[string][]agollo.OpOption))
require.NoError(t, err)
}

0 comments on commit f784f6c

Please sign in to comment.