Skip to content

Commit

Permalink
Support hooks on embed:"" fields (#493)
Browse files Browse the repository at this point in the history
Relates to 840220c (#90)

This change adds support for hooks to be called on fields
that are tagged with `embed:""`.

### Use case

If a command has several subcommands,
many (but not all) of which need the same external resource,
this allows defining the flag-level inputs for that resource centrally,
and then using `embed:""` in any command that needs that resource.

For example, imagine:

```go
type githubClientProvider struct {
    Token string `name:"github-token" env:"GITHUB_TOKEN"`
    URL   string `name:"github-url" env:"GITHUB_URL"`
}

func (g *githubClientProvider) BeforeApply(kctx *kong.Context) error {
  return kctx.BindToProvider(func() (*github.Client, error) {
    return github.NewClient(...), nil
  })
}
```

Then, any command that needs GitHub client will add this field,
any other resource providers it needs,
and add parameters to its `Run` method to accept those resources:

```go
type listUsersCmd struct {
    GitHub githubClientProvider `embed:""`
    S3     s3ClientProvider     `embed:""`
}

func (l *listUsersCmd) Run(gh *github.Client, s3 *s3.Client) error {
    ...
}
```

### Alternatives

It is possible to do the same today if the `*Provider` struct above
is actually a Go embed instead of a Kong embed, *and* it is exported.

```
type GitHubClientProvider struct{ ... }

type listUsersCmd struct {
    GithubClientProvider
    S3ClientProvider
}
```

The difference is whether the struct defining the flags
is required to be exported or not.
  • Loading branch information
abhinav authored Jan 29, 2025
1 parent 042a325 commit 9c08a58
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
14 changes: 13 additions & 1 deletion callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,19 @@ func getMethods(value reflect.Value, name string) []reflect.Value {
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := t.Field(i)
if fieldType.IsExported() && fieldType.Anonymous {
if !fieldType.IsExported() {
continue
}

// Hooks on exported embedded fields should be called.
if fieldType.Anonymous {
receivers = append(receivers, field)
continue
}

// Hooks on exported fields that are not exported,
// but are tagged with `embed:""` should be called.
if _, ok := fieldType.Tag.Lookup("embed"); ok {
receivers = append(receivers, field)
}
}
Expand Down
15 changes: 14 additions & 1 deletion kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2413,9 +2413,19 @@ func (e *EmbeddedCallback) AfterApply() error {
return nil
}

type taggedEmbeddedCallback struct {
Tagged bool
}

func (e *taggedEmbeddedCallback) AfterApply() error {
e.Tagged = true
return nil
}

type EmbeddedRoot struct {
EmbeddedCallback
Root bool
Tagged taggedEmbeddedCallback `embed:""`
Root bool
}

func (e *EmbeddedRoot) AfterApply() error {
Expand All @@ -2432,6 +2442,9 @@ func TestEmbeddedCallbacks(t *testing.T) {
EmbeddedCallback: EmbeddedCallback{
Embedded: true,
},
Tagged: taggedEmbeddedCallback{
Tagged: true,
},
Root: true,
}
assert.Equal(t, expected, actual)
Expand Down

0 comments on commit 9c08a58

Please sign in to comment.