github.com/safing/portbase@v0.19.5/config/registry.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "regexp" 6 "sort" 7 "strings" 8 "sync" 9 ) 10 11 var ( 12 optionsLock sync.RWMutex 13 options = make(map[string]*Option) 14 ) 15 16 // ForEachOption calls fn for each defined option. If fn returns 17 // and error the iteration is stopped and the error is returned. 18 // Note that ForEachOption does not guarantee a stable order of 19 // iteration between multiple calles. ForEachOption does NOT lock 20 // opt when calling fn. 21 func ForEachOption(fn func(opt *Option) error) error { 22 optionsLock.RLock() 23 defer optionsLock.RUnlock() 24 25 for _, opt := range options { 26 if err := fn(opt); err != nil { 27 return err 28 } 29 } 30 return nil 31 } 32 33 // ExportOptions exports the registered options. The returned data must be 34 // treated as immutable. 35 // The data does not include the current active or default settings. 36 func ExportOptions() []*Option { 37 optionsLock.RLock() 38 defer optionsLock.RUnlock() 39 40 // Copy the map into a slice. 41 opts := make([]*Option, 0, len(options)) 42 for _, opt := range options { 43 opts = append(opts, opt) 44 } 45 46 sort.Sort(sortByKey(opts)) 47 return opts 48 } 49 50 // GetOption returns the option with name or an error 51 // if the option does not exist. The caller should lock 52 // the returned option itself for further processing. 53 func GetOption(name string) (*Option, error) { 54 optionsLock.RLock() 55 defer optionsLock.RUnlock() 56 57 opt, ok := options[name] 58 if !ok { 59 return nil, fmt.Errorf("option %q does not exist", name) 60 } 61 return opt, nil 62 } 63 64 // Register registers a new configuration option. 65 func Register(option *Option) error { 66 if option.Name == "" { 67 return fmt.Errorf("failed to register option: please set option.Name") 68 } 69 if option.Key == "" { 70 return fmt.Errorf("failed to register option: please set option.Key") 71 } 72 if option.Description == "" { 73 return fmt.Errorf("failed to register option: please set option.Description") 74 } 75 if option.OptType == 0 { 76 return fmt.Errorf("failed to register option: please set option.OptType") 77 } 78 79 if option.ValidationRegex == "" && option.PossibleValues != nil { 80 values := make([]string, len(option.PossibleValues)) 81 for idx, val := range option.PossibleValues { 82 values[idx] = fmt.Sprintf("%v", val.Value) 83 } 84 option.ValidationRegex = fmt.Sprintf("^(%s)$", strings.Join(values, "|")) 85 } 86 87 var err error 88 if option.ValidationRegex != "" { 89 option.compiledRegex, err = regexp.Compile(option.ValidationRegex) 90 if err != nil { 91 return fmt.Errorf("config: could not compile option.ValidationRegex: %w", err) 92 } 93 } 94 95 var vErr *ValidationError 96 option.activeFallbackValue, vErr = validateValue(option, option.DefaultValue) 97 if vErr != nil { 98 return fmt.Errorf("config: invalid default value: %w", vErr) 99 } 100 101 optionsLock.Lock() 102 defer optionsLock.Unlock() 103 options[option.Key] = option 104 105 return nil 106 }