Merge pull request #3120 from carlosprados/feat/register-channel-settings
feat(config): add RegisterChannelSettings hook for out-of-tree channels
This commit is contained in:
commit
910bfbe6a4
2 changed files with 73 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
@ -655,6 +656,8 @@ func filterSecureFields(r RawNode, secureFields map[string]struct{}) RawNode {
|
||||||
// channelSettingsFactory maps channel type to a zero-value prototype of the
|
// channelSettingsFactory maps channel type to a zero-value prototype of the
|
||||||
// corresponding Settings struct. InitChannelList uses reflect.New to create
|
// corresponding Settings struct. InitChannelList uses reflect.New to create
|
||||||
// fresh instances, avoiding repeated closure boilerplate.
|
// fresh instances, avoiding repeated closure boilerplate.
|
||||||
|
var channelSettingsMu sync.RWMutex
|
||||||
|
|
||||||
var channelSettingsFactory = map[string]any{
|
var channelSettingsFactory = map[string]any{
|
||||||
ChannelPico: (PicoSettings{}),
|
ChannelPico: (PicoSettings{}),
|
||||||
ChannelPicoClient: (PicoClientSettings{}),
|
ChannelPicoClient: (PicoClientSettings{}),
|
||||||
|
|
@ -679,10 +682,24 @@ var channelSettingsFactory = map[string]any{
|
||||||
ChannelSlackWebHook: (SlackWebhookSettings{}),
|
ChannelSlackWebHook: (SlackWebhookSettings{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterChannelSettings registers a settings struct prototype for a custom
|
||||||
|
// channel type. External packages (out-of-tree channels registered via
|
||||||
|
// channels.RegisterFactory) call this from an init() so their channel type
|
||||||
|
// passes config validation (isValidChannelType) and its settings block decodes
|
||||||
|
// into the right struct (newChannelSettings). The prototype must be a struct
|
||||||
|
// value, e.g. RegisterChannelSettings("my_channel", MyChannelSettings{}).
|
||||||
|
func RegisterChannelSettings(channelType string, prototype any) {
|
||||||
|
channelSettingsMu.Lock()
|
||||||
|
defer channelSettingsMu.Unlock()
|
||||||
|
channelSettingsFactory[channelType] = prototype
|
||||||
|
}
|
||||||
|
|
||||||
// newChannelSettings creates a fresh zero-value pointer for the given channel type.
|
// newChannelSettings creates a fresh zero-value pointer for the given channel type.
|
||||||
// Returns nil if the type is not registered.
|
// Returns nil if the type is not registered.
|
||||||
func newChannelSettings(channelType string) any {
|
func newChannelSettings(channelType string) any {
|
||||||
|
channelSettingsMu.RLock()
|
||||||
proto, ok := channelSettingsFactory[channelType]
|
proto, ok := channelSettingsFactory[channelType]
|
||||||
|
channelSettingsMu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -691,7 +708,9 @@ func newChannelSettings(channelType string) any {
|
||||||
|
|
||||||
// isValidChannelType returns true if the channel type is a known, registered type.
|
// isValidChannelType returns true if the channel type is a known, registered type.
|
||||||
func isValidChannelType(channelType string) bool {
|
func isValidChannelType(channelType string) bool {
|
||||||
|
channelSettingsMu.RLock()
|
||||||
_, ok := channelSettingsFactory[channelType]
|
_, ok := channelSettingsFactory[channelType]
|
||||||
|
channelSettingsMu.RUnlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
54
pkg/config/register_channel_settings_test.go
Normal file
54
pkg/config/register_channel_settings_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// customChannelSettings stands in for an out-of-tree channel's settings struct.
|
||||||
|
type customChannelSettings struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRegisterChannelSettings verifies the public registration hook makes a
|
||||||
|
// previously-unknown channel type valid and decodable — the behavior out-of-tree
|
||||||
|
// channels rely on (they call RegisterChannelSettings from init()).
|
||||||
|
func TestRegisterChannelSettings(t *testing.T) {
|
||||||
|
const typ = "custom_test_channel"
|
||||||
|
|
||||||
|
assert.False(t, isValidChannelType(typ), "type should be unknown before registration")
|
||||||
|
|
||||||
|
RegisterChannelSettings(typ, customChannelSettings{})
|
||||||
|
|
||||||
|
assert.True(t, isValidChannelType(typ), "type should be valid after registration")
|
||||||
|
|
||||||
|
got := newChannelSettings(typ)
|
||||||
|
_, ok := got.(*customChannelSettings)
|
||||||
|
assert.Truef(t, ok, "newChannelSettings(%q) = %T, want *customChannelSettings", typ, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRegisterChannelSettings_InitChannelList verifies that a config carrying a
|
||||||
|
// registered out-of-tree channel type passes InitChannelList and decodes its
|
||||||
|
// settings — the full path that previously errored with "unknown type".
|
||||||
|
func TestRegisterChannelSettings_InitChannelList(t *testing.T) {
|
||||||
|
const typ = "custom_initlist_channel"
|
||||||
|
RegisterChannelSettings(typ, customChannelSettings{})
|
||||||
|
|
||||||
|
channels := ChannelsConfig{
|
||||||
|
"mychan": {
|
||||||
|
Type: typ,
|
||||||
|
Enabled: true,
|
||||||
|
Settings: RawNode(`{"token":"secret-123"}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, InitChannelList(channels))
|
||||||
|
|
||||||
|
decoded, err := channels["mychan"].GetDecoded()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cfg, ok := decoded.(*customChannelSettings)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "secret-123", cfg.Token)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue