feat: raw map parser.

This commit is contained in:
Fernandez Ludovic 2020-07-12 12:56:22 +02:00 committed by Traefiker Bot
parent 0186c31d59
commit c42f1b7a50
13 changed files with 901 additions and 18 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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] == ']'
}

View file

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

View file

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

View 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)
})
}
}