New static configuration loading system.
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
parent
d18edd6f77
commit
8d7eccad5d
165 changed files with 10894 additions and 6076 deletions
327
pkg/config/parser/element_fill.go
Normal file
327
pkg/config/parser/element_fill.go
Normal file
|
@ -0,0 +1,327 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
type initializer interface {
|
||||
SetDefaults()
|
||||
}
|
||||
|
||||
// Fill populates the fields of the element using the information in node.
|
||||
func Fill(element interface{}, node *Node) error {
|
||||
if element == nil || node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.Kind == 0 {
|
||||
return fmt.Errorf("missing node type: %s", node.Name)
|
||||
}
|
||||
|
||||
root := reflect.ValueOf(element)
|
||||
if root.Kind() == reflect.Struct {
|
||||
return fmt.Errorf("struct are not supported, use pointer instead")
|
||||
}
|
||||
|
||||
return fill(root.Elem(), node)
|
||||
}
|
||||
|
||||
func fill(field reflect.Value, node *Node) error {
|
||||
// related to allow-empty tag
|
||||
if node.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(node.Value)
|
||||
return nil
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(node.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(val)
|
||||
return nil
|
||||
case reflect.Int8:
|
||||
return setInt(field, node.Value, 8)
|
||||
case reflect.Int16:
|
||||
return setInt(field, node.Value, 16)
|
||||
case reflect.Int32:
|
||||
return setInt(field, node.Value, 32)
|
||||
case reflect.Int64, reflect.Int:
|
||||
return setInt(field, node.Value, 64)
|
||||
case reflect.Uint8:
|
||||
return setUint(field, node.Value, 8)
|
||||
case reflect.Uint16:
|
||||
return setUint(field, node.Value, 16)
|
||||
case reflect.Uint32:
|
||||
return setUint(field, node.Value, 32)
|
||||
case reflect.Uint64, reflect.Uint:
|
||||
return setUint(field, node.Value, 64)
|
||||
case reflect.Float32:
|
||||
return setFloat(field, node.Value, 32)
|
||||
case reflect.Float64:
|
||||
return setFloat(field, node.Value, 64)
|
||||
case reflect.Struct:
|
||||
return setStruct(field, node)
|
||||
case reflect.Ptr:
|
||||
return setPtr(field, node)
|
||||
case reflect.Map:
|
||||
return setMap(field, node)
|
||||
case reflect.Slice:
|
||||
return setSlice(field, node)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setPtr(field reflect.Value, node *Node) error {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
|
||||
if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) {
|
||||
method := field.MethodByName("SetDefaults")
|
||||
if method.IsValid() {
|
||||
method.Call([]reflect.Value{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fill(field.Elem(), node)
|
||||
}
|
||||
|
||||
func setStruct(field reflect.Value, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
fd := field.FieldByName(child.FieldName)
|
||||
|
||||
zeroValue := reflect.Value{}
|
||||
if fd == zeroValue {
|
||||
return fmt.Errorf("field not found, node: %s (%s)", child.Name, child.FieldName)
|
||||
}
|
||||
|
||||
err := fill(fd, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSlice(field reflect.Value, node *Node) error {
|
||||
if field.Type().Elem().Kind() == reflect.Struct ||
|
||||
field.Type().Elem().Kind() == reflect.Ptr && field.Type().Elem().Elem().Kind() == reflect.Struct {
|
||||
return setSliceStruct(field, node)
|
||||
}
|
||||
|
||||
if len(node.Value) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := strings.Split(node.Value, ",")
|
||||
|
||||
slice := reflect.MakeSlice(field.Type(), len(values), len(values))
|
||||
field.Set(slice)
|
||||
|
||||
for i := 0; i < len(values); i++ {
|
||||
value := strings.TrimSpace(values[i])
|
||||
|
||||
switch field.Type().Elem().Kind() {
|
||||
case reflect.String:
|
||||
field.Index(i).SetString(value)
|
||||
case reflect.Int:
|
||||
val, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetInt(val)
|
||||
case reflect.Int8:
|
||||
err := setInt(field.Index(i), value, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int16:
|
||||
err := setInt(field.Index(i), value, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int32:
|
||||
err := setInt(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int64:
|
||||
err := setInt(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint:
|
||||
val, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetUint(val)
|
||||
case reflect.Uint8:
|
||||
err := setUint(field.Index(i), value, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint16:
|
||||
err := setUint(field.Index(i), value, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint32:
|
||||
err := setUint(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint64:
|
||||
err := setUint(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Float32:
|
||||
err := setFloat(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Float64:
|
||||
err := setFloat(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetBool(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %s", field.Type().Elem())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceStruct(field reflect.Value, node *Node) error {
|
||||
if node.Tag.Get(TagLabelSliceAsStruct) != "" {
|
||||
return setSliceAsStruct(field, node)
|
||||
}
|
||||
|
||||
field.Set(reflect.MakeSlice(field.Type(), len(node.Children), len(node.Children)))
|
||||
|
||||
for i, child := range node.Children {
|
||||
// use Ptr to allow "SetDefaults"
|
||||
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
||||
err := setPtr(value, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Index(i).Set(value.Elem().Elem())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceAsStruct(field reflect.Value, node *Node) error {
|
||||
if len(node.Children) == 0 {
|
||||
return fmt.Errorf("invalid slice: node %s", node.Name)
|
||||
}
|
||||
|
||||
// use Ptr to allow "SetDefaults"
|
||||
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
||||
err := setPtr(value, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elem := value.Elem().Elem()
|
||||
|
||||
field.Set(reflect.MakeSlice(field.Type(), 1, 1))
|
||||
field.Index(0).Set(elem)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setMap(field reflect.Value, node *Node) error {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
||||
|
||||
err := fill(ptrValue, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := ptrValue.Elem().Elem()
|
||||
|
||||
key := reflect.ValueOf(child.Name)
|
||||
field.SetMapIndex(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setInt(field reflect.Value, value string, bitSize int) error {
|
||||
switch field.Type() {
|
||||
case reflect.TypeOf(types.Duration(0)):
|
||||
return setDuration(field, value, bitSize, time.Second)
|
||||
case reflect.TypeOf(time.Duration(0)):
|
||||
return setDuration(field, value, bitSize, time.Nanosecond)
|
||||
default:
|
||||
val, err := strconv.ParseInt(value, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setDuration(field reflect.Value, value string, bitSize int, defaultUnit time.Duration) error {
|
||||
val, err := strconv.ParseInt(value, 10, bitSize)
|
||||
if err == nil {
|
||||
field.Set(reflect.ValueOf(time.Duration(val) * defaultUnit).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(duration).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUint(field reflect.Value, value string, bitSize int) error {
|
||||
val, err := strconv.ParseUint(value, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func setFloat(field reflect.Value, value string, bitSize int) error {
|
||||
val, err := strconv.ParseFloat(value, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
1431
pkg/config/parser/element_fill_test.go
Normal file
1431
pkg/config/parser/element_fill_test.go
Normal file
File diff suppressed because it is too large
Load diff
203
pkg/config/parser/element_nodes.go
Normal file
203
pkg/config/parser/element_nodes.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EncodeToNode converts an element to a node.
|
||||
// element -> nodes
|
||||
func EncodeToNode(element interface{}, omitEmpty bool) (*Node, error) {
|
||||
rValue := reflect.ValueOf(element)
|
||||
node := &Node{Name: "traefik"}
|
||||
|
||||
encoder := encoderToNode{omitEmpty: omitEmpty}
|
||||
|
||||
err := encoder.setNodeValue(node, rValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
type encoderToNode struct {
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
func (e encoderToNode) setNodeValue(node *Node, rValue reflect.Value) error {
|
||||
switch rValue.Kind() {
|
||||
case reflect.String:
|
||||
node.Value = rValue.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
node.Value = strconv.FormatInt(rValue.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
node.Value = strconv.FormatUint(rValue.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
node.Value = strconv.FormatFloat(rValue.Float(), 'f', 6, 64)
|
||||
case reflect.Bool:
|
||||
node.Value = strconv.FormatBool(rValue.Bool())
|
||||
case reflect.Struct:
|
||||
return e.setStructValue(node, rValue)
|
||||
case reflect.Ptr:
|
||||
return e.setNodeValue(node, rValue.Elem())
|
||||
case reflect.Map:
|
||||
return e.setMapValue(node, rValue)
|
||||
case reflect.Slice:
|
||||
return e.setSliceValue(node, rValue)
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e encoderToNode) setStructValue(node *Node, rValue reflect.Value) error {
|
||||
rType := rValue.Type()
|
||||
|
||||
for i := 0; i < rValue.NumField(); i++ {
|
||||
field := rType.Field(i)
|
||||
fieldValue := rValue.Field(i)
|
||||
|
||||
if !IsExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Tag.Get(TagLabel) == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := isSupportedType(field); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.isSkippedField(field, fieldValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeName := field.Name
|
||||
if field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
|
||||
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
|
||||
}
|
||||
|
||||
if field.Anonymous {
|
||||
if err := e.setNodeValue(node, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
child := &Node{Name: nodeName, FieldName: field.Name, Description: field.Tag.Get(TagDescription)}
|
||||
|
||||
if err := e.setNodeValue(child, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
if field.Type.Elem().Kind() != reflect.Struct && fieldValue.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Type.Elem().Kind() == reflect.Struct && len(child.Children) == 0 {
|
||||
if field.Tag.Get(TagLabel) != TagLabelAllowEmpty {
|
||||
continue
|
||||
}
|
||||
|
||||
child.Value = "true"
|
||||
}
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e encoderToNode) setMapValue(node *Node, rValue reflect.Value) error {
|
||||
for _, key := range rValue.MapKeys() {
|
||||
child := &Node{Name: key.String(), FieldName: key.String()}
|
||||
node.Children = append(node.Children, child)
|
||||
|
||||
if err := e.setNodeValue(child, rValue.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e encoderToNode) setSliceValue(node *Node, rValue reflect.Value) error {
|
||||
// label-slice-as-struct
|
||||
if rValue.Type().Elem().Kind() == reflect.Struct && !strings.EqualFold(node.Name, node.FieldName) {
|
||||
if rValue.Len() > 1 {
|
||||
return fmt.Errorf("node %s has too many slice entries: %d", node.Name, rValue.Len())
|
||||
}
|
||||
|
||||
return e.setNodeValue(node, rValue.Index(0))
|
||||
}
|
||||
|
||||
if rValue.Type().Elem().Kind() == reflect.Struct ||
|
||||
rValue.Type().Elem().Kind() == reflect.Ptr && rValue.Type().Elem().Elem().Kind() == reflect.Struct {
|
||||
for i := 0; i < rValue.Len(); i++ {
|
||||
child := &Node{Name: "[" + strconv.Itoa(i) + "]"}
|
||||
|
||||
eValue := rValue.Index(i)
|
||||
|
||||
err := e.setNodeValue(child, eValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var values []string
|
||||
|
||||
for i := 0; i < rValue.Len(); i++ {
|
||||
eValue := rValue.Index(i)
|
||||
|
||||
switch eValue.Kind() {
|
||||
case reflect.String:
|
||||
values = append(values, eValue.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
values = append(values, strconv.FormatInt(eValue.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
values = append(values, strconv.FormatUint(eValue.Uint(), 10))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
values = append(values, strconv.FormatFloat(eValue.Float(), 'f', 6, 64))
|
||||
case reflect.Bool:
|
||||
values = append(values, strconv.FormatBool(eValue.Bool()))
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
node.Value = strings.Join(values, ", ")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e encoderToNode) isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
|
||||
if e.omitEmpty && field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct && fieldValue.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
if e.omitEmpty && (field.Type.Kind() == reflect.Slice) &&
|
||||
(fieldValue.IsNil() || fieldValue.Len() == 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (field.Type.Kind() == reflect.Map) &&
|
||||
(fieldValue.IsNil() || fieldValue.Len() == 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
737
pkg/config/parser/element_nodes_test.go
Normal file
737
pkg/config/parser/element_nodes_test.go
Normal file
|
@ -0,0 +1,737 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeToNode(t *testing.T) {
|
||||
type expected struct {
|
||||
node *Node
|
||||
error bool
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "Description",
|
||||
element: struct {
|
||||
Foo string `description:"text"`
|
||||
}{Foo: "bar"},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Description: "text"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string",
|
||||
element: struct {
|
||||
Foo string
|
||||
}{Foo: "bar"},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 string fields",
|
||||
element: struct {
|
||||
Foo string
|
||||
Fii string
|
||||
}{Foo: "bar", Fii: "hii"},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar"},
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int",
|
||||
element: struct {
|
||||
Foo int
|
||||
}{Foo: 1},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int8",
|
||||
element: struct {
|
||||
Foo int8
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int16",
|
||||
element: struct {
|
||||
Foo int16
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int32",
|
||||
element: struct {
|
||||
Foo int32
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int64",
|
||||
element: struct {
|
||||
Foo int64
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint",
|
||||
element: struct {
|
||||
Foo uint
|
||||
}{Foo: 1},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint8",
|
||||
element: struct {
|
||||
Foo uint8
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint16",
|
||||
element: struct {
|
||||
Foo uint16
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint32",
|
||||
element: struct {
|
||||
Foo uint32
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint64",
|
||||
element: struct {
|
||||
Foo uint64
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "float32",
|
||||
element: struct {
|
||||
Foo float32
|
||||
}{Foo: 1.12},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "float64",
|
||||
element: struct {
|
||||
Foo float64
|
||||
}{Foo: 1.12},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool",
|
||||
element: struct {
|
||||
Foo bool
|
||||
}{Foo: true},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct unexported field",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Fii string
|
||||
fuu string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Fii string
|
||||
fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: func(v string) *string { return &v }("hii"),
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string nil pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: nil,
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *int
|
||||
Fuu int
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *int
|
||||
Fuu int
|
||||
}{
|
||||
Fii: func(v int) *int { return &v }(6),
|
||||
Fuu: 4,
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "6"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "4"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *bool
|
||||
Fuu bool
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *bool
|
||||
Fuu bool
|
||||
}{
|
||||
Fii: func(v bool) *bool { return &v }(true),
|
||||
Fuu: true,
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "true"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "true"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct nil struct pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer, not allowEmpty",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer, allowEmpty",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
} `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Bar map[string]string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: map[string]string{
|
||||
"name1": "huu",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Children: []*Node{
|
||||
{Name: "name1", FieldName: "name1", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
desc: "empty map",
|
||||
element: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: map[string]string{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "map nil",
|
||||
element: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "map with non string key",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Bar map[int]string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar map[int]string
|
||||
}{
|
||||
Bar: map[int]string{
|
||||
1: "huu",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "slice of string",
|
||||
element: struct{ Bar []string }{Bar: []string{"huu", "hii"}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "huu, hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int",
|
||||
element: struct{ Bar []int }{Bar: []int{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int8",
|
||||
element: struct{ Bar []int8 }{Bar: []int8{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int16",
|
||||
element: struct{ Bar []int16 }{Bar: []int16{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int32",
|
||||
element: struct{ Bar []int32 }{Bar: []int32{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int64",
|
||||
element: struct{ Bar []int64 }{Bar: []int64{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint",
|
||||
element: struct{ Bar []uint }{Bar: []uint{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint8",
|
||||
element: struct{ Bar []uint8 }{Bar: []uint8{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint16",
|
||||
element: struct{ Bar []uint16 }{Bar: []uint16{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint32",
|
||||
element: struct{ Bar []uint32 }{Bar: []uint32{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint64",
|
||||
element: struct{ Bar []uint64 }{Bar: []uint64{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of float32",
|
||||
element: struct{ Bar []float32 }{Bar: []float32{4.1, 2, 3.2}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of float64",
|
||||
element: struct{ Bar []float64 }{Bar: []float64{4.1, 2, 3.2}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of bool",
|
||||
element: struct{ Bar []bool }{Bar: []bool{true, false, true}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "true, false, true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice label-slice-as-struct",
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "haa"},
|
||||
{Name: "Bir", FieldName: "Bir", Value: "hii"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice label-slice-as-struct several slice entries",
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct",
|
||||
element: struct {
|
||||
Foo []struct {
|
||||
Field string
|
||||
}
|
||||
}{
|
||||
Foo: []struct {
|
||||
Field string
|
||||
}{
|
||||
{
|
||||
Field: "bar",
|
||||
},
|
||||
{
|
||||
Field: "bir",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "Field", FieldName: "Field", Value: "bar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "Field", FieldName: "Field", Value: "bir"},
|
||||
}},
|
||||
}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
desc: "slice of pointer of struct",
|
||||
element: struct {
|
||||
Foo []*struct {
|
||||
Field string
|
||||
}
|
||||
}{
|
||||
Foo: []*struct {
|
||||
Field string
|
||||
}{
|
||||
{Field: "bar"},
|
||||
{Field: "bir"},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "Field", FieldName: "Field", Value: "bar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "Field", FieldName: "Field", Value: "bir"},
|
||||
}},
|
||||
}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
desc: "empty slice",
|
||||
element: struct {
|
||||
Bar []string
|
||||
}{
|
||||
Bar: []string{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "nil slice",
|
||||
element: struct {
|
||||
Bar []string
|
||||
}{
|
||||
Bar: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "ignore slice",
|
||||
element: struct {
|
||||
Bar []string `label:"-"`
|
||||
}{
|
||||
Bar: []string{"huu", "hii"},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "embedded",
|
||||
element: struct {
|
||||
Foo struct{ FiiFoo }
|
||||
}{
|
||||
Foo: struct{ FiiFoo }{
|
||||
FiiFoo: FiiFoo{
|
||||
Fii: "hii",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := EncodeToNode(test.element, true)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected.node, node)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
166
pkg/config/parser/flat_encode.go
Normal file
166
pkg/config/parser/flat_encode.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
const defaultPtrValue = "false"
|
||||
|
||||
// FlatOpts holds options used when encoding to Flat.
|
||||
type FlatOpts struct {
|
||||
Case string // "lower" or "upper", defaults to "lower".
|
||||
Separator string
|
||||
SkipRoot bool
|
||||
}
|
||||
|
||||
// Flat is a configuration item representation.
|
||||
type Flat struct {
|
||||
Name string
|
||||
Description string
|
||||
Default string
|
||||
}
|
||||
|
||||
// EncodeToFlat encodes a node to a Flat representation.
|
||||
// Even though the given node argument should have already been augmented with metadata such as kind,
|
||||
// the element (and its type information) is still needed to treat remaining edge cases.
|
||||
func EncodeToFlat(element interface{}, node *Node, opts FlatOpts) ([]Flat, error) {
|
||||
if element == nil || node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if node.Kind == 0 {
|
||||
return nil, fmt.Errorf("missing node type: %s", node.Name)
|
||||
}
|
||||
|
||||
elem := reflect.ValueOf(element)
|
||||
if elem.Kind() == reflect.Struct {
|
||||
return nil, fmt.Errorf("structs are not supported, use pointer instead")
|
||||
}
|
||||
|
||||
encoder := encoderToFlat{FlatOpts: opts}
|
||||
|
||||
var entries []Flat
|
||||
if encoder.SkipRoot {
|
||||
for _, child := range node.Children {
|
||||
field := encoder.getField(elem.Elem(), child)
|
||||
entries = append(entries, encoder.createFlat(field, child.Name, child)...)
|
||||
}
|
||||
} else {
|
||||
entries = encoder.createFlat(elem, strings.ToLower(node.Name), node)
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
type encoderToFlat struct {
|
||||
FlatOpts
|
||||
}
|
||||
|
||||
func (e encoderToFlat) createFlat(field reflect.Value, name string, node *Node) []Flat {
|
||||
var entries []Flat
|
||||
if node.Kind != reflect.Map && node.Description != "-" {
|
||||
if !(node.Kind == reflect.Ptr && len(node.Children) > 0) ||
|
||||
(node.Kind == reflect.Ptr && node.Tag.Get("label") == TagLabelAllowEmpty) {
|
||||
if node.Name[0] != '[' {
|
||||
entries = append(entries, Flat{
|
||||
Name: e.getName(name),
|
||||
Description: node.Description,
|
||||
Default: e.getNodeValue(e.getField(field, node), node),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
if node.Kind == reflect.Map {
|
||||
fChild := e.getField(field, child)
|
||||
|
||||
var v string
|
||||
if child.Kind == reflect.Struct {
|
||||
v = defaultPtrValue
|
||||
} else {
|
||||
v = e.getNodeValue(fChild, child)
|
||||
}
|
||||
|
||||
if node.Description != "-" {
|
||||
entries = append(entries, Flat{
|
||||
Name: e.getName(name, child.Name),
|
||||
Description: node.Description,
|
||||
Default: v,
|
||||
})
|
||||
}
|
||||
|
||||
if child.Kind == reflect.Struct || child.Kind == reflect.Ptr {
|
||||
for _, ch := range child.Children {
|
||||
f := e.getField(fChild, ch)
|
||||
n := e.getName(name, child.Name, ch.Name)
|
||||
entries = append(entries, e.createFlat(f, n, ch)...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f := e.getField(field, child)
|
||||
n := e.getName(name, child.Name)
|
||||
entries = append(entries, e.createFlat(f, n, child)...)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func (e encoderToFlat) getField(field reflect.Value, node *Node) reflect.Value {
|
||||
switch field.Kind() {
|
||||
case reflect.Struct:
|
||||
return field.FieldByName(node.FieldName)
|
||||
case reflect.Ptr:
|
||||
if field.Elem().Kind() == reflect.Struct {
|
||||
return field.Elem().FieldByName(node.FieldName)
|
||||
}
|
||||
return field.Elem()
|
||||
case reflect.Map:
|
||||
return field.MapIndex(reflect.ValueOf(node.FieldName))
|
||||
default:
|
||||
return field
|
||||
}
|
||||
}
|
||||
|
||||
func (e encoderToFlat) getNodeValue(field reflect.Value, node *Node) string {
|
||||
if node.Kind == reflect.Ptr && len(node.Children) > 0 {
|
||||
return defaultPtrValue
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Int64 {
|
||||
i, _ := strconv.ParseInt(node.Value, 10, 64)
|
||||
|
||||
switch field.Type() {
|
||||
case reflect.TypeOf(types.Duration(time.Second)):
|
||||
return strconv.Itoa(int(i) / int(time.Second))
|
||||
case reflect.TypeOf(time.Second):
|
||||
return time.Duration(i).String()
|
||||
}
|
||||
}
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
func (e encoderToFlat) getName(names ...string) string {
|
||||
var name string
|
||||
if names[len(names)-1][0] == '[' {
|
||||
name = strings.Join(names, "")
|
||||
} else {
|
||||
name = strings.Join(names, e.Separator)
|
||||
}
|
||||
|
||||
if strings.EqualFold(e.Case, "upper") {
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
}
|
1250
pkg/config/parser/flat_encode_test.go
Normal file
1250
pkg/config/parser/flat_encode_test.go
Normal file
File diff suppressed because it is too large
Load diff
95
pkg/config/parser/labels_decode.go
Normal file
95
pkg/config/parser/labels_decode.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const labelRoot = "traefik"
|
||||
|
||||
// DecodeToNode converts the labels to a tree of nodes.
|
||||
// If any filters are present, labels which do not match the filters are skipped.
|
||||
func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) {
|
||||
sortedKeys := sortKeys(labels, filters)
|
||||
|
||||
var node *Node
|
||||
for i, key := range sortedKeys {
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
if split[0] != labelRoot {
|
||||
return nil, fmt.Errorf("invalid label root %s", split[0])
|
||||
}
|
||||
|
||||
var parts []string
|
||||
for _, v := range split {
|
||||
if v[0] == '[' {
|
||||
return nil, fmt.Errorf("invalid leading character '[' in field name (bracket is a slice delimiter): %s", v)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(v, "]") && v[0] != '[' {
|
||||
indexLeft := strings.Index(v, "[")
|
||||
parts = append(parts, v[:indexLeft], v[indexLeft:])
|
||||
} else {
|
||||
parts = append(parts, v)
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
node = &Node{}
|
||||
}
|
||||
decodeToNode(node, parts, labels[key])
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func decodeToNode(root *Node, path []string, value string) {
|
||||
if len(root.Name) == 0 {
|
||||
root.Name = path[0]
|
||||
}
|
||||
|
||||
// it's a leaf or not -> children
|
||||
if len(path) > 1 {
|
||||
if n := containsNode(root.Children, path[1]); n != nil {
|
||||
// the child already exists
|
||||
decodeToNode(n, path[1:], value)
|
||||
} else {
|
||||
// new child
|
||||
child := &Node{Name: path[1]}
|
||||
decodeToNode(child, path[1:], value)
|
||||
root.Children = append(root.Children, child)
|
||||
}
|
||||
} else {
|
||||
root.Value = value
|
||||
}
|
||||
}
|
||||
|
||||
func containsNode(nodes []*Node, name string) *Node {
|
||||
for _, n := range nodes {
|
||||
if name == n.Name {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortKeys(labels map[string]string, filters []string) []string {
|
||||
var sortedKeys []string
|
||||
for key := range labels {
|
||||
if len(filters) == 0 {
|
||||
sortedKeys = append(sortedKeys, key)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, filter := range filters {
|
||||
if len(key) >= len(filter) && strings.EqualFold(key[:len(filter)], filter) {
|
||||
sortedKeys = append(sortedKeys, key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
return sortedKeys
|
||||
}
|
236
pkg/config/parser/labels_decode_test.go
Normal file
236
pkg/config/parser/labels_decode_test.go
Normal file
|
@ -0,0 +1,236 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeToNode(t *testing.T) {
|
||||
type expected struct {
|
||||
error bool
|
||||
node *Node
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
in map[string]string
|
||||
filters []string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "no label",
|
||||
in: map[string]string{},
|
||||
expected: expected{node: nil},
|
||||
},
|
||||
{
|
||||
desc: "level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 1 empty value",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: ""},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 2",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar": "bar",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{{
|
||||
Name: "foo",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 0",
|
||||
in: map[string]string{
|
||||
"traefik": "bar",
|
||||
"traefic": "bur",
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "several entries, prefix filter",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
"traefik.fii": "bir",
|
||||
},
|
||||
filters: []string{"traefik.Foo"},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
"traefik.fii": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "fii", Value: "bur"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 2",
|
||||
in: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
"traefik.foo.bbb": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 2, 3 children",
|
||||
in: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
"traefik.foo.bbb": "bur",
|
||||
"traefik.foo.ccc": "bir",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 3",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 3, 2 children level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
"traefik.bar.foo.bbb": "bir",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bbb", Value: "bir"},
|
||||
}},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, slice syntax",
|
||||
in: map[string]string{
|
||||
"traefik.foo[0].aaa": "bar0",
|
||||
"traefik.foo[0].bbb": "bur0",
|
||||
"traefik.foo[1].aaa": "bar1",
|
||||
"traefik.foo[1].bbb": "bur1",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar0"},
|
||||
{Name: "bbb", Value: "bur0"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar1"},
|
||||
{Name: "bbb", Value: "bur1"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, invalid slice syntax",
|
||||
in: map[string]string{
|
||||
"traefik.foo.[0].aaa": "bar0",
|
||||
"traefik.foo.[0].bbb": "bur0",
|
||||
"traefik.foo.[1].aaa": "bar1",
|
||||
"traefik.foo.[1].bbb": "bur1",
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out, err := DecodeToNode(test.in, test.filters...)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
if !assert.Equal(t, test.expected.node, out) {
|
||||
bytes, err := json.MarshalIndent(out, "", " ")
|
||||
require.NoError(t, err)
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
30
pkg/config/parser/labels_encode.go
Normal file
30
pkg/config/parser/labels_encode.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package parser
|
||||
|
||||
// EncodeNode Converts a node to labels.
|
||||
// nodes -> labels
|
||||
func EncodeNode(node *Node) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
encodeNode(labels, node.Name, node)
|
||||
return labels
|
||||
}
|
||||
|
||||
func encodeNode(labels map[string]string, root string, node *Node) {
|
||||
for _, child := range node.Children {
|
||||
if child.Disabled {
|
||||
continue
|
||||
}
|
||||
|
||||
var sep string
|
||||
if child.Name[0] != '[' {
|
||||
sep = "."
|
||||
}
|
||||
|
||||
childName := root + sep + child.Name
|
||||
|
||||
if len(child.Children) > 0 {
|
||||
encodeNode(labels, childName, child)
|
||||
} else if len(child.Name) > 0 {
|
||||
labels[childName] = child.Value
|
||||
}
|
||||
}
|
||||
}
|
180
pkg/config/parser/labels_encode_test.go
Normal file
180
pkg/config/parser/labels_encode_test.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeNode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
node *Node
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "1 label",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 labels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
"traefik.bbb": "bur",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 labels, 1 disabled",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur", Disabled: true},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "3 levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 levels, same root",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "several levels, different root",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.bar.ccc": "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple labels, multiple levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
"traefik.bar.ccc": "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct syntax",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar0"},
|
||||
{Name: "bbb", Value: "bur0"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar1"},
|
||||
{Name: "bbb", Value: "bur1"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo[0].aaa": "bar0",
|
||||
"traefik.foo[0].bbb": "bur0",
|
||||
"traefik.foo[1].aaa": "bar1",
|
||||
"traefik.foo[1].bbb": "bur1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
labels := EncodeNode(test.node)
|
||||
|
||||
assert.Equal(t, test.expected, labels)
|
||||
})
|
||||
}
|
||||
}
|
18
pkg/config/parser/node.go
Normal file
18
pkg/config/parser/node.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package parser
|
||||
|
||||
import "reflect"
|
||||
|
||||
// MapNamePlaceholder is the placeholder for the map name.
|
||||
const MapNamePlaceholder = "<name>"
|
||||
|
||||
// Node is a label node.
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
FieldName string `json:"fieldName"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Kind reflect.Kind `json:"kind,omitempty"`
|
||||
Tag reflect.StructTag `json:"tag,omitempty"`
|
||||
Children []*Node `json:"children,omitempty"`
|
||||
}
|
182
pkg/config/parser/nodes_metadata.go
Normal file
182
pkg/config/parser/nodes_metadata.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddMetadata adds metadata such as type, inferred from element, to a node.
|
||||
func AddMetadata(element interface{}, node *Node) error {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
return fmt.Errorf("invalid node %s: no child", node.Name)
|
||||
}
|
||||
|
||||
if element == nil {
|
||||
return errors.New("nil structure")
|
||||
}
|
||||
|
||||
rootType := reflect.TypeOf(element)
|
||||
node.Kind = rootType.Kind()
|
||||
|
||||
return browseChildren(rootType, node)
|
||||
}
|
||||
|
||||
func browseChildren(fType reflect.Type, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
if err := addMetadata(fType, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addMetadata(rootType reflect.Type, node *Node) error {
|
||||
rType := rootType
|
||||
if rootType.Kind() == reflect.Ptr {
|
||||
rType = rootType.Elem()
|
||||
}
|
||||
|
||||
field, err := findTypedField(rType, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = isSupportedType(field); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fType := field.Type
|
||||
node.Kind = fType.Kind()
|
||||
node.Tag = field.Tag
|
||||
|
||||
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct ||
|
||||
fType.Kind() == reflect.Map {
|
||||
if len(node.Children) == 0 && field.Tag.Get(TagLabel) != TagLabelAllowEmpty {
|
||||
return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType)
|
||||
}
|
||||
|
||||
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(TagLabel) == TagLabelAllowEmpty
|
||||
}
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
|
||||
return browseChildren(fType, node)
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Map {
|
||||
for _, child := range node.Children {
|
||||
// elem is a map entry value type
|
||||
elem := fType.Elem()
|
||||
child.Kind = elem.Kind()
|
||||
|
||||
if elem.Kind() == reflect.Map || elem.Kind() == reflect.Struct ||
|
||||
(elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
|
||||
if err = browseChildren(elem, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Slice {
|
||||
if field.Tag.Get(TagLabelSliceAsStruct) != "" {
|
||||
return browseChildren(fType.Elem(), node)
|
||||
}
|
||||
|
||||
for _, ch := range node.Children {
|
||||
ch.Kind = fType.Elem().Kind()
|
||||
if err = browseChildren(fType.Elem(), ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
|
||||
}
|
||||
|
||||
func findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
|
||||
for i := 0; i < rType.NumField(); i++ {
|
||||
cField := rType.Field(i)
|
||||
|
||||
fieldName := cField.Tag.Get(TagLabelSliceAsStruct)
|
||||
if len(fieldName) == 0 {
|
||||
fieldName = cField.Name
|
||||
}
|
||||
|
||||
if IsExported(cField) {
|
||||
if cField.Anonymous {
|
||||
if cField.Type.Kind() == reflect.Struct {
|
||||
structField, err := findTypedField(cField.Type, node)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return structField, nil
|
||||
}
|
||||
}
|
||||
|
||||
if strings.EqualFold(fieldName, node.Name) {
|
||||
node.FieldName = cField.Name
|
||||
return cField, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return reflect.StructField{}, fmt.Errorf("field not found, node: %s", node.Name)
|
||||
}
|
||||
|
||||
// IsExported reports whether f is exported.
|
||||
// https://golang.org/pkg/reflect/#StructField
|
||||
func IsExported(f reflect.StructField) bool {
|
||||
return f.PkgPath == ""
|
||||
}
|
||||
|
||||
func isSupportedType(field reflect.StructField) error {
|
||||
fType := field.Type
|
||||
|
||||
if fType.Kind() == reflect.Slice {
|
||||
switch fType.Elem().Kind() {
|
||||
case reflect.String,
|
||||
reflect.Bool,
|
||||
reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Struct,
|
||||
reflect.Ptr:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported slice type: %v", fType)
|
||||
}
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Map && fType.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("unsupported map key type: %v", fType.Key())
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Func {
|
||||
return fmt.Errorf("unsupported type: %v", fType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1011
pkg/config/parser/nodes_metadata_test.go
Normal file
1011
pkg/config/parser/nodes_metadata_test.go
Normal file
File diff suppressed because it is too large
Load diff
38
pkg/config/parser/parser.go
Normal file
38
pkg/config/parser/parser.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Package parser implements decoding and encoding between a flat map of labels and a typed Configuration.
|
||||
package parser
|
||||
|
||||
// Decode decodes the given map of labels into the given element.
|
||||
// If any filters are present, labels which do not match the filters are skipped.
|
||||
// The operation goes through three stages roughly summarized as:
|
||||
// labels -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(labels map[string]string, element interface{}, filters ...string) error {
|
||||
node, err := DecodeToNode(labels, filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = Fill(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode converts an element to labels.
|
||||
// element -> node (value) -> label (node)
|
||||
func Encode(element interface{}) (map[string]string, error) {
|
||||
node, err := EncodeToNode(element, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return EncodeNode(node), nil
|
||||
}
|
18
pkg/config/parser/tags.go
Normal file
18
pkg/config/parser/tags.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package parser
|
||||
|
||||
const (
|
||||
// TagLabel allows to apply a custom behavior.
|
||||
// - "allowEmpty": allows to create an empty struct.
|
||||
// - "-": ignore the field.
|
||||
TagLabel = "label"
|
||||
|
||||
// TagLabelSliceAsStruct allows to use a slice of struct by creating one entry into the slice.
|
||||
// The value is the substitution name used in the label to access the slice.
|
||||
TagLabelSliceAsStruct = "label-slice-as-struct"
|
||||
|
||||
// TagDescription is the documentation for the field.
|
||||
TagDescription = "description"
|
||||
|
||||
// TagLabelAllowEmpty is related to TagLabel.
|
||||
TagLabelAllowEmpty = "allowEmpty"
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue