traefik/pkg/config/parser/element_nodes.go
Ludovic Fernandez 8d7eccad5d New static configuration loading system.
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
2019-06-17 11:48:05 +02:00

203 lines
5 KiB
Go

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
}