diff --git a/README.md b/README.md index 7628c9dd..db6a71e8 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ The fields have to be one of the types that the sync package supports in order t - sync.Bool, allows for concurrent bool manipulation - sync.Secret, allows for concurrent secret manipulation. Secrets can only be strings - sync.TimeDuration, allows for concurrent time.duration manipulation. +- sync.Regexp, allows for concurrent *regexp.Regexp manipulation. - sync.StringMap, allows for concurrent map[string]string manipulation. +- sync.StringSlice, allows for concurrent []string manipulation. For sensitive configuration (passwords, tokens, etc.) that shouldn't be printed in log, you can use the `Secret` flavor of `sync` types. If one of these is selected, then at harvester log instead of the real value the text `***` will be displayed. diff --git a/sync/sync.go b/sync/sync.go index 48d965ea..c90d79db 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/json" "fmt" + "regexp" "strconv" "strings" "sync" @@ -38,7 +39,7 @@ func (b *Bool) MarshalJSON() ([]byte, error) { return json.Marshal(b.value) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (b *Bool) UnmarshalJSON(d []byte) error { b.rw.RLock() defer b.rw.RUnlock() @@ -92,7 +93,7 @@ func (i *Int64) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (i *Int64) UnmarshalJSON(d []byte) error { i.rw.RLock() defer i.rw.RUnlock() @@ -143,7 +144,7 @@ func (f *Float64) MarshalJSON() ([]byte, error) { return json.Marshal(f.value) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (f *Float64) UnmarshalJSON(d []byte) error { f.rw.RLock() defer f.rw.RUnlock() @@ -194,7 +195,7 @@ func (s *String) MarshalJSON() ([]byte, error) { return json.Marshal(s.value) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (s *String) UnmarshalJSON(d []byte) error { s.rw.RLock() defer s.rw.RUnlock() @@ -241,7 +242,7 @@ func (s *TimeDuration) MarshalJSON() ([]byte, error) { return json.Marshal(s.value) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (s *TimeDuration) UnmarshalJSON(d []byte) error { s.rw.RLock() defer s.rw.RUnlock() @@ -290,7 +291,7 @@ func (s *Secret) MarshalJSON() (out []byte, err error) { return json.Marshal(s.String()) } -// MarshalJSON returns the JSON encoding of the value. +// UnmarshalJSON returns the JSON encoding of the value. func (s *Secret) UnmarshalJSON(d []byte) error { s.rw.RLock() defer s.rw.RUnlock() @@ -308,6 +309,67 @@ func (s *Secret) SetString(val string) error { return nil } +type Regexp struct { + rw sync.RWMutex + value *regexp.Regexp +} + +// Get returns the internal value. +func (r *Regexp) Get() *regexp.Regexp { + r.rw.RLock() + defer r.rw.RUnlock() + return r.value +} + +// Set a value. +func (r *Regexp) Set(value *regexp.Regexp) { + r.rw.Lock() + defer r.rw.Unlock() + r.value = value +} + +// MarshalJSON returns the JSON encoding of the value. +func (r *Regexp) MarshalJSON() ([]byte, error) { + r.rw.RLock() + defer r.rw.RUnlock() + return json.Marshal(r.value.String()) +} + +// UnmarshalJSON returns the JSON encoding of the value. +func (r *Regexp) UnmarshalJSON(d []byte) error { + var str string + err := json.Unmarshal(d, &str) + if err != nil { + fmt.Println("json unmarshal") + return err + } + regex, err := regexp.Compile(str) + if err != nil { + fmt.Println("regex compile") + return err + } + r.Set(regex) + return nil +} + +// String returns a string representation of the value. +func (r *Regexp) String() string { + r.rw.RLock() + defer r.rw.RUnlock() + return r.value.String() +} + +// +// SetString parses and sets a value from string type. +func (r *Regexp) SetString(val string) error { + compiled, err := regexp.Compile(val) + if err != nil { + return err + } + r.Set(compiled) + return nil +} + // StringMap is a map[string]string type with concurrent access support. type StringMap struct { rw sync.RWMutex diff --git a/sync/sync_test.go b/sync/sync_test.go index 2e5376bd..bb5a16a3 100644 --- a/sync/sync_test.go +++ b/sync/sync_test.go @@ -2,6 +2,7 @@ package sync import ( "fmt" + "regexp" "testing" "time" @@ -207,6 +208,65 @@ func TestTimeDuration_UnmarshalJSON(t *testing.T) { assert.Equal(t, time.Duration(1), b.Get()) } +func TestRegexp(t *testing.T) { + regex := regexp.MustCompile(".*") + + var r Regexp + ch := make(chan struct{}) + go func() { + r.Set(regex) + ch <- struct{}{} + }() + <-ch + assert.Equal(t, regex, r.Get()) + assert.Equal(t, regex.String(), r.String()) + + d, err := r.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, `".*"`, string(d)) +} + +func TestRegexp_UnmarshalJSON(t *testing.T) { + var r Regexp + err := r.UnmarshalJSON([]byte(`invalid json`)) + assert.Error(t, err) + assert.Nil(t, r.Get()) + + // Invalid regex: + err = r.UnmarshalJSON([]byte(`"[a-z]++"`)) + assert.Error(t, err) + assert.Nil(t, r.Get()) + + err = r.UnmarshalJSON([]byte(`"[a-z0-7]+"`)) + assert.NoError(t, err) + assert.Equal(t, regexp.MustCompile("[a-z0-7]+"), r.Get()) +} + +func TestRegexp_SetString(t *testing.T) { + tests := []struct { + name string + input string + result *regexp.Regexp + throwsError bool + }{ + {"empty", "", regexp.MustCompile(""), false}, + {"simple regex", ".*", regexp.MustCompile(".*"), false}, + {"invalid regex", "[0-9]++", nil, true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sr := Regexp{} + + err := sr.SetString(test.input) + if test.throwsError { + assert.Error(t, err) + } + + assert.Equal(t, test.result, sr.Get()) + }) + } +} + func TestStringMap(t *testing.T) { var sm StringMap ch := make(chan struct{})