mirror of
https://github.com/containous/traefik.git
synced 2025-01-06 13:17:52 +03:00
feat: raw map parser.
This commit is contained in:
parent
0186c31d59
commit
c42f1b7a50
@ -68,6 +68,30 @@ fii = "bir"
|
|||||||
assert.Equal(t, expected, element)
|
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) {
|
func TestDecode_YAML(t *testing.T) {
|
||||||
f, err := ioutil.TempFile("", "traefik-config-*.yaml")
|
f, err := ioutil.TempFile("", "traefik-config-*.yaml")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -126,3 +150,28 @@ yi: {}
|
|||||||
}
|
}
|
||||||
assert.Equal(t, expected, element)
|
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,7 +123,8 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||||||
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware19.compress": "true",
|
"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.entrypoints": "foobar, fiibar",
|
||||||
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
|
||||||
"traefik.http.routers.Router0.priority": "42",
|
"traefik.http.routers.Router0.priority": "42",
|
||||||
@ -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{
|
Services: map[string]*dynamic.Service{
|
||||||
"Service0": {
|
"Service0": {
|
||||||
@ -897,6 +906,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||||||
RetryExpression: "foobar",
|
RetryExpression: "foobar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"Middleware20": {
|
||||||
|
Plugin: map[string]dynamic.PluginConf{
|
||||||
|
"tomato": {
|
||||||
|
"aaa": "foo1",
|
||||||
|
"bbb": "foo2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"Middleware3": {
|
"Middleware3": {
|
||||||
Chain: &dynamic.Chain{
|
Chain: &dynamic.Chain{
|
||||||
Middlewares: []string{
|
Middlewares: []string{
|
||||||
@ -1205,6 +1222,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",
|
"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.EntryPoints": "foobar, fiibar",
|
||||||
"traefik.HTTP.Routers.Router0.Middlewares": "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()))
|
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 {
|
for _, child := range node.Children {
|
||||||
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
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)
|
key := reflect.ValueOf(child.Name)
|
||||||
field.SetMapIndex(key, value)
|
field.SetMapIndex(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,3 +350,23 @@ func setFloat(field reflect.Value, value string, bitSize int) error {
|
|||||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||||
return nil
|
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 {
|
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 {
|
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() {
|
for _, key := range rValue.MapKeys() {
|
||||||
child := &Node{Name: key.String(), FieldName: key.String()}
|
child := &Node{Name: key.String(), FieldName: key.String()}
|
||||||
node.Children = append(node.Children, child)
|
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 {
|
for _, test := range testCases {
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
// EncodeNode Converts a node to labels.
|
// EncodeNode Converts a node to labels.
|
||||||
// nodes -> labels.
|
// nodes -> labels.
|
||||||
func EncodeNode(node *Node) map[string]string {
|
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
|
childName := root + sep + child.Name
|
||||||
|
|
||||||
|
if child.RawValue != nil {
|
||||||
|
encodeRawValue(labels, childName, child.RawValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if len(child.Children) > 0 {
|
if len(child.Children) > 0 {
|
||||||
encodeNode(labels, childName, child)
|
encodeNode(labels, childName, child)
|
||||||
} else if len(child.Name) > 0 {
|
} 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",
|
"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 {
|
for _, test := range testCases {
|
||||||
|
@ -14,6 +14,7 @@ type Node struct {
|
|||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
FieldName string `json:"fieldName"`
|
FieldName string `json:"fieldName"`
|
||||||
Value string `json:"value,omitempty"`
|
Value string `json:"value,omitempty"`
|
||||||
|
RawValue interface{} `json:"rawValue,omitempty"`
|
||||||
Disabled bool `json:"disabled,omitempty"`
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
Kind reflect.Kind `json:"kind,omitempty"`
|
Kind reflect.Kind `json:"kind,omitempty"`
|
||||||
Tag reflect.StructTag `json:"tag,omitempty"`
|
Tag reflect.StructTag `json:"tag,omitempty"`
|
||||||
|
@ -57,6 +57,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
|
|||||||
rType = rootType.Elem()
|
rType = rootType.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface {
|
||||||
|
addRawValue(node)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
field, err := m.findTypedField(rType, node)
|
field, err := m.findTypedField(rType, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -88,6 +93,11 @@ func (m metadata) add(rootType reflect.Type, node *Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fType.Kind() == reflect.Map {
|
if fType.Kind() == reflect.Map {
|
||||||
|
if fType.Elem().Kind() == reflect.Interface {
|
||||||
|
addRawValue(node)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, child := range node.Children {
|
for _, child := range node.Children {
|
||||||
// elem is a map entry value type
|
// elem is a map entry value type
|
||||||
elem := fType.Elem()
|
elem := fType.Elem()
|
||||||
@ -194,3 +204,75 @@ func isSupportedType(field reflect.StructField) error {
|
|||||||
|
|
||||||
return nil
|
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 {
|
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
|
type MySliceType []string
|
||||||
|
@ -19,12 +19,7 @@ func Decode(labels map[string]string, element interface{}, rootName string, filt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
|
return Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode converts an element to labels.
|
// 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 New Issue
Block a user