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
44
pkg/config/flag/flag.go
Normal file
44
pkg/config/flag/flag.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Package flag implements encoding and decoding between flag arguments and a typed Configuration.
|
||||
package flag
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// Decode decodes the given flag arguments into the given element.
|
||||
// The operation goes through four stages roughly summarized as:
|
||||
// flag arguments -> parsed map of flags
|
||||
// map -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(args []string, element interface{}) error {
|
||||
ref, err := Parse(args, element)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parser.Decode(ref, element)
|
||||
}
|
||||
|
||||
// Encode encodes the configuration in element into the flags represented in the returned Flats.
|
||||
// The operation goes through three stages roughly summarized as:
|
||||
// typed configuration in element -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> flags with default values (determined by type/kind)
|
||||
func Encode(element interface{}) ([]parser.Flat, error) {
|
||||
if element == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parser.AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parser.EncodeToFlat(element, node, parser.FlatOpts{Separator: ".", SkipRoot: true})
|
||||
}
|
926
pkg/config/flag/flag_test.go
Normal file
926
pkg/config/flag/flag_test.go
Normal file
|
@ -0,0 +1,926 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/generator"
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
desc: "no args",
|
||||
args: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "types.Duration value",
|
||||
args: []string{"--foo=1"},
|
||||
element: &struct {
|
||||
Foo types.Duration
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo types.Duration
|
||||
}{
|
||||
Foo: types.Duration(1 * time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time.Duration value",
|
||||
args: []string{"--foo=1"},
|
||||
element: &struct {
|
||||
Foo time.Duration
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo time.Duration
|
||||
}{
|
||||
Foo: 1 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo bool
|
||||
}{
|
||||
Foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--foo=bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated",
|
||||
args: []string{"--foo", "bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated with end of parameter",
|
||||
args: []string{"--foo=bir", "--", "--bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple bool flags without value",
|
||||
args: []string{"--foo", "--bar"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{
|
||||
Foo: true,
|
||||
Bar: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags",
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
}{
|
||||
Foo: []string{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
args: []string{"--foo.name=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]string
|
||||
}{
|
||||
Foo: map[string]string{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
args: []string{"--foo.name.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{
|
||||
Foo: map[string]struct{ Value string }{
|
||||
"name": {
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-struct",
|
||||
args: []string{"--foo.name.bar.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{
|
||||
Foo: map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}{
|
||||
"name": {
|
||||
Bar: &struct {
|
||||
Value string
|
||||
}{
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-map",
|
||||
args: []string{"--foo.name1.bar.name2.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{
|
||||
Foo: map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}{
|
||||
"name1": {
|
||||
Bar: map[string]struct{ Value string }{
|
||||
"name2": {
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 2",
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
}{
|
||||
Foo: []string{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 3",
|
||||
args: []string{"--foo", "bar", "--foo=", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{
|
||||
Foo: []string{"bar", ""},
|
||||
Baz: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 4",
|
||||
args: []string{"--foo", "bar", "--foo", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{
|
||||
Foo: []string{"bar", "--baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct",
|
||||
args: []string{
|
||||
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
|
||||
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
|
||||
},
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{
|
||||
Foo: []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}{
|
||||
{
|
||||
Field1: "bar",
|
||||
Field2: 6,
|
||||
},
|
||||
{
|
||||
Field1: "bur",
|
||||
Field2: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of pointer of struct",
|
||||
args: []string{
|
||||
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
|
||||
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
|
||||
},
|
||||
element: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{
|
||||
Foo: []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}{
|
||||
{
|
||||
Field1: "bar",
|
||||
Field2: 6,
|
||||
},
|
||||
{
|
||||
Field1: "bur",
|
||||
Field2: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag 2",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo", "--bar"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{
|
||||
Foo: "--bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo *struct{ Field string } `label:"allowEmpty"`
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo *struct{ Field string } `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct{ Field string }{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := Decode(test.args, test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, test.element)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected []parser.Flat
|
||||
}{
|
||||
{
|
||||
desc: "string field",
|
||||
element: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "int field",
|
||||
element: &struct {
|
||||
Field int `description:"field description"`
|
||||
}{
|
||||
Field: 6,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "bool field",
|
||||
element: &struct {
|
||||
Field bool `description:"field description"`
|
||||
}{
|
||||
Field: true,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "true",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "string pointer field",
|
||||
element: &struct {
|
||||
Field *string `description:"field description"`
|
||||
}{
|
||||
Field: func(v string) *string { return &v }("test"),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "int pointer field",
|
||||
element: &struct {
|
||||
Field *int `description:"field description"`
|
||||
}{
|
||||
Field: func(v int) *int { return &v }(6),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "bool pointer field",
|
||||
element: &struct {
|
||||
Field *bool `description:"field description"`
|
||||
}{
|
||||
Field: func(v bool) *bool { return &v }(true),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "true",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of string field, no initial value",
|
||||
element: &struct {
|
||||
Field []string `description:"field description"`
|
||||
}{},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of string field, with initial value",
|
||||
element: &struct {
|
||||
Field []string `description:"field description"`
|
||||
}{
|
||||
Field: []string{"foo", "bar"},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "foo, bar",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of int field, no initial value",
|
||||
element: &struct {
|
||||
Field []int `description:"field description"`
|
||||
}{},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of int field, with initial value",
|
||||
element: &struct {
|
||||
Field []int `description:"field description"`
|
||||
}{
|
||||
Field: []int{6, 3},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6, 3",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "map string field",
|
||||
element: &struct {
|
||||
Field map[string]string `description:"field description"`
|
||||
}{
|
||||
Field: map[string]string{
|
||||
parser.MapNamePlaceholder: "",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field.<name>",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field, allow empty",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"foo description" label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description"`
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field level 2, allow empty",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description" label:"allowEmpty"`
|
||||
} `description:"foo description" label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description" label:"allowEmpty"`
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii",
|
||||
Description: "fii description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii map[string]string `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii map[string]string `description:"fii description"`
|
||||
}{
|
||||
Fii: map[string]string{
|
||||
parser.MapNamePlaceholder: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.<name>",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string pointer field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii map[string]*string `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii map[string]*string `description:"fii description"`
|
||||
}{
|
||||
Fii: map[string]*string{
|
||||
parser.MapNamePlaceholder: func(v string) *string { return &v }(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.<name>",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct level 1",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Field string `description:"field description"`
|
||||
Yo int `description:"yo description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.yo",
|
||||
Description: "yo description",
|
||||
Default: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct pointer level 1",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Field string `description:"field description"`
|
||||
Yo string `description:"yo description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.yo",
|
||||
Description: "yo description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field",
|
||||
element: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field map",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: map[string]*struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "0s",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field map 2",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: map[string]*struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.fii.field",
|
||||
Description: "field description",
|
||||
Default: "0s",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field 3",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field",
|
||||
element: &struct {
|
||||
Field types.Duration `description:"field description"`
|
||||
}{
|
||||
Field: types.Duration(180 * time.Second),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "180",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii []struct {
|
||||
Field1 string `description:"field1 description"`
|
||||
Field2 int `description:"field2 description"`
|
||||
} `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii[0].field1",
|
||||
Description: "field1 description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii[0].field2",
|
||||
Description: "field2 description",
|
||||
Default: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Skipped: because realistically not needed in Traefik for now.
|
||||
// {
|
||||
// desc: "map of map field level 2",
|
||||
// element: &struct {
|
||||
// Foo *struct {
|
||||
// Fii map[string]map[string]string `description:"fii description"`
|
||||
// } `description:"foo description"`
|
||||
// }{
|
||||
// Foo: &struct {
|
||||
// Fii map[string]map[string]string `description:"fii description"`
|
||||
// }{
|
||||
// Fii: map[string]map[string]string{
|
||||
// parser.MapNamePlaceholder: {
|
||||
// parser.MapNamePlaceholder: "test",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: `XXX`,
|
||||
// },
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
generator.Generate(test.element)
|
||||
|
||||
entries, err := Encode(test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, entries)
|
||||
})
|
||||
}
|
||||
}
|
108
pkg/config/flag/flagparser.go
Normal file
108
pkg/config/flag/flagparser.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse parses the command-line flag arguments into a map,
|
||||
// using the type information in element to discriminate whether a flag is supposed to be a bool,
|
||||
// and other such ambiguities.
|
||||
func Parse(args []string, element interface{}) (map[string]string, error) {
|
||||
f := flagSet{
|
||||
flagTypes: getFlagTypes(element),
|
||||
args: args,
|
||||
values: make(map[string]string),
|
||||
}
|
||||
|
||||
for {
|
||||
seen, err := f.parseOne()
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return f.values, nil
|
||||
}
|
||||
|
||||
type flagSet struct {
|
||||
flagTypes map[string]reflect.Kind
|
||||
args []string
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func (f *flagSet) parseOne() (bool, error) {
|
||||
if len(f.args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s := f.args[0]
|
||||
if len(s) < 2 || s[0] != '-' {
|
||||
return false, nil
|
||||
}
|
||||
numMinuses := 1
|
||||
if s[1] == '-' {
|
||||
numMinuses++
|
||||
if len(s) == 2 { // "--" terminates the flags
|
||||
f.args = f.args[1:]
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
name := s[numMinuses:]
|
||||
if len(name) == 0 || name[0] == '-' || name[0] == '=' {
|
||||
return false, fmt.Errorf("bad flag syntax: %s", s)
|
||||
}
|
||||
|
||||
// it's a flag. does it have an argument?
|
||||
f.args = f.args[1:]
|
||||
hasValue := false
|
||||
value := ""
|
||||
for i := 1; i < len(name); i++ { // equals cannot be first
|
||||
if name[i] == '=' {
|
||||
value = name[i+1:]
|
||||
hasValue = true
|
||||
name = name[0:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasValue {
|
||||
f.setValue(name, value)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.flagTypes[name] == reflect.Bool || f.flagTypes[name] == reflect.Ptr {
|
||||
f.setValue(name, "true")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(f.args) > 0 {
|
||||
// value is the next arg
|
||||
hasValue = true
|
||||
value, f.args = f.args[0], f.args[1:]
|
||||
}
|
||||
|
||||
if !hasValue {
|
||||
return false, fmt.Errorf("flag needs an argument: -%s", name)
|
||||
}
|
||||
|
||||
f.setValue(name, value)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *flagSet) setValue(name string, value string) {
|
||||
n := strings.ToLower("traefik." + name)
|
||||
v, ok := f.values[n]
|
||||
|
||||
if ok && f.flagTypes[name] == reflect.Slice {
|
||||
f.values[n] = v + "," + value
|
||||
return
|
||||
}
|
||||
|
||||
f.values[n] = value
|
||||
}
|
255
pkg/config/flag/flagparser_test.go
Normal file
255
pkg/config/flag/flagparser_test.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "no args",
|
||||
args: nil,
|
||||
expected: map[string]string{},
|
||||
},
|
||||
{
|
||||
desc: "bool value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--foo=bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated",
|
||||
args: []string{"--foo", "bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated with end of parameter",
|
||||
args: []string{"--foo=bir", "--", "--bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple bool flags without value",
|
||||
args: []string{"--foo", "--bar"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
"traefik.bar": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags",
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
args: []string{"--foo.name=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
args: []string{"--foo.name.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-struct",
|
||||
args: []string{"--foo.name.bar.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name.bar.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-map",
|
||||
args: []string{"--foo.name1.bar.name2.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name1.bar.name2.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 2",
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 3",
|
||||
args: []string{"--foo", "bar", "--foo=", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,",
|
||||
"traefik.baz": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 4",
|
||||
args: []string{"--foo", "bar", "--foo", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,--baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag 2",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo", "--bar"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "--bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo *struct{ Field string }
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fl, err := Parse(test.args, test.element)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, fl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
}{
|
||||
{
|
||||
desc: "triple hyphen",
|
||||
args: []string{"---foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--=foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := Parse(test.args, test.element)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
60
pkg/config/flag/flagtype.go
Normal file
60
pkg/config/flag/flagtype.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
func getFlagTypes(element interface{}) map[string]reflect.Kind {
|
||||
ref := map[string]reflect.Kind{}
|
||||
|
||||
if element == nil {
|
||||
return ref
|
||||
}
|
||||
|
||||
tp := reflect.TypeOf(element).Elem()
|
||||
|
||||
addFlagType(ref, "", tp)
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func addFlagType(ref map[string]reflect.Kind, name string, typ reflect.Type) {
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool, reflect.Slice:
|
||||
ref[name] = typ.Kind()
|
||||
|
||||
case reflect.Map:
|
||||
addFlagType(ref, getName(name, parser.MapNamePlaceholder), typ.Elem())
|
||||
|
||||
case reflect.Ptr:
|
||||
if typ.Elem().Kind() == reflect.Struct {
|
||||
ref[name] = typ.Kind()
|
||||
}
|
||||
addFlagType(ref, name, typ.Elem())
|
||||
|
||||
case reflect.Struct:
|
||||
for j := 0; j < typ.NumField(); j++ {
|
||||
subField := typ.Field(j)
|
||||
|
||||
if !parser.IsExported(subField) {
|
||||
continue
|
||||
}
|
||||
|
||||
if subField.Anonymous {
|
||||
addFlagType(ref, getName(name), subField.Type)
|
||||
} else {
|
||||
addFlagType(ref, getName(name, subField.Name), subField.Type)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
func getName(names ...string) string {
|
||||
return strings.TrimPrefix(strings.ToLower(strings.Join(names, ".")), ".")
|
||||
}
|
226
pkg/config/flag/flagtype_test.go
Normal file
226
pkg/config/flag/flagtype_test.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_getFlagTypes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected map[string]reflect.Kind
|
||||
}{
|
||||
{
|
||||
desc: "nil",
|
||||
element: nil,
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "no fields",
|
||||
element: &struct {
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "string field",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 0",
|
||||
element: &struct {
|
||||
Foo bool
|
||||
fii bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 1",
|
||||
element: &struct {
|
||||
Foo struct {
|
||||
Field bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field bool
|
||||
}
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
"foo.fii": reflect.Ptr,
|
||||
"foo.fii.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer field",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 3",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Fuu *struct {
|
||||
Field bool
|
||||
}
|
||||
}
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
"foo.fii": reflect.Ptr,
|
||||
"foo.fii.fuu": reflect.Ptr,
|
||||
"foo.fii.fuu.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "map bool",
|
||||
element: &struct {
|
||||
Foo map[string]bool
|
||||
Fii struct{}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Field bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + ".field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map map bool",
|
||||
element: &struct {
|
||||
Foo map[string]map[string]bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + "." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct map",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Fii map[string]bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + ".fii." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer bool field level 0",
|
||||
element: &struct {
|
||||
Foo *bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer int field level 0",
|
||||
element: &struct {
|
||||
Foo *int
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "bool slice field level 0",
|
||||
element: &struct {
|
||||
Foo []bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string slice field level 0",
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice field level 1",
|
||||
element: &struct {
|
||||
Foo struct {
|
||||
Field []string
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo.field": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map slice string",
|
||||
element: &struct {
|
||||
Foo map[string][]string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder: reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct",
|
||||
element: &struct {
|
||||
Yo
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getFlagTypes(test.element)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Yo struct {
|
||||
Foo bool
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue