feat: support optionally passing kong.Context to Validate()

Fixes #340
This commit is contained in:
Alec Thomas
2024-11-03 14:14:55 +11:00
parent 64229c9fe7
commit 1b9d57eec1
3 changed files with 33 additions and 4 deletions
+7 -2
View File
@@ -663,8 +663,7 @@ func main() {
## Validation ## Validation
Kong does validation on the structure of a command-line, but also supports Kong does validation on the structure of a command-line, but also supports
extensible validation. Any node in the tree may implement the following extensible validation. Any node in the tree may implement either of the following interfaces:
interface:
```go ```go
type Validatable interface { type Validatable interface {
@@ -672,6 +671,12 @@ type Validatable interface {
} }
``` ```
```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 If one of these nodes is in the active command-line it will be called during
normal validation. normal validation.
+13 -2
View File
@@ -208,7 +208,7 @@ func (c *Context) Validate() error { //nolint: gocyclo
desc = node.Path() desc = node.Path()
} }
if validate := isValidatable(value); validate != nil { if validate := isValidatable(value); validate != nil {
if err := validate.Validate(); err != nil { if err := validate.Validate(c); err != nil {
if desc != "" { if desc != "" {
return fmt.Errorf("%s: %w", desc, err) return fmt.Errorf("%s: %w", desc, err)
} }
@@ -1094,12 +1094,23 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
} }
type validatable interface{ Validate() error } type validatable interface{ Validate() error }
type extendedValidatable interface {
Validate(kctx *Context) error
}
func isValidatable(v reflect.Value) validatable { // Proxy a validatable function to the extendedValidatable interface
type validatableFunc func() error
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() { if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
return nil return nil
} }
if validate, ok := v.Interface().(validatable); ok { if validate, ok := v.Interface().(validatable); ok {
return validatableFunc(validate.Validate)
}
if validate, ok := v.Interface().(extendedValidatable); ok {
return validate return validate
} }
if v.CanAddr() { if v.CanAddr() {
+13
View File
@@ -1466,6 +1466,19 @@ func TestValidateArg(t *testing.T) {
assert.EqualError(t, err, "<arg>: flag error") 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) { func TestPointers(t *testing.T) {
cli := struct { cli := struct {
Mapped *mappedValue Mapped *mappedValue