|
| 1 | +package config |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "net/http" |
| 6 | + "strconv" |
| 7 | +) |
| 8 | + |
| 9 | +// ConfigValue represents a configuration value that can be set by client or resolved from server. |
| 10 | +// This implements the config overlay pattern: client > server > default |
| 11 | +// |
| 12 | +// T is the type of the configuration value (bool, string, int, etc.) |
| 13 | +// |
| 14 | +// Example usage: |
| 15 | +// |
| 16 | +// type MyConfig struct { |
| 17 | +// EnableFeature ConfigValue[bool] |
| 18 | +// BatchSize ConfigValue[int] |
| 19 | +// } |
| 20 | +// |
| 21 | +// // Client explicitly sets value (overrides server) |
| 22 | +// config.EnableFeature = NewConfigValue(true) |
| 23 | +// |
| 24 | +// // Client doesn't set value (use server) |
| 25 | +// config.EnableFeature = ConfigValue[bool]{} // nil/unset |
| 26 | +// |
| 27 | +// // Resolve value with overlay priority |
| 28 | +// enabled := config.EnableFeature.Resolve(ctx, serverResolver, defaultValue) |
| 29 | +type ConfigValue[T any] struct { |
| 30 | + // value is the client-set configuration value |
| 31 | + // nil = not set by client (use server config) |
| 32 | + // non-nil = explicitly set by client (overrides server) |
| 33 | + value *T |
| 34 | +} |
| 35 | + |
| 36 | +// NewConfigValue creates a ConfigValue with a client-set value. |
| 37 | +// The value will override any server-side configuration. |
| 38 | +func NewConfigValue[T any](value T) ConfigValue[T] { |
| 39 | + return ConfigValue[T]{value: &value} |
| 40 | +} |
| 41 | + |
| 42 | +// IsSet returns true if the client explicitly set this configuration value. |
| 43 | +func (cv ConfigValue[T]) IsSet() bool { |
| 44 | + return cv.value != nil |
| 45 | +} |
| 46 | + |
| 47 | +// Get returns the client-set value and whether it was set. |
| 48 | +// If not set, returns zero value and false. |
| 49 | +func (cv ConfigValue[T]) Get() (T, bool) { |
| 50 | + if cv.value != nil { |
| 51 | + return *cv.value, true |
| 52 | + } |
| 53 | + var zero T |
| 54 | + return zero, false |
| 55 | +} |
| 56 | + |
| 57 | +// ServerResolver defines how to fetch a configuration value from the server. |
| 58 | +// Implementations should handle caching, retries, and error handling. |
| 59 | +type ServerResolver[T any] interface { |
| 60 | + // Resolve fetches the configuration value from the server. |
| 61 | + // Returns the value and any error encountered. |
| 62 | + // On error, the config overlay will fall back to the default value. |
| 63 | + Resolve(ctx context.Context, host string, httpClient *http.Client) (T, error) |
| 64 | +} |
| 65 | + |
| 66 | +// Resolve applies config overlay priority to determine the final value: |
| 67 | +// |
| 68 | +// Priority 1: Client Config - if explicitly set (overrides server) |
| 69 | +// Priority 2: Server Config - resolved via serverResolver (when client doesn't set) |
| 70 | +// Priority 3: Default Value - used when server unavailable/errors (fail-safe) |
| 71 | +// |
| 72 | +// Parameters: |
| 73 | +// - ctx: Context for server requests |
| 74 | +// - serverResolver: How to fetch from server (can be nil if no server config) |
| 75 | +// - defaultValue: Fail-safe default when client unset and server unavailable |
| 76 | +// |
| 77 | +// Returns: The resolved configuration value following overlay priority |
| 78 | +func (cv ConfigValue[T]) Resolve( |
| 79 | + ctx context.Context, |
| 80 | + serverResolver ServerResolver[T], |
| 81 | + defaultValue T, |
| 82 | +) T { |
| 83 | + // Priority 1: Client explicitly set (overrides everything) |
| 84 | + if cv.value != nil { |
| 85 | + return *cv.value |
| 86 | + } |
| 87 | + |
| 88 | + // Priority 2: Try server config (if resolver provided) |
| 89 | + if serverResolver != nil { |
| 90 | + // Note: We pass empty host/httpClient here. Actual resolver should have these injected |
| 91 | + // This is a simplified interface - real usage would inject dependencies |
| 92 | + if serverValue, err := serverResolver.Resolve(ctx, "", nil); err == nil { |
| 93 | + return serverValue |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + // Priority 3: Fail-safe default |
| 98 | + return defaultValue |
| 99 | +} |
| 100 | + |
| 101 | +// ResolveWithContext is a more flexible version that takes host and httpClient. |
| 102 | +// This is the recommended method for production use. |
| 103 | +func (cv ConfigValue[T]) ResolveWithContext( |
| 104 | + ctx context.Context, |
| 105 | + host string, |
| 106 | + httpClient *http.Client, |
| 107 | + serverResolver ServerResolver[T], |
| 108 | + defaultValue T, |
| 109 | +) T { |
| 110 | + // Priority 1: Client explicitly set (overrides everything) |
| 111 | + if cv.value != nil { |
| 112 | + return *cv.value |
| 113 | + } |
| 114 | + |
| 115 | + // Priority 2: Try server config (if resolver provided) |
| 116 | + if serverResolver != nil { |
| 117 | + if serverValue, err := serverResolver.Resolve(ctx, host, httpClient); err == nil { |
| 118 | + return serverValue |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + // Priority 3: Fail-safe default |
| 123 | + return defaultValue |
| 124 | +} |
| 125 | + |
| 126 | +// ParseBoolConfigValue parses a string value into a ConfigValue[bool]. |
| 127 | +// Returns unset ConfigValue if the parameter is not present. |
| 128 | +// |
| 129 | +// Example: |
| 130 | +// |
| 131 | +// params := map[string]string{"enableFeature": "true"} |
| 132 | +// value := ParseBoolConfigValue(params, "enableFeature") |
| 133 | +// // value.IsSet() == true, value.Get() == (true, true) |
| 134 | +func ParseBoolConfigValue(params map[string]string, key string) ConfigValue[bool] { |
| 135 | + if v, ok := params[key]; ok { |
| 136 | + enabled := (v == "true" || v == "1") |
| 137 | + return NewConfigValue(enabled) |
| 138 | + } |
| 139 | + return ConfigValue[bool]{} // Unset |
| 140 | +} |
| 141 | + |
| 142 | +// ParseStringConfigValue parses a string value into a ConfigValue[string]. |
| 143 | +// Returns unset ConfigValue if the parameter is not present. |
| 144 | +func ParseStringConfigValue(params map[string]string, key string) ConfigValue[string] { |
| 145 | + if v, ok := params[key]; ok { |
| 146 | + return NewConfigValue(v) |
| 147 | + } |
| 148 | + return ConfigValue[string]{} // Unset |
| 149 | +} |
| 150 | + |
| 151 | +// ParseIntConfigValue parses a string value into a ConfigValue[int]. |
| 152 | +// Returns unset ConfigValue if the parameter is not present or invalid. |
| 153 | +func ParseIntConfigValue(params map[string]string, key string) ConfigValue[int] { |
| 154 | + if v, ok := params[key]; ok { |
| 155 | + if i, err := strconv.Atoi(v); err == nil { |
| 156 | + return NewConfigValue(i) |
| 157 | + } |
| 158 | + } |
| 159 | + return ConfigValue[int]{} // Unset |
| 160 | +} |
0 commit comments