Skip to content

Commit

Permalink
use mapstructure lib. removed structToMap custom func
Browse files Browse the repository at this point in the history
  • Loading branch information
adityathebe committed Jul 21, 2023
1 parent 21bd4e1 commit 559c20c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 107 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ require (
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
Expand Down
71 changes: 16 additions & 55 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package gomplate
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"reflect"
Expand All @@ -15,6 +14,7 @@ import (
pkgStrings "github.com/flanksource/gomplate/v3/strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/ext"
"github.com/mitchellh/mapstructure"
"github.com/robertkrimen/otto"
"github.com/robertkrimen/otto/registry"
_ "github.com/robertkrimen/otto/underscore"
Expand Down Expand Up @@ -67,15 +67,8 @@ func RunTemplate(environment map[string]any, template Template) (string, error)
return "", err
}

// marshal data from any to map[string]any
data, _ := json.Marshal(environment)
unstructured := make(map[string]any)
if err := json.Unmarshal(data, &unstructured); err != nil {
return "", err
}

var buf bytes.Buffer
if err := tpl.Execute(&buf, unstructured); err != nil {
if err := tpl.Execute(&buf, serialize(environment)); err != nil {
return "", fmt.Errorf("error executing template %s: %v", strings.Split(template.Template, "\n")[0], err)
}
return strings.TrimSpace(buf.String()), nil
Expand Down Expand Up @@ -142,57 +135,25 @@ func serialize(in map[string]any) map[string]any {

newMap := make(map[string]any, len(in))
for k, v := range in {
if reflect.ValueOf(v).Kind() == reflect.Struct {
newMap[k] = structToMap(v)
} else {
if reflect.ValueOf(v).Kind() != reflect.Struct {
newMap[k] = v
continue
}
}

return newMap
}

func structToMap(i any) map[string]any {
data := make(map[string]any)
value := reflect.ValueOf(i)
typeOf := value.Type()

if value.Kind() == reflect.Ptr {
value = value.Elem()
typeOf = value.Type()
}
var vMap map[string]any
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &vMap})
if err != nil {
fmt.Printf("error creating new mapstructure decoder: %v\n", err)
continue
}

for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
switch field.Kind() {
case reflect.Struct:
data[typeOf.Field(i).Name] = structToMap(field.Interface())
case reflect.Slice:
sliceData := make([]any, field.Len())
for j := 0; j < field.Len(); j++ {
sliceValue := field.Index(j)
if sliceValue.Kind() == reflect.Struct {
sliceData[j] = structToMap(sliceValue.Interface())
} else {
sliceData[j] = sliceValue.Interface()
}
}
data[typeOf.Field(i).Name] = sliceData
case reflect.Map:
mapData := make(map[string]any)
for _, key := range field.MapKeys() {
mapValue := field.MapIndex(key)
if mapValue.Kind() == reflect.Struct {
mapData[key.Interface().(string)] = structToMap(mapValue.Interface())
} else {
mapData[key.Interface().(string)] = mapValue.Interface()
}
}
data[typeOf.Field(i).Name] = mapData
default:
data[typeOf.Field(i).Name] = field.Interface()
if err := dec.Decode(v); err != nil {
fmt.Printf("error decoding new mapstructure decoder: %v\n", err)
continue
}

newMap[k] = vMap
}

return data
return newMap
}
76 changes: 24 additions & 52 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ import (
"github.com/stretchr/testify/assert"
)

type Address struct {
City string `json:"city_name"`
}

type Person struct {
Name string `json:"name"`
Address Address
MetaData map[string]any
Codes []string
}

// A shared test data for all template test
var structEnv = map[string]any{
"results": Person{
Name: "Aditya",
Address: Address{
City: "Kathmandu",
},
},
}

func TestJavascript(t *testing.T) {
tests := []struct {
env map[string]interface{}
Expand All @@ -27,6 +48,7 @@ func TestJavascript(t *testing.T) {
{map[string]interface{}{"x": "2m"}, "fromMillicores(x)", "2"},
{map[string]interface{}{"name": "mission.compute.internal"}, "k8s.getNodeName(name)", "mission"},
{map[string]interface{}{"msg": map[string]any{"healthStatus": map[string]string{"status": "HEALTHY"}}}, "k8s.conditions.isReady(msg)", "true"},
{structEnv, `results.name + " " + results.Address.city_name`, "Aditya Kathmandu"},
}

for _, tc := range tests {
Expand Down Expand Up @@ -55,6 +77,7 @@ func TestGomplate(t *testing.T) {
{map[string]interface{}{"v": "1.2.3-beta.1+c0ff33"}, "{{ (.v | semver).Prerelease }}", "beta.1"},
{map[string]interface{}{"old": "1.2.3", "new": "1.2.3"}, "{{ .old | semverCompare .new }}", "true"},
{map[string]interface{}{"old": "1.2.3", "new": "1.2.4"}, "{{ .old | semverCompare .new }}", "false"},
{structEnv, `{{.results.name}} {{.results.Address.city_name}}`, "Aditya Kathmandu"},
}

for _, tc := range tests {
Expand Down Expand Up @@ -105,18 +128,7 @@ func TestCel(t *testing.T) {
{nil, `[1,2,3,4].slice(1, 3)`, "[2 3]"}, // lists lib

// Support structs as environment var (by default they are not)
{
map[string]any{
"results": Person{
Name: "Aditya",
Address: Address{
City: "Kathmandu",
},
},
},
`results.Address.City == "Kathmandu" && results.Name == "Aditya"`,
"true",
},
{structEnv, `results.Address.city_name == "Kathmandu" && results.name == "Aditya"`, "true"},
}

for _, tc := range tests {
Expand All @@ -129,43 +141,3 @@ func TestCel(t *testing.T) {
})
}
}

type Address struct {
City string
}

type Person struct {
Name string
Address Address
MetaData map[string]any
Codes []string
}

func Test_structToMap(t *testing.T) {
sample := Person{
Name: "Aditya",
MetaData: map[string]any{
"foo": "bar",
},
Codes: []string{"1", "2"},
Address: Address{
City: "Kathmandu",
},
}

res := structToMap(sample)
assert.IsType(t, "", res["Name"])
assert.Equal(t, "Aditya", res["Name"])

assert.IsType(t, map[string]any{}, res["Address"])
if val, ok := res["Address"].(map[string]any); ok {
assert.IsType(t, "", val["City"])
assert.Equal(t, "Kathmandu", val["City"])
}

assert.IsType(t, map[string]any{}, res["MetaData"])
assert.Equal(t, map[string]any{"foo": "bar"}, res["MetaData"])

assert.IsType(t, []any{}, res["Codes"])
assert.Equal(t, []any{"1", "2"}, res["Codes"])
}

0 comments on commit 559c20c

Please sign in to comment.