feat(config): add RegisterChannelSettings hook for out-of-tree channels

Expose a public registration hook so external packages that register a channel
factory via channels.RegisterFactory can also register the channel's settings
struct prototype. Without this, InitChannelList rejects any channel type absent
from the private channelSettingsFactory map, making out-of-tree channels
impossible without forking. The map access is now guarded by a RWMutex.

This is additive and changes no existing behavior.
This commit is contained in:
Carlos Prados
2026-06-14 11:52:32 +02:00
parent cf67dd3851
commit e55ddf2e24
+19
View File
@@ -7,6 +7,7 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"github.com/caarlos0/env/v11"
"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
// corresponding Settings struct. InitChannelList uses reflect.New to create
// fresh instances, avoiding repeated closure boilerplate.
var channelSettingsMu sync.RWMutex
var channelSettingsFactory = map[string]any{
ChannelPico: (PicoSettings{}),
ChannelPicoClient: (PicoClientSettings{}),
@@ -679,10 +682,24 @@ var channelSettingsFactory = map[string]any{
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.
// Returns nil if the type is not registered.
func newChannelSettings(channelType string) any {
channelSettingsMu.RLock()
proto, ok := channelSettingsFactory[channelType]
channelSettingsMu.RUnlock()
if !ok {
return nil
}
@@ -691,7 +708,9 @@ func newChannelSettings(channelType string) any {
// isValidChannelType returns true if the channel type is a known, registered type.
func isValidChannelType(channelType string) bool {
channelSettingsMu.RLock()
_, ok := channelSettingsFactory[channelType]
channelSettingsMu.RUnlock()
return ok
}