feat: raw map parser.
This commit is contained in:
parent
0186c31d59
commit
c42f1b7a50
13 changed files with 901 additions and 18 deletions
|
@ -68,6 +68,30 @@ fii = "bir"
|
|||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
||||
func TestDecodeContent_TOML_rawValue(t *testing.T) {
|
||||
content := `
|
||||
name = "test"
|
||||
[[meta.aaa]]
|
||||
bbb = 1
|
||||
`
|
||||
|
||||
type Foo struct {
|
||||
Name string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
element := &Foo{}
|
||||
|
||||
err := DecodeContent(content, ".toml", element)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &Foo{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{"aaa": []interface{}{map[string]interface{}{"bbb": "1"}}},
|
||||
}
|
||||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
||||
func TestDecode_YAML(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "traefik-config-*.yaml")
|
||||
require.NoError(t, err)
|
||||
|
@ -126,3 +150,28 @@ yi: {}
|
|||
}
|
||||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
||||
func TestDecodeContent_YAML_rawValue(t *testing.T) {
|
||||
content := `
|
||||
name: test
|
||||
meta:
|
||||
aaa:
|
||||
- bbb: 1
|
||||
`
|
||||
|
||||
type Foo struct {
|
||||
Name string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
element := &Foo{}
|
||||
|
||||
err := DecodeContent(content, ".yaml", element)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &Foo{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{"aaa": []interface{}{map[string]interface{}{"bbb": "1"}}},
|
||||
}
|
||||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
|
|
@ -123,18 +123,19 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware19.compress": "true",
|
||||
|
||||
"traefik.http.routers.Router0.entrypoints": "foobar, fiibar",
|
||||
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
||||
"traefik.http.routers.Router0.priority": "42",
|
||||
"traefik.http.routers.Router0.rule": "foobar",
|
||||
"traefik.http.routers.Router0.tls": "true",
|
||||
"traefik.http.routers.Router0.service": "foobar",
|
||||
"traefik.http.routers.Router1.entrypoints": "foobar, fiibar",
|
||||
"traefik.http.routers.Router1.middlewares": "foobar, fiibar",
|
||||
"traefik.http.routers.Router1.priority": "42",
|
||||
"traefik.http.routers.Router1.rule": "foobar",
|
||||
"traefik.http.routers.Router1.service": "foobar",
|
||||
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
|
||||
"traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2",
|
||||
"traefik.http.routers.Router0.entrypoints": "foobar, fiibar",
|
||||
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
||||
"traefik.http.routers.Router0.priority": "42",
|
||||
"traefik.http.routers.Router0.rule": "foobar",
|
||||
"traefik.http.routers.Router0.tls": "true",
|
||||
"traefik.http.routers.Router0.service": "foobar",
|
||||
"traefik.http.routers.Router1.entrypoints": "foobar, fiibar",
|
||||
"traefik.http.routers.Router1.middlewares": "foobar, fiibar",
|
||||
"traefik.http.routers.Router1.priority": "42",
|
||||
"traefik.http.routers.Router1.rule": "foobar",
|
||||
"traefik.http.routers.Router1.service": "foobar",
|
||||
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.headers.name0": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||
|
@ -574,6 +575,14 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"Middleware20": {
|
||||
Plugin: map[string]dynamic.PluginConf{
|
||||
"tomato": {
|
||||
"aaa": "foo1",
|
||||
"bbb": "foo2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"Service0": {
|
||||
|
@ -897,6 +906,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
RetryExpression: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware20": {
|
||||
Plugin: map[string]dynamic.PluginConf{
|
||||
"tomato": {
|
||||
"aaa": "foo1",
|
||||
"bbb": "foo2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware3": {
|
||||
Chain: &dynamic.Chain{
|
||||
Middlewares: []string{
|
||||
|
@ -1205,6 +1222,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",
|
||||
|
||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
|
|
|
@ -271,6 +271,16 @@ func (f filler) setMap(field reflect.Value, node *Node) error {
|
|||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
|
||||
if field.Type().Elem().Kind() == reflect.Interface {
|
||||
fillRawValue(field, node, false)
|
||||
|
||||
for _, child := range node.Children {
|
||||
fillRawValue(field, child, true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
||||
|
||||
|
@ -284,6 +294,7 @@ func (f filler) setMap(field reflect.Value, node *Node) error {
|
|||
key := reflect.ValueOf(child.Name)
|
||||
field.SetMapIndex(key, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -339,3 +350,23 @@ func setFloat(field reflect.Value, value string, bitSize int) error {
|
|||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func fillRawValue(field reflect.Value, node *Node, subMap bool) {
|
||||
m, ok := node.RawValue.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if _, self := m[node.Name]; self || !subMap {
|
||||
for k, v := range m {
|
||||
field.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
p := map[string]interface{}{node.Name: m}
|
||||
node.RawValue = p
|
||||
|
||||
field.SetMapIndex(reflect.ValueOf(node.Name), reflect.ValueOf(p[node.Name]))
|
||||
}
|
||||
|
|
|
@ -1413,6 +1413,84 @@ func TestFill(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "raw value",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Ptr,
|
||||
Children: []*Node{
|
||||
{Name: "meta", FieldName: "Meta", Kind: reflect.Map, RawValue: map[string]interface{}{
|
||||
"aaa": "test",
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test",
|
||||
"ddd": map[string]interface{}{
|
||||
"eee": "test",
|
||||
},
|
||||
},
|
||||
}},
|
||||
{Name: "name", FieldName: "Name", Value: "test", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
element: &struct {
|
||||
Name string
|
||||
Meta map[string]interface{}
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Name string
|
||||
Meta map[string]interface{}
|
||||
}{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": "test",
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test",
|
||||
"ddd": map[string]interface{}{
|
||||
"eee": "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, raw value",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Ptr,
|
||||
Children: []*Node{
|
||||
{Name: "meta", FieldName: "Meta", Kind: reflect.Map, Children: []*Node{
|
||||
{Name: "aaa", Kind: reflect.Map, Children: []*Node{
|
||||
{Name: "bbb", RawValue: map[string]interface{}{
|
||||
"ccc": "test1",
|
||||
"ddd": "test2",
|
||||
}},
|
||||
{Name: "eee", Value: "test3", RawValue: map[string]interface{}{
|
||||
"eee": "test3",
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
{Name: "name", FieldName: "Name", Value: "test", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
element: &struct {
|
||||
Name string
|
||||
Meta map[string]map[string]interface{}
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Name string
|
||||
Meta map[string]map[string]interface{}
|
||||
}{
|
||||
Name: "test",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test1",
|
||||
"ddd": "test2",
|
||||
},
|
||||
"eee": "test3",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -123,6 +123,11 @@ func (e encoderToNode) setStructValue(node *Node, rValue reflect.Value) error {
|
|||
}
|
||||
|
||||
func (e encoderToNode) setMapValue(node *Node, rValue reflect.Value) error {
|
||||
if rValue.Type().Elem().Kind() == reflect.Interface {
|
||||
node.RawValue = rValue.Interface()
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, key := range rValue.MapKeys() {
|
||||
child := &Node{Name: key.String(), FieldName: key.String()}
|
||||
node.Children = append(node.Children, child)
|
||||
|
|
|
@ -755,6 +755,42 @@ func TestEncodeToNode(t *testing.T) {
|
|||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "raw value",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Bar map[string]interface{}
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Bar map[string]interface{}
|
||||
}{
|
||||
Bar: map[string]interface{}{
|
||||
"AAA": "valueA",
|
||||
"BBB": map[string]interface{}{
|
||||
"CCC": map[string]interface{}{
|
||||
"DDD": "valueD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", RawValue: map[string]interface{}{
|
||||
"AAA": "valueA",
|
||||
"BBB": map[string]interface{}{
|
||||
"CCC": map[string]interface{}{
|
||||
"DDD": "valueD",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// EncodeNode Converts a node to labels.
|
||||
// nodes -> labels.
|
||||
func EncodeNode(node *Node) map[string]string {
|
||||
|
@ -21,6 +26,11 @@ func encodeNode(labels map[string]string, root string, node *Node) {
|
|||
|
||||
childName := root + sep + child.Name
|
||||
|
||||
if child.RawValue != nil {
|
||||
encodeRawValue(labels, childName, child.RawValue)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(child.Children) > 0 {
|
||||
encodeNode(labels, childName, child)
|
||||
} else if len(child.Name) > 0 {
|
||||
|
@ -28,3 +38,30 @@ func encodeNode(labels map[string]string, root string, node *Node) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encodeRawValue(labels map[string]string, root string, rawValue interface{}) {
|
||||
if rawValue == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tValue := reflect.TypeOf(rawValue)
|
||||
|
||||
if tValue.Kind() == reflect.Map && tValue.Elem().Kind() == reflect.Interface {
|
||||
r := reflect.ValueOf(rawValue).
|
||||
Convert(reflect.TypeOf((map[string]interface{})(nil))).
|
||||
Interface().(map[string]interface{})
|
||||
|
||||
for k, v := range r {
|
||||
switch tv := v.(type) {
|
||||
case string:
|
||||
labels[root+"."+k] = tv
|
||||
case []interface{}:
|
||||
for i, e := range tv {
|
||||
encodeRawValue(labels, fmt.Sprintf("%s.%s[%d]", root, k, i), e)
|
||||
}
|
||||
default:
|
||||
encodeRawValue(labels, root+"."+k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,6 +165,60 @@ func TestEncodeNode(t *testing.T) {
|
|||
"traefik.foo[1].bbb": "bur1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "raw value, level 1",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", RawValue: map[string]interface{}{
|
||||
"bbb": "test1",
|
||||
"ccc": "test2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa.bbb": "test1",
|
||||
"traefik.aaa.ccc": "test2",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "raw value, level 2",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", RawValue: map[string]interface{}{
|
||||
"bbb": "test1",
|
||||
"ccc": map[string]interface{}{
|
||||
"ddd": "test2",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa.bbb": "test1",
|
||||
"traefik.aaa.ccc.ddd": "test2",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "raw value, slice of struct",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", RawValue: map[string]interface{}{
|
||||
"bbb": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ccc": "test1",
|
||||
"ddd": "test2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa.bbb[0].ccc": "test1",
|
||||
"traefik.aaa.bbb[0].ddd": "test2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -14,6 +14,7 @@ type Node struct {
|
|||
Description string `json:"description,omitempty"`
|
||||
FieldName string `json:"fieldName"`
|
||||
Value string `json:"value,omitempty"`
|
||||
RawValue interface{} `json:"rawValue,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Kind reflect.Kind `json:"kind,omitempty"`
|
||||
Tag reflect.StructTag `json:"tag,omitempty"`
|
||||
|
|
|
@ -57,6 +57,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
|
|||
rType = rootType.Elem()
|
||||
}
|
||||
|
||||
if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface {
|
||||
addRawValue(node)
|
||||
return nil
|
||||
}
|
||||
|
||||
field, err := m.findTypedField(rType, node)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -88,6 +93,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
|
|||
}
|
||||
|
||||
if fType.Kind() == reflect.Map {
|
||||
if fType.Elem().Kind() == reflect.Interface {
|
||||
addRawValue(node)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
// elem is a map entry value type
|
||||
elem := fType.Elem()
|
||||
|
@ -194,3 +204,75 @@ func isSupportedType(field reflect.StructField) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
RawMap section
|
||||
*/
|
||||
|
||||
func addRawValue(node *Node) {
|
||||
if node.RawValue == nil {
|
||||
node.RawValue = nodeToRawMap(node)
|
||||
}
|
||||
|
||||
node.Children = nil
|
||||
}
|
||||
|
||||
func nodeToRawMap(node *Node) map[string]interface{} {
|
||||
result := map[string]interface{}{}
|
||||
|
||||
squashNode(node, result, true)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func squashNode(node *Node, acc map[string]interface{}, root bool) {
|
||||
if len(node.Children) == 0 {
|
||||
acc[node.Name] = node.Value
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// slice
|
||||
if isArrayKey(node.Children[0].Name) {
|
||||
var accChild []interface{}
|
||||
|
||||
for _, child := range node.Children {
|
||||
tmp := map[string]interface{}{}
|
||||
squashNode(child, tmp, false)
|
||||
accChild = append(accChild, tmp[child.Name])
|
||||
}
|
||||
|
||||
acc[node.Name] = accChild
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// map
|
||||
var accChild map[string]interface{}
|
||||
if root {
|
||||
accChild = acc
|
||||
} else {
|
||||
accChild = typedRawMap(acc, node.Name)
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
squashNode(child, accChild, false)
|
||||
}
|
||||
}
|
||||
|
||||
func typedRawMap(m map[string]interface{}, k string) map[string]interface{} {
|
||||
if m[k] == nil {
|
||||
m[k] = map[string]interface{}{}
|
||||
}
|
||||
|
||||
r, ok := m[k].(map[string]interface{})
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unsupported value (key: %s): %T", k, m[k]))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func isArrayKey(name string) bool {
|
||||
return name[0] == '[' && name[len(name)-1] == ']'
|
||||
}
|
||||
|
|
|
@ -991,6 +991,51 @@ func TestAddMetadata(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "raw value",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Children: []*Node{
|
||||
{Name: "AAA", FieldName: "AAA", Value: "valueA"},
|
||||
{Name: "BBB", FieldName: "BBB", Children: []*Node{
|
||||
{Name: "CCC", FieldName: "CCC", Children: []*Node{
|
||||
{Name: "DDD", FieldName: "DDD", Value: "valueD"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *struct {
|
||||
Bar map[string]interface{}
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Bar map[string]interface{}
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Kind: reflect.Map, RawValue: map[string]interface{}{
|
||||
"AAA": "valueA",
|
||||
"BBB": map[string]interface{}{
|
||||
"CCC": map[string]interface{}{
|
||||
"DDD": "valueD",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -1015,4 +1060,109 @@ func TestAddMetadata(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_nodeToRawMap(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
root *Node
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
desc: "simple",
|
||||
root: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "meta", Children: []*Node{
|
||||
{Name: "aaa", Value: "test1"},
|
||||
{Name: "bbb", Children: []*Node{
|
||||
{Name: "ccc", Value: "test2"},
|
||||
{Name: "ddd", Children: []*Node{
|
||||
{Name: "eee", Value: "test3"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
{Name: "name", Value: "bla"},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"meta": map[string]interface{}{
|
||||
"aaa": "test1",
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test2",
|
||||
"ddd": map[string]interface{}{
|
||||
"eee": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "bla",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct, level 1",
|
||||
root: &Node{
|
||||
Name: "aaa",
|
||||
Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "bbb", Value: "test1"},
|
||||
{Name: "ccc", Value: "test2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"aaa": []interface{}{
|
||||
map[string]interface{}{
|
||||
"bbb": "test1",
|
||||
"ccc": "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct, level 2",
|
||||
root: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "meta", Children: []*Node{{
|
||||
Name: "aaa", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "bbb", Value: "test2"},
|
||||
{Name: "ccc", Value: "test3"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "bbb", Value: "test4"},
|
||||
{Name: "ccc", Value: "test5"},
|
||||
}},
|
||||
},
|
||||
}}},
|
||||
{Name: "name", Value: "test1"},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"meta": map[string]interface{}{
|
||||
"aaa": []interface{}{
|
||||
map[string]interface{}{
|
||||
"bbb": "test2",
|
||||
"ccc": "test3",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bbb": "test4",
|
||||
"ccc": "test5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := nodeToRawMap(test.root)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MySliceType []string
|
||||
|
|
|
@ -19,12 +19,7 @@ func Decode(labels map[string]string, element interface{}, rootName string, filt
|
|||
return err
|
||||
}
|
||||
|
||||
err = Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
|
||||
}
|
||||
|
||||
// Encode converts an element to labels.
|
||||
|
|
346
pkg/config/parser/parser_test.go
Normal file
346
pkg/config/parser/parser_test.go
Normal file
|
@ -0,0 +1,346 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type Tomato struct {
|
||||
Name string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
type Potato struct {
|
||||
Name string
|
||||
Meta map[string]map[string]interface{}
|
||||
}
|
||||
|
||||
func TestDecode_RawValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
labels map[string]string
|
||||
elt interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
desc: "level 1",
|
||||
elt: &Tomato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa": "test",
|
||||
},
|
||||
expected: &Tomato{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2",
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa": "test",
|
||||
"traefik.meta.bbb.ccc": "test",
|
||||
},
|
||||
elt: &Tomato{},
|
||||
expected: &Tomato{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": "test",
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 3",
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa": "test",
|
||||
"traefik.meta.bbb.ccc": "test",
|
||||
"traefik.meta.bbb.ddd.eee": "test",
|
||||
},
|
||||
elt: &Tomato{},
|
||||
expected: &Tomato{
|
||||
Name: "test",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": "test",
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test",
|
||||
"ddd": map[string]interface{}{
|
||||
"eee": "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct slice, one entry",
|
||||
elt: &Tomato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa[0].bbb": "test2",
|
||||
"traefik.meta.aaa[0].ccc": "test3",
|
||||
},
|
||||
expected: &Tomato{
|
||||
Name: "test1",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": []interface{}{
|
||||
map[string]interface{}{
|
||||
"bbb": "test2",
|
||||
"ccc": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct slice, multiple entries",
|
||||
elt: &Tomato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa[0].bbb": "test2",
|
||||
"traefik.meta.aaa[0].ccc": "test3",
|
||||
"traefik.meta.aaa[1].bbb": "test4",
|
||||
"traefik.meta.aaa[1].ccc": "test5",
|
||||
"traefik.meta.aaa[2].bbb": "test6",
|
||||
"traefik.meta.aaa[2].ccc": "test7",
|
||||
},
|
||||
expected: &Tomato{
|
||||
Name: "test1",
|
||||
Meta: map[string]interface{}{
|
||||
"aaa": []interface{}{
|
||||
map[string]interface{}{
|
||||
"bbb": "test2",
|
||||
"ccc": "test3",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bbb": "test4",
|
||||
"ccc": "test5",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bbb": "test6",
|
||||
"ccc": "test7",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, level 1",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa.bbb": "test1",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, level 2",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa.bbb": "test1",
|
||||
"traefik.meta.aaa.ccc": "test2",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": "test1",
|
||||
"ccc": "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, level 3",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa.bbb.ccc": "test1",
|
||||
"traefik.meta.aaa.bbb.ddd": "test2",
|
||||
"traefik.meta.aaa.eee": "test3",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": "test1",
|
||||
"ddd": "test2",
|
||||
},
|
||||
"eee": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, level 4",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test",
|
||||
"traefik.meta.aaa.bbb.ccc.ddd": "test1",
|
||||
"traefik.meta.aaa.bbb.ccc.eee": "test2",
|
||||
"traefik.meta.aaa.bbb.fff": "test3",
|
||||
"traefik.meta.aaa.ggg": "test4",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": map[string]interface{}{
|
||||
"ddd": "test1",
|
||||
"eee": "test2",
|
||||
},
|
||||
"fff": "test3",
|
||||
},
|
||||
"ggg": "test4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, struct slice, level 1, one entry",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa.bbb[0].ccc": "test2",
|
||||
"traefik.meta.aaa.bbb[0].ddd": "test3",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test1",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ccc": "test2",
|
||||
"ddd": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, struct slice, level 1, multiple entries",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa.bbb[0].ccc": "test2",
|
||||
"traefik.meta.aaa.bbb[0].ddd": "test3",
|
||||
"traefik.meta.aaa.bbb[1].ccc": "test4",
|
||||
"traefik.meta.aaa.bbb[1].ddd": "test5",
|
||||
"traefik.meta.aaa.bbb[2].ccc": "test6",
|
||||
"traefik.meta.aaa.bbb[2].ddd": "test7",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test1",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ccc": "test2",
|
||||
"ddd": "test3",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"ccc": "test4",
|
||||
"ddd": "test5",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"ccc": "test6",
|
||||
"ddd": "test7",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, struct slice, level 2, one entry",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa.bbb.ccc[0].ddd": "test2",
|
||||
"traefik.meta.aaa.bbb.ccc[0].eee": "test3",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test1",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ddd": "test2",
|
||||
"eee": "test3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "explicit map of map, struct slice, level 2, multiple entries",
|
||||
elt: &Potato{},
|
||||
labels: map[string]string{
|
||||
"traefik.name": "test1",
|
||||
"traefik.meta.aaa.bbb.ccc[0].ddd": "test2",
|
||||
"traefik.meta.aaa.bbb.ccc[0].eee": "test3",
|
||||
"traefik.meta.aaa.bbb.ccc[1].ddd": "test4",
|
||||
"traefik.meta.aaa.bbb.ccc[1].eee": "test5",
|
||||
"traefik.meta.aaa.bbb.ccc[2].ddd": "test6",
|
||||
"traefik.meta.aaa.bbb.ccc[2].eee": "test7",
|
||||
},
|
||||
expected: &Potato{
|
||||
Name: "test1",
|
||||
Meta: map[string]map[string]interface{}{
|
||||
"aaa": {
|
||||
"bbb": map[string]interface{}{
|
||||
"ccc": []interface{}{
|
||||
map[string]interface{}{
|
||||
"ddd": "test2",
|
||||
"eee": "test3",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"ddd": "test4",
|
||||
"eee": "test5",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"ddd": "test6",
|
||||
"eee": "test7",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if test.desc != "level 3" {
|
||||
continue
|
||||
}
|
||||
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
err := Decode(test.labels, test.elt, "traefik")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, test.elt)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue