@@ -11,12 +11,14 @@ import (
1111// Supports simple fields and nested fields like "logs.level" or "telemetry.enabled".
1212// Uses reflection to dynamically set fields based on struct tags.
1313func SetConfigValue (data * Data , path string , value string ) error {
14- if err := ValidateConfigValue (data , path , value ); err != nil {
14+ // Validate and normalize the value (validation also normalizes case for oneof fields)
15+ normalizedValue , err := ValidateAndNormalizeConfigValue (data , path , value )
16+ if err != nil {
1517 return err
1618 }
1719
1820 parts := strings .Split (path , "." )
19- return setFieldByPath (reflect .ValueOf (data ).Elem (), parts , value )
21+ return setFieldByPath (reflect .ValueOf (data ).Elem (), parts , normalizedValue )
2022}
2123
2224// setFieldByPath recursively navigates and sets a field using the path segments
@@ -124,12 +126,18 @@ func setFieldValue(field reflect.Value, value string) error {
124126
125127// ValidateConfigValue validates that a config path exists and the value is valid for its type
126128func ValidateConfigValue (data * Data , path string , value string ) error {
129+ _ , err := ValidateAndNormalizeConfigValue (data , path , value )
130+ return err
131+ }
132+
133+ // ValidateAndNormalizeConfigValue validates and normalizes (case) the value for oneof fields
134+ func ValidateAndNormalizeConfigValue (data * Data , path string , value string ) (string , error ) {
127135 if path == "" {
128- return fmt .Errorf ("config path cannot be empty" )
136+ return "" , fmt .Errorf ("config path cannot be empty" )
129137 }
130138
131139 if value == "" {
132- return fmt .Errorf ("config value cannot be empty" )
140+ return "" , fmt .Errorf ("config value cannot be empty" )
133141 }
134142
135143 parts := strings .Split (path , "." )
@@ -143,83 +151,83 @@ func ValidateConfigValue(data *Data, path string, value string) error {
143151 if err != nil {
144152 // Provide helpful context about where we are in the path
145153 if i > 0 {
146- return fmt .Errorf ("invalid config path %s: %w (at level %d)" , path , err , i + 1 )
154+ return "" , fmt .Errorf ("invalid config path %s: %w (at level %d)" , path , err , i + 1 )
147155 }
148- return err
156+ return "" , err
149157 }
150158
151- // If this is the last part, validate the value can be set
159+ // If this is the last part, validate and normalize the value
152160 if i == len (parts )- 1 {
153- return validateValueForTypeWithTag (field , sf , value , path )
161+ return validateAndNormalizeValueForTypeWithTag (field , sf , value , path )
154162 }
155163
156164 // Navigate to nested struct
157165 if field .Kind () != reflect .Struct {
158- return fmt .Errorf ("field %s is not a struct, cannot navigate to %s" , part , parts [i + 1 ])
166+ return "" , fmt .Errorf ("field %s is not a struct, cannot navigate to %s" , part , parts [i + 1 ])
159167 }
160168
161169 v = field
162170 t = field .Type ()
163171 }
164172
165- return nil
173+ return value , nil
166174}
167175
168- // validateValueForTypeWithTag checks if a value can be converted to the field's type
169- // and validates against any constraints defined in the validate tag
170- func validateValueForTypeWithTag (field reflect.Value , sf reflect.StructField , value string , path string ) error {
176+ // validateAndNormalizeValueForTypeWithTag validates and normalizes the value
177+ func validateAndNormalizeValueForTypeWithTag (field reflect.Value , sf reflect.StructField , value string , path string ) (string , error ) {
171178 // First check type conversion
172179 switch field .Kind () {
173180 case reflect .String :
174- // Check validate tag for string constraints
175- return validateWithTag (sf , value , path )
181+ // Check validate tag for string constraints and normalize if oneof
182+ return validateAndNormalizeWithTag (sf , value , path )
176183
177184 case reflect .Bool :
178185 if _ , err := strconv .ParseBool (value ); err != nil {
179- return fmt .Errorf ("invalid boolean value for %s: %s (valid values: true, false, 1, 0)" , path , value )
186+ return "" , fmt .Errorf ("invalid boolean value for %s: %s (valid values: true, false, 1, 0)" , path , value )
180187 }
181- return nil
188+ return value , nil
182189
183190 case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
184191 if _ , err := strconv .ParseInt (value , 10 , 64 ); err != nil {
185- return fmt .Errorf ("invalid integer value for %s: %s" , path , value )
192+ return "" , fmt .Errorf ("invalid integer value for %s: %s" , path , value )
186193 }
187- return nil
194+ return value , nil
188195
189196 case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
190197 if _ , err := strconv .ParseUint (value , 10 , 64 ); err != nil {
191- return fmt .Errorf ("invalid unsigned integer value for %s: %s" , path , value )
198+ return "" , fmt .Errorf ("invalid unsigned integer value for %s: %s" , path , value )
192199 }
193- return nil
200+ return value , nil
194201
195202 case reflect .Float32 , reflect .Float64 :
196203 if _ , err := strconv .ParseFloat (value , 64 ); err != nil {
197- return fmt .Errorf ("invalid float value for %s: %s" , path , value )
204+ return "" , fmt .Errorf ("invalid float value for %s: %s" , path , value )
198205 }
199- return nil
206+ return value , nil
200207
201208 default :
202- return fmt .Errorf ("field %s has unsupported type %s (only string, bool, and numeric types can be set)" , path , field .Kind ())
209+ return "" , fmt .Errorf ("field %s has unsupported type %s (only string, bool, and numeric types can be set)" , path , field .Kind ())
203210 }
204211}
205212
206- // validateWithTag validates a value against the validate struct tag
207- func validateWithTag (sf reflect.StructField , value string , path string ) error {
213+ // validateAndNormalizeWithTag validates and normalizes a value against the validate struct tag
214+ func validateAndNormalizeWithTag (sf reflect.StructField , value string , path string ) ( string , error ) {
208215 validateTag := sf .Tag .Get ("validate" )
209216 if validateTag == "" {
210- return nil // No validation tag
217+ return value , nil // No validation tag
211218 }
212219
213220 // Parse the validate tag - currently only support "oneof=value1 value2 ..."
214221 if strings .HasPrefix (validateTag , "oneof=" ) {
215222 validValues := strings .Split (strings .TrimPrefix (validateTag , "oneof=" ), " " )
223+ // Case-insensitive comparison, return correctly-cased value from tag
216224 for _ , valid := range validValues {
217- if value == valid {
218- return nil
225+ if strings . EqualFold ( value , valid ) {
226+ return valid , nil // Return normalized value
219227 }
220228 }
221- return fmt .Errorf ("invalid value for %s: %s (valid values: %s)" , path , value , strings .Join (validValues , ", " ))
229+ return "" , fmt .Errorf ("invalid value for %s: %s (valid values: %s)" , path , value , strings .Join (validValues , ", " ))
222230 }
223231
224- return nil
232+ return value , nil
225233}
0 commit comments