Skip to content

Commit

Permalink
more updates and refactoring to handle TODOs
Browse files Browse the repository at this point in the history
  • Loading branch information
mjc-gh committed Jan 6, 2024
1 parent 7a8a2a1 commit 217094c
Show file tree
Hide file tree
Showing 21 changed files with 298 additions and 109 deletions.
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ together over Redis.

## Usage

To Kredis, you must first configure a connection. The default connection
configuration is named `"shared"`. `SetConfiguration` expects a config
name string, optional namespace (pass an empty string for no namespace),
and a Redis URL connection string.
To use Kredis, you must first configure a connection. The default
connection configuration is named `"shared"`. `SetConfiguration` expects
a config name string, optional namespace (pass an empty string for no
namespace), and a Redis URL connection string.

```go
kredis.SetConfiguration("shared", "ns", "redis://localhost:6379/2")
Expand Down Expand Up @@ -122,17 +122,19 @@ err = enum.SetValue("error")

### Flag

By default the `Mark()` function does not call `SET` with `nx`

```go
flag, err := kredis.NewFlag("flag")
flag.IsMarked() // EXISTS flag
flag.IsMarked() // EXISTS flag
// false
err = flag.Mark() // SETNX flag 1
err = flag.Mark() // SETNX flag 1
// nil
flag.IsMarked() // EXISTS flag
flag.IsMarked() // EXISTS flag
// true
err = flag.Remove() // DEL flag
err = flag.Remove() // DEL flag

flag.Mark(kredis.WithFlagMarkExpiry("1s")) // SET flag 1 ex 1 nx
flag.Mark(kredis.WithFlagMarkExpiry("1s")) // SET flag 1 ex 1
flag.IsMarked() // EXISTS flag
// true

Expand All @@ -142,27 +144,26 @@ flag.IsMarked() // EXISTS flag
// false
```

You can use the `WithFlagMarkForced` option function to always set the
flag (and thus not use `SET` with `nx`)
The `SoftMark()` function will call set with `NX`

```go
flag.Mark(kredis.WithFlagMarkExpiry("1s")) // SET flag 1 ex 1 nx
flag.Mark(kredis.WithFlagMarkExpiry("10s"), kredis.WithFlagMarkForced())
// SET flag 1 ex 10
flag.IsMarked() // EXISTS flag
flag.SoftMark(kredis.WithFlagMarkExpiry("1s")) // SET flag 1 ex 1 nx
flag.SoftMark(kredis.WithFlagMarkExpiry("10s")) // SET flag 1 ex 10 nx
flag.IsMarked() // EXISTS flag
// true

time.Sleep(2 * time.Second)

flag.IsMarked() // EXISTS flag
flag.IsMarked() // EXISTS flag
// true
```

### Limiter

The `Limiter` type is based off the `Counter` type and provides a
simple rate limiting with a failsafe on Reids errors. See the original
[Rails PR for more details](https://github.com/rails/kredis/pull/136).
simple rate limiting type with a failsafe on Reids errors. See the
original [Rails PR for more
details](https://github.com/rails/kredis/pull/136).

`IsExceeded()` will return `false` in the event of a Redis error.
`Poke()` does return an error, but it can easily be ignored in Go.
Expand Down
13 changes: 6 additions & 7 deletions connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,19 @@ func SetCommandLogging(v bool) {
// cmdLogging interface
// func SetCommandLogger(userLogger cmdLogging)

func SetConfiguration(name, namespace, url string) error {
opt, err := redis.ParseURL(url)
type RedisOption func(*redis.Options)

func SetConfiguration(name, namespace, url string, opts ...RedisOption) error {
opt, err := redis.ParseURL(url)
if err != nil {
return err
}

// TODO handle redis settings
//opt.ReadTimeout
//opt.WriteTimeout
//opt.PoolSize
for _, optFn := range opts {
optFn(opt)
}

configs[name] = &config{options: opt, namespace: namespace}

return nil
}

Expand Down
22 changes: 22 additions & 0 deletions connections_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package kredis

import (
"time"

"github.com/redis/go-redis/v9"
)

func (s *KredisTestSuite) TestGetConfigurationUsesCacheMap() {
_, ok := connections["shared"]
s.False(ok)
Expand All @@ -10,3 +16,19 @@ func (s *KredisTestSuite) TestGetConfigurationUsesCacheMap() {
c2, _, e := getConnection("shared")
s.Same(c, c2)
}

func (s *KredisTestSuite) TestSetConfigurationWithRedisOptions() {
SetConfiguration("test", "", "redis://localhost:6379/0", func(opts *redis.Options) {
opts.ReadTimeout = time.Duration(1)
opts.WriteTimeout = time.Duration(1)
opts.PoolSize = 1
})

cfg := configs["test"]

s.Equal(time.Duration(1), cfg.options.ReadTimeout)
s.Equal(time.Duration(1), cfg.options.WriteTimeout)
s.Equal(1, cfg.options.PoolSize)

delete(configs, "test")
}
2 changes: 0 additions & 2 deletions cycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ type Cycle struct {
values []string
}

// TODO add default value factory

func NewCycle(key string, values []string, opts ...ProxyOption) (*Cycle, error) {
proxy, err := NewProxy(key, opts...)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Enum struct {
}

var EnumEmptyValues = errors.New("values cannot be empty")
var EnumExpiryNotSupported = errors.New("cannot use WithExpiry with Enum")
var EnumInvalidValue = errors.New("invalid enum value")

func NewEnum(key string, defaultValue string, values []string, opts ...ProxyOption) (*Enum, error) {
Expand All @@ -24,8 +25,9 @@ func NewEnum(key string, defaultValue string, values []string, opts ...ProxyOpti
return nil, err
}

// TODO return runtime error if expiresIn option is used -- this option just
// doesn't fit well into this Kredis data structure
if proxy.expiresIn != 0 {
return nil, EnumExpiryNotSupported
}

enum := &Enum{Proxy: *proxy, defaultValue: defaultValue, values: map[string]bool{}}
for _, value := range values {
Expand Down
6 changes: 6 additions & 0 deletions enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ func (s *KredisTestSuite) TestEnumWithEmptyValues() {
s.Error(err)
s.Equal(EnumEmptyValues, err)
}

func (s *KredisTestSuite) TestEnumWithExpiry() {
_, err := NewEnum("key", "go", []string{"go"}, WithExpiry("1ms"))
s.Error(err)
s.Equal(EnumExpiryNotSupported, err)
}
6 changes: 3 additions & 3 deletions examples/flag/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ func main() {
fmt.Println(flag.IsMarked())
flag.Remove()

flag.Mark(kredis.WithFlagMarkExpiry("1s"))
flag.Mark(kredis.WithFlagExpiry("1s"))
fmt.Println(flag.IsMarked())

time.Sleep(2 * time.Second)

fmt.Println(flag.IsMarked())

flag.Mark(kredis.WithFlagMarkExpiry("1s"))
flag.Mark(kredis.WithFlagMarkExpiry("10s"), kredis.WithFlagMarkForced())
flag.Mark(kredis.WithFlagExpiry("1s"))
flag.SoftMark(kredis.WithFlagExpiry("10s"))
fmt.Println(flag.IsMarked())

time.Sleep(2 * time.Second)
Expand Down
43 changes: 24 additions & 19 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ type Flag struct {

type FlagMarkOptions struct {
expiresIn time.Duration
force bool
}

type FlagMarkOption func(*FlagMarkOptions)

// TODO add default value factory

func NewFlag(key string, opts ...ProxyOption) (*Flag, error) {
proxy, err := NewProxy(key, opts...)
if err != nil {
Expand All @@ -28,22 +25,36 @@ func NewFlag(key string, opts ...ProxyOption) (*Flag, error) {
return &Flag{Proxy: *proxy}, nil
}

// TODO this should return true/false if flag was set or not
// true == flag.mark(expires_in: 1.second, force: false) #=> SET myflag 1 EX 1 NX
// false == flag.mark(expires_in: 10.seconds, force: false) #=> SET myflag 10 EX 1 NX
func NewMarkedFlag(key string, opts ...ProxyOption) (*Flag, error) {
flag, err := NewFlag(key, opts...)
if err != nil {
return nil, err
}

err = flag.Mark()
if err != nil {
return nil, err
}

return flag, nil
}

func (f *Flag) Mark(opts ...FlagMarkOption) error {
options := FlagMarkOptions{force: false}
options := FlagMarkOptions{f.expiresIn}
for _, opt := range opts {
opt(&options)
}

if options.force {
f.client.Set(f.ctx, f.key, 1, options.expiresIn)
} else {
f.client.SetNX(f.ctx, f.key, 1, options.expiresIn)
return f.client.Set(f.ctx, f.key, 1, options.expiresIn).Err()
}

func (f *Flag) SoftMark(opts ...FlagMarkOption) error {
options := FlagMarkOptions{f.expiresIn}
for _, opt := range opts {
opt(&options)
}

return nil
return f.client.SetNX(f.ctx, f.key, 1, options.expiresIn).Err()
}

func (f *Flag) IsMarked() bool {
Expand All @@ -62,7 +73,7 @@ func (f *Flag) Remove() (err error) {

// Mark() function optional configuration functions

func WithFlagMarkExpiry(expires string) FlagMarkOption {
func WithFlagExpiry(expires string) FlagMarkOption {
return func(o *FlagMarkOptions) {
duration, err := time.ParseDuration(expires)
if err != nil {
Expand All @@ -72,9 +83,3 @@ func WithFlagMarkExpiry(expires string) FlagMarkOption {
o.expiresIn = duration
}
}

func WithFlagMarkForced() FlagMarkOption {
return func(o *FlagMarkOptions) {
o.force = true
}
}
16 changes: 11 additions & 5 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,37 @@ func (s *KredisTestSuite) TestFlag() {
s.False(flag.IsMarked())
}

func (s *KredisTestSuite) TestMarkedFlag() {
flag, err := NewMarkedFlag("flag")
s.NoError(err)
s.True(flag.IsMarked())
}

// TODO refactor test to check redis cmds with some sort of test env
// ProcessHook
func (s *KredisTestSuite) TestFlagWithMarkOptions() {
s.T().Skip() // TODO this test depends to much on timing :(
s.T().Skip()

flag, _ := NewFlag("flag_ex")

s.NoError(flag.Mark(WithFlagMarkExpiry("2ms")))
s.NoError(flag.Mark(WithFlagExpiry("2ms")))
s.True(flag.IsMarked())

time.Sleep(1 * time.Millisecond)

s.NoError(flag.Mark(WithFlagMarkExpiry("2ms")))
s.NoError(flag.Mark(WithFlagExpiry("2ms")))
s.True(flag.IsMarked())

time.Sleep(2 * time.Millisecond)

s.False(flag.IsMarked())

s.NoError(flag.Mark(WithFlagMarkExpiry("2ms")))
s.NoError(flag.Mark(WithFlagExpiry("2ms")))
s.True(flag.IsMarked())

time.Sleep(1 * time.Millisecond)

s.NoError(flag.Mark(WithFlagMarkExpiry("5ms"), WithFlagMarkForced()))
s.NoError(flag.Mark(WithFlagExpiry("5ms")))
s.True(flag.IsMarked())

time.Sleep(2 * time.Millisecond)
Expand Down
37 changes: 35 additions & 2 deletions kredis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/redis/go-redis/v9"
)

// TODO does this need to be exported??
// type KredisJSON []byte
type KredisJSON struct {
s string
}
Expand Down Expand Up @@ -170,3 +168,38 @@ func copyCmdSliceTo[T KredisTyped](slice []interface{}, dst []T) (total int64) {

return
}

// used in most collection types for copying a slice of interfaces to a slice
// of KredisTyped.
func copyCmdSliceToMap[T KredisTyped](slice []interface{}, dst map[T]struct{}, typed *T) (total int64) {
for _, e := range slice {
switch any(*typed).(type) {
case bool:
b, _ := strconv.ParseBool(e.(string))

dst[any(b).(T)] = struct{}{}
case int:
n, _ := strconv.Atoi(e.(string))

dst[any(n).(T)] = struct{}{}
case float64:
f, _ := strconv.ParseFloat(e.(string), 64)

dst[any(f).(T)] = struct{}{}
case KredisJSON:
j := NewKredisJSON(e.(string))

dst[any(*j).(T)] = struct{}{}
case time.Time:
t, _ := time.Parse(time.RFC3339Nano, e.(string))

dst[any(t).(T)] = struct{}{}
default:
dst[any(e).(T)] = struct{}{}
}

total += 1
}

return
}
16 changes: 14 additions & 2 deletions kredis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (suite *KredisTestSuite) SetupTest() {
// TODO use a unique namespace for each test (thus potentially enabling
// parallel tests)
SetConfiguration("shared", "ns", "redis://localhost:6379/2")
SetConfiguration("badconn", "ns", "redis://localhost:1234/0")
SetConfiguration("badconn", "", "redis://localhost:1234/0")

SetCommandLogging(true)
}
Expand All @@ -41,4 +41,16 @@ func TestKredisTestSuit(t *testing.T) {
suite.Run(t, new(KredisTestSuite))
}

// TODO tests for KredisJSON struct ??
func (s *KredisTestSuite) TestKredisJSON() {
kj := NewKredisJSON(`{"a":1}`)

s.Equal(`{"a":1}`, kj.String())

var data interface{}
err := kj.Unmarshal(&data)
s.NoError(err)

obj, ok := data.(map[string]interface{})
s.True(ok)
s.Equal(1.0, obj["a"])
}
Loading

0 comments on commit 217094c

Please sign in to comment.