diff --git a/config.go b/config.go index b7fe9ee..7e5e975 100644 --- a/config.go +++ b/config.go @@ -5,9 +5,19 @@ import ( "time" ) +// Types + type CloseHandler func(code int, text string) error -type Config struct { +// Pool Config + +type PoolConfig struct { + ConnectionConfig *ConnectionConfig +} + +// Connection Config + +type ConnectionConfig struct { CloseHandler CloseHandler WriteTimeout time.Duration Retry *RetryConfig @@ -20,21 +30,21 @@ type RetryConfig struct { JitterFactor float64 } -type ConfigOption func(*Config) error +type ConnectionOption func(*ConnectionConfig) error -func NewConfig(options ...ConfigOption) (*Config, error) { - conf := GetDefaultConfig() - if err := SetConfig(conf, options...); err != nil { +func NewConnectionConfig(options ...ConnectionOption) (*ConnectionConfig, error) { + conf := GetDefaultConnectionConfig() + if err := applyConnectionOptions(conf, options...); err != nil { return nil, err } - if err := ValidateConfig(conf); err != nil { + if err := validateConnectionConfig(conf); err != nil { return nil, err } return conf, nil } -func GetDefaultConfig() *Config { - return &Config{ +func GetDefaultConnectionConfig() *ConnectionConfig { + return &ConnectionConfig{ CloseHandler: nil, WriteTimeout: 30 * time.Second, Retry: GetDefaultRetryConfig(), @@ -50,7 +60,7 @@ func GetDefaultRetryConfig() *RetryConfig { } } -func SetConfig(config *Config, options ...ConfigOption) error { +func applyConnectionOptions(config *ConnectionConfig, options ...ConnectionOption) error { for _, option := range options { if err := option(config); err != nil { return err @@ -59,7 +69,7 @@ func SetConfig(config *Config, options ...ConfigOption) error { return nil } -func ValidateConfig(config *Config) error { +func validateConnectionConfig(config *ConnectionConfig) error { if config.Retry != nil { if config.Retry.InitialDelay > config.Retry.MaxDelay { return errors.NewConfigError("initial delay may not exceed maximum delay") @@ -71,16 +81,16 @@ func ValidateConfig(config *Config) error { // Configuration Options -func WithCloseHandler(handler CloseHandler) ConfigOption { - return func(c *Config) error { +func WithCloseHandler(handler CloseHandler) ConnectionOption { + return func(c *ConnectionConfig) error { c.CloseHandler = handler return nil } } // When WriteTimeout is set to zero, read timeouts are disabled. -func WithWriteTimeout(value time.Duration) ConfigOption { - return func(c *Config) error { +func WithWriteTimeout(value time.Duration) ConnectionOption { + return func(c *ConnectionConfig) error { if value < 0 { return errors.InvalidWriteTimeout } @@ -95,15 +105,15 @@ func WithWriteTimeout(value time.Duration) ConfigOption { // If passed after granular retry options (WithRetryMaxRetries, etc.), // it will overwrite them. Use either WithRetry alone or the granular // options; not both. -func WithRetry() ConfigOption { - return func(c *Config) error { +func WithRetry() ConnectionOption { + return func(c *ConnectionConfig) error { c.Retry = GetDefaultRetryConfig() return nil } } -func WithRetryMaxRetries(value int) ConfigOption { - return func(c *Config) error { +func WithRetryMaxRetries(value int) ConnectionOption { + return func(c *ConnectionConfig) error { if c.Retry == nil { c.Retry = GetDefaultRetryConfig() } @@ -115,8 +125,8 @@ func WithRetryMaxRetries(value int) ConfigOption { } } -func WithRetryInitialDelay(value time.Duration) ConfigOption { - return func(c *Config) error { +func WithRetryInitialDelay(value time.Duration) ConnectionOption { + return func(c *ConnectionConfig) error { if c.Retry == nil { c.Retry = GetDefaultRetryConfig() } @@ -128,8 +138,8 @@ func WithRetryInitialDelay(value time.Duration) ConfigOption { } } -func WithRetryMaxDelay(value time.Duration) ConfigOption { - return func(c *Config) error { +func WithRetryMaxDelay(value time.Duration) ConnectionOption { + return func(c *ConnectionConfig) error { if c.Retry == nil { c.Retry = GetDefaultRetryConfig() } @@ -141,8 +151,8 @@ func WithRetryMaxDelay(value time.Duration) ConfigOption { } } -func WithRetryJitterFactor(value float64) ConfigOption { - return func(c *Config) error { +func WithRetryJitterFactor(value float64) ConnectionOption { + return func(c *ConnectionConfig) error { if c.Retry == nil { c.Retry = GetDefaultRetryConfig() } diff --git a/config_test.go b/config_test.go index a608727..1a5670b 100644 --- a/config_test.go +++ b/config_test.go @@ -10,29 +10,29 @@ import ( // Config Tests func TestNewConfig(t *testing.T) { - conf, err := NewConfig() + conf, err := NewConnectionConfig() assert.NoError(t, err) - assert.Equal(t, conf, &Config{ + assert.Equal(t, conf, &ConnectionConfig{ CloseHandler: nil, WriteTimeout: 30 * time.Second, Retry: GetDefaultRetryConfig(), }) // errors propagate - _, err = NewConfig(WithRetryMaxRetries(-1)) + _, err = NewConnectionConfig(WithRetryMaxRetries(-1)) assert.Error(t, err) - _, err = NewConfig(WithRetryInitialDelay(10), WithRetryMaxDelay(1)) + _, err = NewConnectionConfig(WithRetryInitialDelay(10), WithRetryMaxDelay(1)) assert.Error(t, err) } // Default Config Tests func TestDefaultConfig(t *testing.T) { - conf := GetDefaultConfig() + conf := GetDefaultConnectionConfig() - assert.Equal(t, conf, &Config{ + assert.Equal(t, conf, &ConnectionConfig{ CloseHandler: nil, WriteTimeout: 30 * time.Second, Retry: GetDefaultRetryConfig(), @@ -53,8 +53,8 @@ func TestDefaultRetryConfig(t *testing.T) { // Config Builder Tests func TestSetConfig(t *testing.T) { - conf := &Config{} - err := SetConfig( + conf := &ConnectionConfig{} + err := applyConnectionOptions( conf, WithRetryMaxRetries(0), WithRetryInitialDelay(3*time.Second), @@ -67,7 +67,7 @@ func TestSetConfig(t *testing.T) { assert.Equal(t, 0.5, conf.Retry.JitterFactor) // errors propagate - err = SetConfig( + err = applyConnectionOptions( conf, WithRetryMaxRetries(-10), ) @@ -78,118 +78,118 @@ func TestSetConfig(t *testing.T) { // Config Option Tests func TestWithCloseHandler(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithCloseHandler(func(code int, text string) error { return nil }) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Nil(t, conf.CloseHandler(0, "")) } func TestWithWriteTimeout(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithWriteTimeout(30) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, conf.WriteTimeout, time.Duration(30)) // zero allowed - conf = &Config{} + conf = &ConnectionConfig{} opt = WithWriteTimeout(0) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, conf.WriteTimeout, time.Duration(0)) // negative disallowed - conf = &Config{} + conf = &ConnectionConfig{} opt = WithWriteTimeout(-30) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidWriteTimeout) assert.ErrorContains(t, err, "write timeout must be positive") } func TestWithRetry(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithRetry() - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.NotNil(t, conf.Retry) assert.Equal(t, conf.Retry, GetDefaultRetryConfig()) } func TestWithRetryAttempts(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithRetryMaxRetries(3) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, 3, conf.Retry.MaxRetries) // zero allowed opt = WithRetryMaxRetries(0) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.NoError(t, err) // negative disallowed opt = WithRetryMaxRetries(-10) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryMaxRetries) assert.ErrorContains(t, err, "max retry count cannot be negative") } func TestWithRetryInitialDelay(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithRetryInitialDelay(10 * time.Second) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, 10*time.Second, conf.Retry.InitialDelay) // zero disallowed opt = WithRetryInitialDelay(0 * time.Second) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryInitialDelay) assert.ErrorContains(t, err, "initial delay must be positive") // negative disallowed opt = WithRetryInitialDelay(-10 * time.Second) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryInitialDelay) } func TestWithRetryMaxDelay(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithRetryMaxDelay(10 * time.Second) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, 10*time.Second, conf.Retry.MaxDelay) // zero disallowed opt = WithRetryMaxDelay(0 * time.Second) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryMaxDelay) assert.ErrorContains(t, err, "max delay must be positive") // negative disallowed opt = WithRetryMaxDelay(-10 * time.Second) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryMaxDelay) } func TestWithRetryJitterFactor(t *testing.T) { - conf := &Config{} + conf := &ConnectionConfig{} opt := WithRetryJitterFactor(0.2) - err := SetConfig(conf, opt) + err := applyConnectionOptions(conf, opt) assert.NoError(t, err) assert.Equal(t, 0.2, conf.Retry.JitterFactor) // negative disallowed opt = WithRetryJitterFactor(-1) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryJitterFactor) assert.ErrorContains(t, err, "jitter factor must be between 0.0 and 1.0") // >1 disallowed opt = WithRetryJitterFactor(1.1) - err = SetConfig(conf, opt) + err = applyConnectionOptions(conf, opt) assert.ErrorIs(t, err, errors.InvalidRetryJitterFactor) } @@ -198,21 +198,21 @@ func TestWithRetryJitterFactor(t *testing.T) { func TestValidateConfig(t *testing.T) { cases := []struct { name string - conf Config + conf ConnectionConfig wantErr error wantErrText string }{ { name: "valid empty", - conf: *&Config{}, + conf: *&ConnectionConfig{}, }, { name: "valid defaults", - conf: *GetDefaultConfig(), + conf: *GetDefaultConnectionConfig(), }, { name: "valid complete", - conf: Config{ + conf: ConnectionConfig{ CloseHandler: (func(code int, text string) error { return nil }), WriteTimeout: time.Duration(30), Retry: &RetryConfig{ @@ -225,7 +225,7 @@ func TestValidateConfig(t *testing.T) { }, { name: "invalid - initial delay > max delay", - conf: Config{ + conf: ConnectionConfig{ Retry: &RetryConfig{ InitialDelay: 10 * time.Second, MaxDelay: 1 * time.Second, @@ -237,7 +237,7 @@ func TestValidateConfig(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - err := ValidateConfig(&tc.conf) + err := validateConnectionConfig(&tc.conf) if tc.wantErr != nil || tc.wantErrText != "" { if tc.wantErr != nil { diff --git a/connection.go b/connection.go index a248205..166a303 100644 --- a/connection.go +++ b/connection.go @@ -39,7 +39,7 @@ type Connection struct { url *url.URL dialer Dialer socket Socket - config *Config + config *ConnectionConfig logger *slog.Logger incoming chan []byte @@ -54,12 +54,12 @@ type Connection struct { mu sync.RWMutex } -func NewConnection(urlStr string, config *Config, logger *slog.Logger) (*Connection, error) { +func NewConnection(urlStr string, config *ConnectionConfig, logger *slog.Logger) (*Connection, error) { if config == nil { - config = GetDefaultConfig() + config = GetDefaultConnectionConfig() } - if err := ValidateConfig(config); err != nil { + if err := validateConnectionConfig(config); err != nil { return nil, err } @@ -84,16 +84,16 @@ func NewConnection(urlStr string, config *Config, logger *slog.Logger) (*Connect return conn, nil } -func NewConnectionFromSocket(socket Socket, config *Config, logger *slog.Logger) (*Connection, error) { +func NewConnectionFromSocket(socket Socket, config *ConnectionConfig, logger *slog.Logger) (*Connection, error) { if socket == nil { return nil, errors.NewConnectionError("socket cannot be nil") } if config == nil { - config = GetDefaultConfig() + config = GetDefaultConnectionConfig() } - if err := ValidateConfig(config); err != nil { + if err := validateConnectionConfig(config); err != nil { return nil, err } diff --git a/connection_goroutine_test.go b/connection_goroutine_test.go index 5c53474..3c7699c 100644 --- a/connection_goroutine_test.go +++ b/connection_goroutine_test.go @@ -120,7 +120,7 @@ func TestStartWriter(t *testing.T) { t.Skip("skipping test in short mode") } - config := &Config{WriteTimeout: 0} + config := &ConnectionConfig{WriteTimeout: 0} outgoingData := make(chan mockOutgoingData, 10) mockSocket := NewMockSocket() @@ -166,7 +166,7 @@ func TestStartWriter(t *testing.T) { }) t.Run("write timeout sets deadline when positive", func(t *testing.T) { - config := &Config{WriteTimeout: 30 * time.Millisecond} + config := &ConnectionConfig{WriteTimeout: 30 * time.Millisecond} outgoingData := make(chan mockOutgoingData, 10) mockSocket := NewMockSocket() @@ -212,7 +212,7 @@ func TestStartWriter(t *testing.T) { }) t.Run("writer exits on deadline error", func(t *testing.T) { - config := &Config{WriteTimeout: 1 * time.Millisecond} + config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond} mockSocket := NewMockSocket() diff --git a/connection_test.go b/connection_test.go index eba65a9..ed4b68c 100644 --- a/connection_test.go +++ b/connection_test.go @@ -50,7 +50,7 @@ func TestNewConnection(t *testing.T) { cases := []struct { name string url string - config *Config + config *ConnectionConfig wantErr bool wantErrText string }{ @@ -62,7 +62,7 @@ func TestNewConnection(t *testing.T) { { name: "valid url, valid config", url: "wss://relay.example.com:8080/path", - config: &Config{WriteTimeout: 30 * time.Second}, + config: &ConnectionConfig{WriteTimeout: 30 * time.Second}, }, { name: "invalid url", @@ -74,7 +74,7 @@ func TestNewConnection(t *testing.T) { { name: "invalid config", url: "ws://example.com", - config: &Config{ + config: &ConnectionConfig{ Retry: &RetryConfig{ InitialDelay: 10 * time.Second, MaxDelay: 1 * time.Second, @@ -115,7 +115,7 @@ func TestNewConnection(t *testing.T) { // Verify default config is used if nil is passed if tc.config == nil { - assert.Equal(t, GetDefaultConfig(), conn.config) + assert.Equal(t, GetDefaultConnectionConfig(), conn.config) } else { assert.Equal(t, tc.config, conn.config) } @@ -127,7 +127,7 @@ func TestNewConnectionFromSocket(t *testing.T) { cases := []struct { name string socket Socket - config *Config + config *ConnectionConfig wantErr bool wantErrText string }{ @@ -146,12 +146,12 @@ func TestNewConnectionFromSocket(t *testing.T) { { name: "valid socket with valid config", socket: NewMockSocket(), - config: &Config{WriteTimeout: 30 * time.Second}, + config: &ConnectionConfig{WriteTimeout: 30 * time.Second}, }, { name: "invalid config", socket: NewMockSocket(), - config: &Config{ + config: &ConnectionConfig{ Retry: &RetryConfig{ InitialDelay: 10 * time.Second, MaxDelay: 1 * time.Second, @@ -163,7 +163,7 @@ func TestNewConnectionFromSocket(t *testing.T) { { name: "close handler set when provided", socket: NewMockSocket(), - config: &Config{ + config: &ConnectionConfig{ CloseHandler: func(code int, text string) error { return nil }, @@ -216,7 +216,7 @@ func TestNewConnectionFromSocket(t *testing.T) { // Verify default config is used if nil is passed if tc.config == nil { - assert.Equal(t, GetDefaultConfig(), conn.config) + assert.Equal(t, GetDefaultConnectionConfig(), conn.config) } else { assert.Equal(t, tc.config, conn.config) } @@ -293,7 +293,7 @@ func TestConnect(t *testing.T) { }) t.Run("connect retries on dial failure", func(t *testing.T) { - config := &Config{ + config := &ConnectionConfig{ Retry: &RetryConfig{ MaxRetries: 2, InitialDelay: 1 * time.Millisecond, @@ -325,7 +325,7 @@ func TestConnect(t *testing.T) { }) t.Run("connect fails after max retries", func(t *testing.T) { - config := &Config{ + config := &ConnectionConfig{ Retry: &RetryConfig{ MaxRetries: 2, InitialDelay: 1 * time.Millisecond, @@ -373,7 +373,7 @@ func TestConnect(t *testing.T) { t.Run("close handler configured when provided", func(t *testing.T) { handlerSet := false - config := &Config{ + config := &ConnectionConfig{ CloseHandler: func(code int, text string) error { return nil }, diff --git a/logging_test.go b/logging_test.go index 9601a6d..040d449 100644 --- a/logging_test.go +++ b/logging_test.go @@ -168,7 +168,7 @@ func TestConnectLogging(t *testing.T) { mockHandler := newMockSlogHandler() logger := slog.New(mockHandler) - config := &Config{ + config := &ConnectionConfig{ Retry: &RetryConfig{ MaxRetries: 2, InitialDelay: 1 * time.Millisecond, @@ -211,7 +211,7 @@ func TestConnectLogging(t *testing.T) { mockHandler := newMockSlogHandler() logger := slog.New(mockHandler) - config := &Config{ + config := &ConnectionConfig{ Retry: &RetryConfig{ MaxRetries: 3, InitialDelay: 1 * time.Millisecond, @@ -348,7 +348,7 @@ func TestWriterLogging(t *testing.T) { mockHandler := newMockSlogHandler() logger := slog.New(mockHandler) - config := &Config{WriteTimeout: 1 * time.Millisecond} + config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond} deadlineErr := fmt.Errorf("deadline error") mockSocket := NewMockSocket() diff --git a/mocks_test.go b/mocks_test.go index 77ee5b0..7206980 100644 --- a/mocks_test.go +++ b/mocks_test.go @@ -96,7 +96,7 @@ type mockOutgoingData struct { data []byte } -func setupTestConnection(t *testing.T, config *Config) ( +func setupTestConnection(t *testing.T, config *ConnectionConfig) ( conn *Connection, mockSocket *MockSocket, incomingData chan mockIncomingData, diff --git a/pool.go b/pool.go index 55bdb36..92c378e 100644 --- a/pool.go +++ b/pool.go @@ -62,7 +62,7 @@ type pool struct { errors chan error done chan struct{} - config *Config + config *ConnectionConfig logger *slog.Logger mu sync.RWMutex @@ -131,12 +131,12 @@ type OutboundPool struct { dialer Dialer } -func NewOutboundPool(config *Config, logger *slog.Logger) (*OutboundPool, error) { +func NewOutboundPool(config *ConnectionConfig, logger *slog.Logger) (*OutboundPool, error) { if config == nil { - config = GetDefaultConfig() + config = GetDefaultConnectionConfig() } - if err := ValidateConfig(config); err != nil { + if err := validateConnectionConfig(config); err != nil { return nil, err } diff --git a/pool_test.go b/pool_test.go index 7289df3..0a06d5a 100644 --- a/pool_test.go +++ b/pool_test.go @@ -68,7 +68,7 @@ func TestPoolConnect(t *testing.T) { }) t.Run("fails to add connection", func(t *testing.T) { - pool, err := NewOutboundPool(&Config{ + pool, err := NewOutboundPool(&ConnectionConfig{ Retry: &RetryConfig{ MaxRetries: 1, InitialDelay: 1 * time.Millisecond,