@@ -5,6 +5,7 @@ package config
55
66import (
77 "context"
8+ "reflect"
89 "sync"
910
1011 "code.gitea.io/gitea/modules/json"
@@ -16,18 +17,31 @@ type CfgSecKey struct {
1617 Sec , Key string
1718}
1819
19- type Value [T any ] struct {
20+ // OptionInterface is used to overcome Golang's generic interface limitation
21+ type OptionInterface interface {
22+ GetDefaultValue () any
23+ }
24+
25+ type Option [T any ] struct {
2026 mu sync.RWMutex
2127
2228 cfgSecKey CfgSecKey
2329 dynKey string
2430
25- def , value T
31+ value T
32+ defSimple T
33+ defFunc func () T
34+ emptyAsDef bool
35+ has bool
2636 revision int
2737}
2838
29- func (value * Value [T ]) parse (key , valStr string ) (v T ) {
30- v = value .def
39+ func (opt * Option [T ]) GetDefaultValue () any {
40+ return opt .DefaultValue ()
41+ }
42+
43+ func (opt * Option [T ]) parse (key , valStr string ) (v T ) {
44+ v = opt .DefaultValue ()
3145 if valStr != "" {
3246 if err := json .Unmarshal (util .UnsafeStringToBytes (valStr ), & v ); err != nil {
3347 log .Error ("Unable to unmarshal json config for key %q, err: %v" , key , err )
@@ -36,63 +50,132 @@ func (value *Value[T]) parse(key, valStr string) (v T) {
3650 return v
3751}
3852
39- func (value * Value [T ]) Value (ctx context.Context ) (v T ) {
53+ func (opt * Option [T ]) HasValue (ctx context.Context ) bool {
54+ _ , _ , has := opt .ValueRevision (ctx )
55+ return has
56+ }
57+
58+ func (opt * Option [T ]) Value (ctx context.Context ) (v T ) {
59+ v , _ , _ = opt .ValueRevision (ctx )
60+ return v
61+ }
62+
63+ func isZeroOrEmpty (v any ) bool {
64+ if v == nil {
65+ return true // interface itself is nil
66+ }
67+ r := reflect .ValueOf (v )
68+ if r .IsZero () {
69+ return true
70+ }
71+
72+ if r .Kind () == reflect .Slice || r .Kind () == reflect .Map {
73+ if r .IsNil () {
74+ return true
75+ }
76+ return r .Len () == 0
77+ }
78+ return false
79+ }
80+
81+ func (opt * Option [T ]) ValueRevision (ctx context.Context ) (v T , rev int , has bool ) {
4082 dg := GetDynGetter ()
4183 if dg == nil {
4284 // this is an edge case: the database is not initialized but the system setting is going to be used
4385 // it should panic to avoid inconsistent config values (from config / system setting) and fix the code
4486 panic ("no config dyn value getter" )
4587 }
4688
47- rev : = dg .GetRevision (ctx )
89+ rev = dg .GetRevision (ctx )
4890
4991 // if the revision in the database doesn't change, use the last value
50- value .mu .RLock ()
51- if rev == value .revision {
52- v = value .value
53- value .mu .RUnlock ()
54- return v
92+ opt .mu .RLock ()
93+ if rev == opt .revision {
94+ v = opt .value
95+ has = opt .has
96+ opt .mu .RUnlock ()
97+ return v , rev , has
5598 }
56- value .mu .RUnlock ()
99+ opt .mu .RUnlock ()
57100
58101 // try to parse the config and cache it
59102 var valStr * string
60- if dynVal , has := dg .GetValue (ctx , value .dynKey ); has {
103+ if dynVal , hasDbValue := dg .GetValue (ctx , opt .dynKey ); hasDbValue {
61104 valStr = & dynVal
62- } else if cfgVal , has := GetCfgSecKeyGetter ().GetValue (value .cfgSecKey .Sec , value .cfgSecKey .Key ); has {
105+ } else if cfgVal , has := GetCfgSecKeyGetter ().GetValue (opt .cfgSecKey .Sec , opt .cfgSecKey .Key ); has {
63106 valStr = & cfgVal
64107 }
65108 if valStr == nil {
66- v = value .def
109+ v = opt .DefaultValue ()
110+ has = false
67111 } else {
68- v = value .parse (value .dynKey , * valStr )
112+ v = opt .parse (opt .dynKey , * valStr )
113+ if opt .emptyAsDef && isZeroOrEmpty (v ) {
114+ v = opt .DefaultValue ()
115+ } else {
116+ has = true
117+ }
69118 }
70119
71- value .mu .Lock ()
72- value .value = v
73- value .revision = rev
74- value .mu .Unlock ()
75- return v
120+ opt .mu .Lock ()
121+ opt .value = v
122+ opt .revision = rev
123+ opt .has = has
124+ opt .mu .Unlock ()
125+ return v , rev , has
126+ }
127+
128+ func (opt * Option [T ]) DynKey () string {
129+ return opt .dynKey
130+ }
131+
132+ // WithDefaultFunc sets the default value with a function
133+ // The "def" value might be changed during runtime (e.g.: Unmarshal with default), so it shouldn't use the same pointer or slice
134+ func (opt * Option [T ]) WithDefaultFunc (f func () T ) * Option [T ] {
135+ opt .defFunc = f
136+ return opt
76137}
77138
78- func (value * Value [T ]) DynKey () string {
79- return value .dynKey
139+ func (opt * Option [T ]) WithDefaultSimple (def T ) * Option [T ] {
140+ v := any (def )
141+ switch v .(type ) {
142+ case string , bool , int , int8 , int16 , int32 , int64 , uint , uint8 , uint16 , uint32 , uint64 :
143+ default :
144+ // TODO: use reflect to support convertable basic types like `type State string`
145+ r := reflect .ValueOf (v )
146+ if r .Kind () != reflect .Struct {
147+ panic ("invalid type for default value, use WithDefaultFunc instead" )
148+ }
149+ }
150+ opt .defSimple = def
151+ return opt
80152}
81153
82- func (value * Value [T ]) WithDefault ( def T ) * Value [T ] {
83- value . def = def
84- return value
154+ func (opt * Option [T ]) WithEmptyAsDefault ( ) * Option [T ] {
155+ opt . emptyAsDef = true
156+ return opt
85157}
86158
87- func (value * Value [T ]) DefaultValue () T {
88- return value .def
159+ func (opt * Option [T ]) DefaultValue () T {
160+ if opt .defFunc != nil {
161+ return opt .defFunc ()
162+ }
163+ return opt .defSimple
89164}
90165
91- func (value * Value [T ]) WithFileConfig (cfgSecKey CfgSecKey ) * Value [T ] {
92- value .cfgSecKey = cfgSecKey
93- return value
166+ func (opt * Option [T ]) WithFileConfig (cfgSecKey CfgSecKey ) * Option [T ] {
167+ opt .cfgSecKey = cfgSecKey
168+ return opt
169+ }
170+
171+ var allConfigOptions = map [string ]OptionInterface {}
172+
173+ func NewOption [T any ](dynKey string ) * Option [T ] {
174+ v := & Option [T ]{dynKey : dynKey }
175+ allConfigOptions [dynKey ] = v
176+ return v
94177}
95178
96- func ValueJSON [ T any ] (dynKey string ) * Value [ T ] {
97- return & Value [ T ]{ dynKey : dynKey }
179+ func GetConfigOption (dynKey string ) OptionInterface {
180+ return allConfigOptions [ dynKey ]
98181}
0 commit comments