55 "errors"
66 "fmt"
77 "reflect"
8- "slices"
98 "strconv"
109 "strings"
10+ "sync"
1111 "time"
1212)
1313
@@ -21,6 +21,21 @@ type SettingVariable struct {
2121 Value string
2222}
2323
24+ type settingFieldMeta struct {
25+ index int
26+ key string
27+ attrs string
28+ isPublic bool
29+ isSensitive bool
30+ isLocal bool
31+ }
32+
33+ var settingsFieldCache struct {
34+ once sync.Once
35+ ordered []settingFieldMeta
36+ byKey map [string ]settingFieldMeta
37+ }
38+
2439// IsTrue returns true if the value is a truthy string
2540func (s SettingVariable ) IsTrue () bool {
2641 ok , _ := strconv .ParseBool (s .Value )
@@ -150,29 +165,63 @@ func (SettingVariable) TableName() string {
150165 return "settings"
151166}
152167
153- func (s * Settings ) ToSettingVariableSlice (showAll bool , redactSensitiveValues bool ) []SettingVariable {
154- cfgValue := reflect .ValueOf (s ).Elem ()
155- cfgType := cfgValue .Type ()
156-
157- var res []SettingVariable
158-
159- for i := 0 ; i < cfgType .NumField (); i ++ {
160- field := cfgType .Field (i )
168+ func buildSettingsFieldCache () {
169+ rt := reflect .TypeFor [Settings ]()
170+ ordered := make ([]settingFieldMeta , 0 , rt .NumField ())
171+ byKey := make (map [string ]settingFieldMeta , rt .NumField ())
161172
173+ for i := 0 ; i < rt .NumField (); i ++ {
174+ field := rt .Field (i )
162175 key , attrs , _ := strings .Cut (field .Tag .Get ("key" ), "," )
163176 if key == "" {
164177 continue
165178 }
166179
167- if ! showAll && ! strings .Contains (attrs , "public" ) {
180+ meta := settingFieldMeta {
181+ index : i ,
182+ key : key ,
183+ attrs : attrs ,
184+ isPublic : strings .Contains (attrs , "public" ),
185+ isSensitive : strings .Contains (attrs , "sensitive" ),
186+ isLocal : strings .Contains (attrs , "local" ),
187+ }
188+ ordered = append (ordered , meta )
189+ byKey [key ] = meta
190+ }
191+
192+ settingsFieldCache .ordered = ordered
193+ settingsFieldCache .byKey = byKey
194+ }
195+
196+ func getSettingsFieldCache () ([]settingFieldMeta , map [string ]settingFieldMeta ) {
197+ settingsFieldCache .once .Do (buildSettingsFieldCache )
198+ return settingsFieldCache .ordered , settingsFieldCache .byKey
199+ }
200+
201+ func (s * Settings ) Clone () * Settings {
202+ if s == nil {
203+ return & Settings {}
204+ }
205+
206+ clone := * s
207+ return & clone
208+ }
209+
210+ func (s * Settings ) ToSettingVariableSlice (showAll bool , redactSensitiveValues bool ) []SettingVariable {
211+ cfgValue := reflect .ValueOf (s ).Elem ()
212+ fields , _ := getSettingsFieldCache ()
213+
214+ res := make ([]SettingVariable , 0 , len (fields ))
215+ for _ , field := range fields {
216+ if ! showAll && ! field .isPublic {
168217 continue
169218 }
170219
171- value := cfgValue .Field (i ).FieldByName ("Value" ).String ()
172- value = redactSettingValue (key , value , attrs , redactSensitiveValues )
220+ value := cfgValue .Field (field . index ).FieldByName ("Value" ).String ()
221+ value = redactSettingValue (field . key , value , field . attrs , redactSensitiveValues )
173222
174223 settingVariable := SettingVariable {
175- Key : key ,
224+ Key : field . key ,
176225 Value : value ,
177226 }
178227 res = append (res , settingVariable )
@@ -183,65 +232,47 @@ func (s *Settings) ToSettingVariableSlice(showAll bool, redactSensitiveValues bo
183232
184233func (s * Settings ) FieldByKey (key string ) (defaultValue string , isPublic bool , isSensitive bool , err error ) {
185234 rv := reflect .ValueOf (s ).Elem ()
186- rt := rv .Type ()
187-
188- for i := 0 ; i < rt .NumField (); i ++ {
189- tagValue := strings .Split (rt .Field (i ).Tag .Get ("key" ), "," )
190- keyFromTag := tagValue [0 ]
191- isPublic = slices .Contains (tagValue , "public" )
192- isSensitive = slices .Contains (tagValue , "sensitive" )
235+ _ , byKey := getSettingsFieldCache ()
193236
194- if keyFromTag != key {
195- continue
196- }
197-
198- valueField := rv .Field (i ).FieldByName ("Value" )
199- return valueField .String (), isPublic , isSensitive , nil
237+ field , ok := byKey [key ]
238+ if ! ok {
239+ return "" , false , false , SettingKeyNotFoundError {field : key }
200240 }
201241
202- return "" , false , false , SettingKeyNotFoundError {field : key }
242+ valueField := rv .Field (field .index ).FieldByName ("Value" )
243+ return valueField .String (), field .isPublic , field .isSensitive , nil
203244}
204245
205246func (s * Settings ) IsLocalSetting (key string ) bool {
206- rt := reflect .TypeFor [Settings ]()
207-
208- for field := range rt .Fields () {
209- tagValue := strings .Split (field .Tag .Get ("key" ), "," )
210- keyFromTag := tagValue [0 ]
211-
212- if keyFromTag == key {
213- return slices .Contains (tagValue , "local" )
214- }
247+ _ , byKey := getSettingsFieldCache ()
248+ field , ok := byKey [key ]
249+ if ! ok {
250+ return false
215251 }
216252
217- return false
253+ return field . isLocal
218254}
219255
220256func (s * Settings ) UpdateField (key string , value string , noSensitive bool ) error {
221257 rv := reflect .ValueOf (s ).Elem ()
222- rt := rv .Type ()
223-
224- for i := 0 ; i < rt .NumField (); i ++ {
225- tagValue , attrs , _ := strings .Cut (rt .Field (i ).Tag .Get ("key" ), "," )
226- if tagValue != key {
227- continue
228- }
258+ _ , byKey := getSettingsFieldCache ()
229259
230- // If the field is sensitive and noSensitive is true, we skip that
231- if noSensitive && strings . Contains ( attrs , "sensitive" ) {
232- return SettingSensitiveForbiddenError {field : key }
233- }
260+ field , ok := byKey [ key ]
261+ if ! ok {
262+ return SettingKeyNotFoundError {field : key }
263+ }
234264
235- valueField := rv .Field (i ).FieldByName ("Value" )
236- if ! valueField .CanSet () {
237- return fmt .Errorf ("field Value in SettingVariable is not settable for config key '%s'" , key )
238- }
265+ if noSensitive && field .isSensitive {
266+ return SettingSensitiveForbiddenError {field : key }
267+ }
239268
240- valueField .SetString (value )
241- return nil
269+ valueField := rv .Field (field .index ).FieldByName ("Value" )
270+ if ! valueField .CanSet () {
271+ return fmt .Errorf ("field Value in SettingVariable is not settable for config key '%s'" , key )
242272 }
243273
244- return SettingKeyNotFoundError {field : key }
274+ valueField .SetString (value )
275+ return nil
245276}
246277
247278// helper keeps redaction logic in one place; behavior unchanged
0 commit comments