Skip to content

Commit

Permalink
use memory storage by default
Browse files Browse the repository at this point in the history
  • Loading branch information
da440dil committed Aug 6, 2019
1 parent e14e43c commit 311b5f8
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 26 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,10 @@ import (
"time"

"github.com/da440dil/go-counter"
gw "github.com/da440dil/go-counter/redis"
"github.com/go-redis/redis"
)

func main() {
client := redis.NewClient(&redis.Options{})
defer client.Close()

c, err := counter.NewCounter(gw.NewGateway(client), 2, time.Millisecond*100)
c, err := counter.New(2, time.Millisecond*100)
if err != nil {
panic(err)
}
Expand Down
30 changes: 23 additions & 7 deletions counter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Package counter provides functions for distributed rate limiting.
package counter

import "time"
import (
"time"

gw "github.com/da440dil/go-counter/memory"
)

// Gateway to storage to store a counter value.
type Gateway interface {
Expand Down Expand Up @@ -30,6 +34,17 @@ const ErrInvalidKey = counterError("counter: key size must be less than or equal
// Option is function returned by functions for setting options.
type Option func(c *Counter) error

// WithGateway sets counter gateway.
// Gateway is gateway to storage to store a counter value.
// If gateway not set counter creates new memory gateway
// with expired keys cleanup every 100 milliseconds.
func WithGateway(v Gateway) Option {
return func(c *Counter) error {
c.gateway = v
return nil
}
}

// WithPrefix sets prefix of a key.
func WithPrefix(v string) Option {
return func(c *Counter) error {
Expand All @@ -49,29 +64,30 @@ type Counter struct {
prefix string
}

// NewCounter creates new Counter.
// Gateway is gateway to storage to store a counter value.
// New creates new Counter.
// Limit is maximum key value, must be greater than 0.
// TTL is TTL of a key, must be greater than or equal to 1 millisecond.
// Options are functional options.
func NewCounter(gateway Gateway, limit int, ttl time.Duration, options ...Option) (*Counter, error) {
func New(limit int, ttl time.Duration, options ...Option) (*Counter, error) {
if limit < 1 {
return nil, ErrInvalidLimit
}
if ttl < time.Millisecond {
return nil, ErrInvalidTTL
}
c := &Counter{
gateway: gateway,
ttl: durationToMilliseconds(ttl),
limit: limit,
ttl: durationToMilliseconds(ttl),
limit: limit,
}
for _, fn := range options {
err := fn(c)
if err != nil {
return nil, err
}
}
if c.gateway == nil {
c.gateway = gw.New(time.Millisecond * 100)
}
return c, nil
}

Expand Down
27 changes: 17 additions & 10 deletions counter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ func TestNewCounter(t *testing.T) {
gw := &gwMock{}

t.Run("ErrInvalidLimit", func(t *testing.T) {
_, err := NewCounter(gw, 0, time.Microsecond)
_, err := New(0, time.Microsecond, WithGateway(gw))
assert.Error(t, err)
assert.Equal(t, ErrInvalidLimit, err)
})

t.Run("ErrInvalidTTL", func(t *testing.T) {
_, err := NewCounter(gw, Limit, time.Microsecond)
_, err := New(Limit, time.Microsecond, WithGateway(gw))
assert.Error(t, err)
assert.Equal(t, ErrInvalidTTL, err)
})

t.Run("success", func(t *testing.T) {
c, err := NewCounter(gw, Limit, TTL)
c, err := New(Limit, TTL, WithGateway(gw))
assert.NoError(t, err)
assert.IsType(t, &Counter{}, c)
})
Expand All @@ -55,13 +55,13 @@ func TestOptions(t *testing.T) {
gw := &gwMock{}

t.Run("ErrInvaldKey", func(t *testing.T) {
_, err := NewCounter(gw, Limit, TTL, WithPrefix(invalidKey))
_, err := New(Limit, TTL, WithPrefix(invalidKey), WithGateway(gw))
assert.Error(t, err)
assert.Equal(t, ErrInvalidKey, err)
})

t.Run("success", func(t *testing.T) {
c, err := NewCounter(gw, Limit, TTL, WithPrefix(""))
c, err := New(Limit, TTL, WithPrefix(""), WithGateway(gw))
assert.NoError(t, err)
assert.IsType(t, &Counter{}, c)
})
Expand All @@ -73,7 +73,7 @@ func TestCounter(t *testing.T) {
t.Run("ErrInvaldKey", func(t *testing.T) {
gw := &gwMock{}

c, err := NewCounter(gw, Limit, TTL)
c, err := New(Limit, TTL, WithGateway(gw))
assert.NoError(t, err)

v, err := c.Count(invalidKey)
Expand All @@ -87,7 +87,7 @@ func TestCounter(t *testing.T) {
gw := &gwMock{}
gw.On("Incr", Key, ttl).Return(-1, 42, e)

c, err := NewCounter(gw, Limit, TTL)
c, err := New(Limit, TTL, WithGateway(gw))
assert.NoError(t, err)

v, err := c.Count(Key)
Expand All @@ -102,7 +102,7 @@ func TestCounter(t *testing.T) {
gw := &gwMock{}
gw.On("Incr", Key, ttl).Return(Limit+1, et, nil)

c, err := NewCounter(gw, Limit, TTL)
c, err := New(Limit, TTL, WithGateway(gw))
assert.NoError(t, err)

v, err := c.Count(Key)
Expand All @@ -116,7 +116,7 @@ func TestCounter(t *testing.T) {
gw := &gwMock{}
gw.On("Incr", Key, ttl).Return(Limit, 42, nil)

c, err := NewCounter(gw, Limit, TTL)
c, err := New(Limit, TTL, WithGateway(gw))
assert.NoError(t, err)

v, err := c.Count(Key)
Expand All @@ -137,4 +137,11 @@ func TestCounterError(t *testing.T) {
v := "any"
err := counterError(v)
assert.Equal(t, v, err.Error())
}
}

func TestDefaultGateway(t *testing.T) {
c, err := New(Limit, TTL)
assert.NoError(t, err)
assert.IsType(t, &Counter{}, c)
assert.NotNil(t, c.gateway)
}
69 changes: 66 additions & 3 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,78 @@ import (
"time"

"github.com/da440dil/go-counter"
gw "github.com/da440dil/go-counter/redis"
gm "github.com/da440dil/go-counter/memory"
gr "github.com/da440dil/go-counter/redis"
"github.com/go-redis/redis"
)

func Example() {
func ExampleCounter_withoutGateway() {
c, err := counter.New(2, time.Millisecond*100)
if err != nil {
panic(err)
}
key := "key"
var wg sync.WaitGroup
count := func() {
wg.Add(1)
go func() {
v, err := c.Count(key)
if err == nil {
fmt.Printf("Counter has counted the key, remainder %v\n", v)
} else {
if e, ok := err.(counter.TTLError); ok {
fmt.Printf("Counter has reached the limit, retry after %v\n", e.TTL())
} else {
panic(err)
}
}
wg.Done()
}()
}

count() // Counter has counted the key, remainder 1
count() // Counter has counted the key, remainder 0
count() // Counter has reached the limit, retry after 100ms
wg.Wait()
}

func ExampleCounter_memoryGateway() {
g := gm.New(time.Millisecond * 20)
c, err := counter.New(2, time.Millisecond*100, counter.WithGateway(g))
if err != nil {
panic(err)
}
key := "key"
var wg sync.WaitGroup
count := func() {
wg.Add(1)
go func() {
v, err := c.Count(key)
if err == nil {
fmt.Printf("Counter has counted the key, remainder %v\n", v)
} else {
if e, ok := err.(counter.TTLError); ok {
fmt.Printf("Counter has reached the limit, retry after %v\n", e.TTL())
} else {
panic(err)
}
}
wg.Done()
}()
}

count() // Counter has counted the key, remainder 1
count() // Counter has counted the key, remainder 0
count() // Counter has reached the limit, retry after 100ms
wg.Wait()
}

func ExampleCounter_redisGateway() {
client := redis.NewClient(&redis.Options{})
defer client.Close()

c, err := counter.NewCounter(gw.NewGateway(client), 2, time.Millisecond*100)
g := gr.New(client)
c, err := counter.New(2, time.Millisecond*100, counter.WithGateway(g))
if err != nil {
panic(err)
}
Expand Down

0 comments on commit 311b5f8

Please sign in to comment.