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:
KUOKA Yusuke 2019-03-29 12:38:16 +09:00 committed by GitHub
parent 870d33f418
commit 5f8b2e5c7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 11 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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 }