pkg/genlib/config/config.go (164 lines of code) (raw):
package config
import (
"errors"
"time"
"math"
"os"
"github.com/elastic/go-ucfg/yaml"
"github.com/spf13/afero"
)
var rangeBoundNotSet = errors.New("range bound not set")
var rangeTimeNotSet = errors.New("range time not set")
var rangeInvalidConfig = errors.New("range defining both `period` and `from`/`to`")
var counterInvalidConfig = errors.New("both `range` and `counter` defined")
type TimeRange struct {
time.Time
}
func (ct *TimeRange) Unpack(t string) error {
var err error
ct.Time, err = time.Parse("2006-01-02T15:04:05.999999999-07:00", t)
return err
}
type Range struct {
// NOTE: we want to distinguish when Min/Max/From/To are explicitly set to zero value or are not set at all. We use a pointer, such that when not set will be `nil`.
Min *float64 `config:"min"`
Max *float64 `config:"max"`
From *TimeRange `config:"from"`
To *TimeRange `config:"to"`
}
type Config struct {
m map[string]ConfigField
}
type ConfigField struct {
Name string `config:"name"`
Fuzziness float64 `config:"fuzziness"`
Range Range `config:"range"`
Cardinality int `config:"cardinality"`
Period time.Duration `config:"period"`
Enum []string `config:"enum"`
ObjectKeys []string `config:"object_keys"`
Value any `config:"value"`
Counter bool `config:"counter"`
CounterReset *CounterReset `config:"counter_reset"`
}
const (
CounterResetStrategyRandom string = "random"
CounterResetStrategyProbabilistic string = "probabilistic"
CounterResetStrategyAfterN string = "after_n"
)
type CounterReset struct {
Strategy string `config:"strategy"`
Probability *uint64 `config:"probability"`
ResetAfterN *uint64 `config:"reset_after_n"`
}
func (cf ConfigField) ValidateCounterResetStrategy() error {
if cf.Counter && cf.CounterReset != nil &&
cf.CounterReset.Strategy != CounterResetStrategyRandom &&
cf.CounterReset.Strategy != CounterResetStrategyProbabilistic &&
cf.CounterReset.Strategy != CounterResetStrategyAfterN {
return errors.New("counter_reset strategy must be one of 'random', 'probabilistic', 'after_n'")
}
return nil
}
func (cf ConfigField) ValidateCounterResetAfterN() error {
if cf.Counter && cf.CounterReset != nil && cf.CounterReset.Strategy == CounterResetStrategyAfterN && cf.CounterReset.ResetAfterN == nil {
return errors.New("counter_reset after_n requires 'reset_after_n' value to be set")
}
return nil
}
func (cf ConfigField) ValidateCounterResetProbabilistic() error {
if cf.Counter && cf.CounterReset != nil && cf.CounterReset.Strategy == CounterResetStrategyProbabilistic && cf.CounterReset.Probability == nil {
return errors.New("counter_reset probabilistic requires 'probability' value to be set")
}
return nil
}
func (cf ConfigField) ValidForDateField() error {
if cf.Period.Abs() > 0 && (cf.Range.From != nil || cf.Range.To != nil) {
return rangeInvalidConfig
}
return nil
}
func (cf ConfigField) ValidCounter() error {
if cf.Counter && (cf.Range.Min != nil || cf.Range.Max != nil) {
return counterInvalidConfig
}
return nil
}
func (r Range) FromAsTime() (time.Time, error) {
if r.From == nil {
return time.Time{}, rangeTimeNotSet
}
return r.From.Time, nil
}
func (r Range) ToAsTime() (time.Time, error) {
if r.To == nil {
return time.Time{}, rangeTimeNotSet
}
return r.To.Time, nil
}
func (r Range) MinAsInt64() (int64, error) {
if r.Min == nil {
return 0, rangeBoundNotSet
}
return int64(*r.Min), nil
}
func (r Range) MaxAsInt64() (int64, error) {
if r.Max == nil {
return math.MaxInt64, rangeBoundNotSet
}
return int64(*r.Max), nil
}
func (r Range) MinAsFloat64() (float64, error) {
if r.Min == nil {
return 0, rangeBoundNotSet
}
return *r.Min, nil
}
func (r Range) MaxAsFloat64() (float64, error) {
if r.Max == nil {
return math.MaxFloat64, rangeBoundNotSet
}
return *r.Max, nil
}
type ConfigFile struct {
Fields []ConfigField `config:"fields"`
}
func LoadConfig(fs afero.Fs, configFile string) (Config, error) {
if len(configFile) == 0 {
return Config{}, nil
}
configFile = os.ExpandEnv(configFile)
if _, err := fs.Stat(configFile); err != nil {
return Config{}, err
}
data, err := afero.ReadFile(fs, configFile)
if err != nil {
return Config{}, err
}
return LoadConfigFromYaml(data)
}
func LoadConfigFromYaml(c []byte) (Config, error) {
cfg, err := yaml.NewConfig(c)
if err != nil {
return Config{}, err
}
var cfgfile ConfigFile
err = cfg.Unpack(&cfgfile)
if err != nil {
return Config{}, err
}
outCfg := Config{
m: make(map[string]ConfigField),
}
for _, c := range cfgfile.Fields {
outCfg.m[c.Name] = c
}
return outCfg, nil
}
func (c Config) GetField(fieldName string) (ConfigField, bool) {
v, ok := c.m[fieldName]
return v, ok
}
func (c Config) SetField(fieldName string, configField ConfigField) {
configField.Name = fieldName
c.m[fieldName] = configField
}