Skip to content

Commit

Permalink
feat: support optionally passing kong.Context to Validate()
Browse files Browse the repository at this point in the history
Fixes #340
  • Loading branch information
alecthomas committed Nov 3, 2024
1 parent 64229c9 commit 1b9d57e
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 4 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,15 +663,20 @@ func main() {
## Validation

Kong does validation on the structure of a command-line, but also supports
extensible validation. Any node in the tree may implement the following
interface:
extensible validation. Any node in the tree may implement either of the following interfaces:

```go
type Validatable interface {
Validate() error
}
```

```go
type Validatable interface {
Validate(kctx *kong.Context) error
}
```

If one of these nodes is in the active command-line it will be called during
normal validation.

Expand Down
15 changes: 13 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (c *Context) Validate() error { //nolint: gocyclo
desc = node.Path()
}
if validate := isValidatable(value); validate != nil {
if err := validate.Validate(); err != nil {
if err := validate.Validate(c); err != nil {
if desc != "" {
return fmt.Errorf("%s: %w", desc, err)
}
Expand Down Expand Up @@ -1094,12 +1094,23 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
}

type validatable interface{ Validate() error }
type extendedValidatable interface {
Validate(kctx *Context) error
}

// Proxy a validatable function to the extendedValidatable interface
type validatableFunc func() error

func isValidatable(v reflect.Value) validatable {
func (f validatableFunc) Validate(kctx *Context) error { return f() }

func isValidatable(v reflect.Value) extendedValidatable {
if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
return nil
}
if validate, ok := v.Interface().(validatable); ok {
return validatableFunc(validate.Validate)
}
if validate, ok := v.Interface().(extendedValidatable); ok {
return validate
}
if v.CanAddr() {
Expand Down
13 changes: 13 additions & 0 deletions kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,19 @@ func TestValidateArg(t *testing.T) {
assert.EqualError(t, err, "<arg>: flag error")
}

type extendedValidateFlag string

func (v *extendedValidateFlag) Validate(kctx *kong.Context) error { return errors.New("flag error") }

func TestExtendedValidateFlag(t *testing.T) {
cli := struct {
Flag extendedValidateFlag
}{}
p := mustNew(t, &cli)
_, err := p.Parse([]string{"--flag=one"})
assert.EqualError(t, err, "--flag: flag error")
}

func TestPointers(t *testing.T) {
cli := struct {
Mapped *mappedValue
Expand Down

0 comments on commit 1b9d57e

Please sign in to comment.