diff --git a/build.go b/build.go index 0b3e4ab..f3838a7 100644 --- a/build.go +++ b/build.go @@ -167,14 +167,13 @@ MAIN: } // Scan through argument positionals to ensure optional is never before a required. - last := true - for i, p := range node.Positional { - if !last && p.Required { - return nil, fmt.Errorf("argument %q can not be required after an optional", p.Name) + var last *Value + for i, curr := range node.Positional { + if last != nil && !last.Required && curr.Required { + return nil, fmt.Errorf("%s: required %q can not come after optional %q", node.FullPath(), curr.Name, last.Name) } - - last = p.Required - p.Position = i + last = curr + curr.Position = i } return node, nil diff --git a/kong_test.go b/kong_test.go index 4c9d5fb..f0844ba 100644 --- a/kong_test.go +++ b/kong_test.go @@ -1382,3 +1382,19 @@ func TestOptionReturnsErr(t *testing.T) { require.Error(t, err) require.Equal(t, "option returned err", err.Error()) } + +func TestEnumValidation(t *testing.T) { + var cli struct { + Enum string `arg:"" enum:"one,two"` + } + _, err := kong.New(&cli) + require.Error(t, err) +} + +func TestEnumValidationSlice(t *testing.T) { + var cli struct { + Enum []string `arg:"" enum:"one,two"` + } + _, err := kong.New(&cli) + require.NoError(t, err) +} diff --git a/tag.go b/tag.go index d76c418..a817c88 100644 --- a/tag.go +++ b/tag.go @@ -138,7 +138,7 @@ func parseTagString(s string) (*Tag, error) { t := &Tag{ items: items, } - err = hydrateTag(t, "", false) + err = hydrateTag(t, nil) if err != nil { return nil, fmt.Errorf("%s: %s", s, err) } @@ -158,14 +158,20 @@ func parseTag(parent reflect.Value, ft reflect.StructField) (*Tag, error) { t := &Tag{ items: items, } - err = hydrateTag(t, ft.Type.Name(), ft.Type.Kind() == reflect.Bool) + err = hydrateTag(t, ft.Type) if err != nil { return nil, failField(parent, ft, "%s", err) } return t, nil } -func hydrateTag(t *Tag, typeName string, isBool bool) error { +func hydrateTag(t *Tag, typ reflect.Type) error { // nolint: gocyclo + var typeName string + var isBool bool + if typ != nil { + typeName = typ.Name() + isBool = typ.Kind() == reflect.Bool + } var err error t.Cmd = t.Has("cmd") t.Arg = t.Has("arg") @@ -220,7 +226,8 @@ func hydrateTag(t *Tag, typeName string, isBool bool) error { } t.PlaceHolder = t.Get("placeholder") t.Enum = t.Get("enum") - if t.Enum != "" && !(t.Required || t.Default != "") { + scalarType := (typ == nil || !(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map)) + if t.Enum != "" && !(t.Required || t.Default != "") && scalarType { return fmt.Errorf("enum value is only valid if it is either required or has a valid default value") } passthrough := t.Has("passthrough")