Skip to content

Commit

Permalink
Add regexp sync type (#142)
Browse files Browse the repository at this point in the history
* Add new sync.Regexp type

* Driveby: fix missing and incorrect docs
  • Loading branch information
peterklijn committed Oct 27, 2022
1 parent bec71f5 commit a863d18
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 6 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
74 changes: 68 additions & 6 deletions sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
60 changes: 60 additions & 0 deletions sync/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sync

import (
"fmt"
"regexp"
"testing"
"time"

Expand Down Expand Up @@ -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{})
Expand Down

0 comments on commit a863d18

Please sign in to comment.