Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support env tag in config #2577

Merged
merged 7 commits into from Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/hash/consistenthash.go
Expand Up @@ -7,7 +7,6 @@ import (
"sync"

"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/mapping"
)

const (
Expand Down Expand Up @@ -183,5 +182,5 @@ func innerRepr(node interface{}) string {
}

func repr(node interface{}) string {
return mapping.Repr(node)
return lang.Repr(node)
}
67 changes: 67 additions & 0 deletions core/lang/lang.go
@@ -1,5 +1,11 @@
package lang

import (
"fmt"
"reflect"
"strconv"
)

// Placeholder is a placeholder object that can be used globally.
var Placeholder PlaceholderType

Expand All @@ -9,3 +15,64 @@ type (
// PlaceholderType represents a placeholder type.
PlaceholderType = struct{}
)

// Repr returns the string representation of v.
func Repr(v interface{}) string {
if v == nil {
return ""
}

// if func (v *Type) String() string, we can't use Elem()
switch vt := v.(type) {
case fmt.Stringer:
return vt.String()
}

val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}

return reprOfValue(val)
}

func reprOfValue(val reflect.Value) string {
switch vt := val.Interface().(type) {
case bool:
return strconv.FormatBool(vt)
case error:
return vt.Error()
case float32:
return strconv.FormatFloat(float64(vt), 'f', -1, 32)
case float64:
return strconv.FormatFloat(vt, 'f', -1, 64)
case fmt.Stringer:
return vt.String()
case int:
return strconv.Itoa(vt)
case int8:
return strconv.Itoa(int(vt))
case int16:
return strconv.Itoa(int(vt))
case int32:
return strconv.Itoa(int(vt))
case int64:
return strconv.FormatInt(vt, 10)
case string:
return vt
case uint:
return strconv.FormatUint(uint64(vt), 10)
case uint8:
return strconv.FormatUint(uint64(vt), 10)
case uint16:
return strconv.FormatUint(uint64(vt), 10)
case uint32:
return strconv.FormatUint(uint64(vt), 10)
case uint64:
return strconv.FormatUint(vt, 10)
case []byte:
return string(vt)
default:
return fmt.Sprint(val.Interface())
}
}
131 changes: 131 additions & 0 deletions core/lang/lang_test.go
@@ -0,0 +1,131 @@
package lang

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestRepr(t *testing.T) {
var (
f32 float32 = 1.1
f64 = 2.2
i8 int8 = 1
i16 int16 = 2
i32 int32 = 3
i64 int64 = 4
u8 uint8 = 5
u16 uint16 = 6
u32 uint32 = 7
u64 uint64 = 8
)
tests := []struct {
v interface{}
expect string
}{
{
nil,
"",
},
{
mockStringable{},
"mocked",
},
{
new(mockStringable),
"mocked",
},
{
newMockPtr(),
"mockptr",
},
{
&mockOpacity{
val: 1,
},
"{1}",
},
{
true,
"true",
},
{
false,
"false",
},
{
f32,
"1.1",
},
{
f64,
"2.2",
},
{
i8,
"1",
},
{
i16,
"2",
},
{
i32,
"3",
},
{
i64,
"4",
},
{
u8,
"5",
},
{
u16,
"6",
},
{
u32,
"7",
},
{
u64,
"8",
},
{
[]byte(`abcd`),
"abcd",
},
{
mockOpacity{val: 1},
"{1}",
},
}

for _, test := range tests {
t.Run(test.expect, func(t *testing.T) {
assert.Equal(t, test.expect, Repr(test.v))
})
}
}

type mockStringable struct{}

func (m mockStringable) String() string {
return "mocked"
}

type mockPtr struct{}

func newMockPtr() *mockPtr {
return new(mockPtr)
}

func (m *mockPtr) String() string {
return "mockptr"
}

type mockOpacity struct {
val int
}
2 changes: 2 additions & 0 deletions core/mapping/fieldoptions.go
Expand Up @@ -13,6 +13,7 @@ type (
Optional bool
Options []string
Default string
EnvVar string
Range *numberRange
}

Expand Down Expand Up @@ -106,5 +107,6 @@ func (o *fieldOptions) toOptionsWithContext(key string, m Valuer, fullName strin
Optional: optional,
Options: o.Options,
Default: o.Default,
EnvVar: o.EnvVar,
}, nil
}
29 changes: 27 additions & 2 deletions core/mapping/unmarshaler.go
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/zeromicro/go-zero/core/jsonx"
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/proc"
"github.com/zeromicro/go-zero/core/stringx"
)

Expand Down Expand Up @@ -92,8 +93,7 @@ func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v interface{}, f
rve := rv.Elem()
numFields := rte.NumField()
for i := 0; i < numFields; i++ {
field := rte.Field(i)
if err := u.processField(field, rve.Field(i), m, fullName); err != nil {
if err := u.processField(rte.Field(i), rve.Field(i), m, fullName); err != nil {
return err
}
}
Expand Down Expand Up @@ -338,6 +338,24 @@ func (u *Unmarshaler) processFieldTextUnmarshaler(field reflect.StructField, val
return false, nil
}

func (u *Unmarshaler) processFieldWithEnvValue(field reflect.StructField, value reflect.Value,
envVal string, opts *fieldOptionsWithContext, fullName string) error {
fieldKind := field.Type.Kind()
switch fieldKind {
case durationType.Kind():
if err := fillDurationValue(fieldKind, value, envVal); err != nil {
return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err)
}

return nil
case reflect.String:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the boolean type should be supported for env?

value.SetString(envVal)
return nil
default:
kevwan marked this conversation as resolved.
Show resolved Hide resolved
return u.processFieldPrimitiveWithJSONNumber(field, value, json.Number(envVal), opts, fullName)
}
}

func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
m valuerWithParent, fullName string) error {
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
Expand All @@ -346,6 +364,13 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
}

fullName = join(fullName, key)
if opts != nil && len(opts.EnvVar) > 0 {
envVal := proc.Env(opts.EnvVar)
if len(envVal) > 0 {
return u.processFieldWithEnvValue(field, value, envVal, opts, fullName)
}
}

canonicalKey := key
if u.opts.canonicalKey != nil {
canonicalKey = u.opts.canonicalKey(key)
Expand Down