feat: Enhanced `get` template function (#513)
`get` is now able to take one more optional argument, that is used as the default value when the value for the key does not exist. Resolves #465 Fixes #427 Fixes #357 Ref #460
This commit is contained in:
parent
870d33f418
commit
5f8b2e5c7f
|
|
@ -110,11 +110,11 @@ func TestRenderTemplate_Defaulting(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplateToString(s string) (string, error) {
|
func renderTemplateToString(s string, data ...interface{}) (string, error) {
|
||||||
ctx := &Context{readFile: func(filename string) ([]byte, error) {
|
ctx := &Context{readFile: func(filename string) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("unexpected call to readFile: filename=%s", filename)
|
return nil, fmt.Errorf("unexpected call to readFile: filename=%s", filename)
|
||||||
}}
|
}}
|
||||||
tplString, err := ctx.RenderTemplateToBuffer(s)
|
tplString, err := ctx.RenderTemplateToBuffer(s, data...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +125,7 @@ func Test_renderTemplateToString(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
s string
|
s string
|
||||||
envs map[string]string
|
envs map[string]string
|
||||||
|
data interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -177,6 +178,18 @@ func Test_renderTemplateToString(t *testing.T) {
|
||||||
want: "7",
|
want: "7",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "get",
|
||||||
|
args: args{
|
||||||
|
s: `{{ . | get "Foo" }}, {{ . | get "Bar" "2" }}`,
|
||||||
|
envs: map[string]string{},
|
||||||
|
data: map[string]interface{}{
|
||||||
|
"Foo": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "1, 2",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "env var not set",
|
name: "env var not set",
|
||||||
args: args{
|
args: args{
|
||||||
|
|
@ -225,7 +238,7 @@ func Test_renderTemplateToString(t *testing.T) {
|
||||||
t.Error("renderTemplateToString() could not set env var for testing")
|
t.Error("renderTemplateToString() could not set env var for testing")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got, err := renderTemplateToString(tt.args.s)
|
got, err := renderTemplateToString(tt.args.s, tt.args.data)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("renderTemplateToString() for %s error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
t.Errorf("renderTemplateToString() for %s error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -14,36 +14,67 @@ func (e *noValueError) Error() string {
|
||||||
return e.msg
|
return e.msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(path string, obj interface{}) (interface{}, error) {
|
func get(path string, varArgs ...interface{}) (interface{}, error) {
|
||||||
|
var defSet bool
|
||||||
|
var def interface{}
|
||||||
|
var obj interface{}
|
||||||
|
switch len(varArgs) {
|
||||||
|
case 1:
|
||||||
|
defSet = false
|
||||||
|
def = nil
|
||||||
|
obj = varArgs[0]
|
||||||
|
case 2:
|
||||||
|
defSet = true
|
||||||
|
def = varArgs[0]
|
||||||
|
obj = varArgs[1]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected number of args pased to the template function get(path, [def, ]obj): expected 1 or 2, got %d, args was %v", len(varArgs), varArgs)
|
||||||
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
keys := strings.Split(path, ".")
|
keys := strings.Split(path, ".")
|
||||||
|
var v interface{}
|
||||||
|
var ok bool
|
||||||
switch typedObj := obj.(type) {
|
switch typedObj := obj.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
v, ok := typedObj[keys[0]]
|
v, ok = typedObj[keys[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if defSet {
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
return nil, &noValueError{fmt.Sprintf("no value exist for key \"%s\" in %v", keys[0], typedObj)}
|
return nil, &noValueError{fmt.Sprintf("no value exist for key \"%s\" in %v", keys[0], typedObj)}
|
||||||
}
|
}
|
||||||
return get(strings.Join(keys[1:], "."), v)
|
|
||||||
case map[interface{}]interface{}:
|
case map[interface{}]interface{}:
|
||||||
v, ok := typedObj[keys[0]]
|
v, ok = typedObj[keys[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if defSet {
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
return nil, &noValueError{fmt.Sprintf("no value exist for key \"%s\" in %v", keys[0], typedObj)}
|
return nil, &noValueError{fmt.Sprintf("no value exist for key \"%s\" in %v", keys[0], typedObj)}
|
||||||
}
|
}
|
||||||
return get(strings.Join(keys[1:], "."), v)
|
|
||||||
default:
|
default:
|
||||||
maybeStruct := reflect.ValueOf(typedObj)
|
maybeStruct := reflect.ValueOf(typedObj)
|
||||||
if maybeStruct.NumField() < 1 {
|
if maybeStruct.Kind() != reflect.Struct {
|
||||||
return nil, &noValueError{fmt.Sprintf("unexpected type(%v) of value for key \"%s\": it must be either map[string]interface{} or any struct", reflect.TypeOf(obj), keys[0])}
|
return nil, &noValueError{fmt.Sprintf("unexpected type(%v) of value for key \"%s\": it must be either map[string]interface{} or any struct", reflect.TypeOf(obj), keys[0])}
|
||||||
|
} else if maybeStruct.NumField() < 1 {
|
||||||
|
return nil, &noValueError{fmt.Sprintf("no accessible struct fields for key \"%s\"", keys[0])}
|
||||||
}
|
}
|
||||||
f := maybeStruct.FieldByName(keys[0])
|
f := maybeStruct.FieldByName(keys[0])
|
||||||
if !f.IsValid() {
|
if !f.IsValid() {
|
||||||
|
if defSet {
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
return nil, &noValueError{fmt.Sprintf("no field named \"%s\" exist in %v", keys[0], typedObj)}
|
return nil, &noValueError{fmt.Sprintf("no field named \"%s\" exist in %v", keys[0], typedObj)}
|
||||||
}
|
}
|
||||||
v := f.Interface()
|
v = f.Interface()
|
||||||
return get(strings.Join(keys[1:], "."), v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if defSet {
|
||||||
|
return get(strings.Join(keys[1:], "."), def, v)
|
||||||
|
}
|
||||||
|
return get(strings.Join(keys[1:], "."), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrNil(path string, o interface{}) (interface{}, error) {
|
func getOrNil(path string, o interface{}) (interface{}, error) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EmptyStruct struct {
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetStruct(t *testing.T) {
|
func TestGetStruct(t *testing.T) {
|
||||||
type Foo struct{ Bar string }
|
type Foo struct{ Bar string }
|
||||||
|
|
||||||
|
|
@ -23,6 +26,12 @@ func TestGetStruct(t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error but was not occurred")
|
t.Errorf("expected error but was not occurred")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = get("foo", EmptyStruct{})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error but was not occurred")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMap(t *testing.T) {
|
func TestGetMap(t *testing.T) {
|
||||||
|
|
@ -44,6 +53,34 @@ func TestGetMap(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGet_Default(t *testing.T) {
|
||||||
|
obj := map[string]interface{}{"Foo": map[string]interface{}{}, "foo": 1}
|
||||||
|
|
||||||
|
v1, err := get("Foo.Bar", "Bar", obj)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if v1 != "Bar" {
|
||||||
|
t.Errorf("unexpected value for path Foo.Bar in %v: expected=Bar, actual=%v", obj, v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2, err := get("Baz", "Baz", obj)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if v2 != "Baz" {
|
||||||
|
t.Errorf("unexpected value for path Baz in %v: expected=Baz, actual=%v", obj, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = get("foo.Bar", "fooBar", obj)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error but was not occurred")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetOrNilStruct(t *testing.T) {
|
func TestGetOrNilStruct(t *testing.T) {
|
||||||
type Foo struct{ Bar string }
|
type Foo struct{ Bar string }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue