Rename cache drivers, add memory driver, add tests and drone integration
continuous-integration/drone/push Build was killed Details

This commit is contained in:
Tyler 2019-10-02 20:17:34 -04:00
parent 43fe2d6511
commit b396128a23
10 changed files with 411 additions and 32 deletions

13
.drone.yml Normal file
View File

@ -0,0 +1,13 @@
kind: pipeline
type: docker
name: default
steps:
- name: redis
image: redis:latest
- name: memcache
image: memcached:latest
- name: test
image: golang
commands:
- go test

View File

@ -2,7 +2,9 @@ package cache
import ( import (
"errors" "errors"
"github.com/vmihailenco/msgpack/v4"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -10,6 +12,7 @@ import (
const ( const (
Memcache = "memcache" Memcache = "memcache"
Redis = "redis" Redis = "redis"
Memory = "memory"
) )
var ( var (
@ -18,7 +21,7 @@ var (
type CacheInterface interface { type CacheInterface interface {
Has(key string) bool Has(key string) bool
Get(key string) ([]byte, error) Get(key string, dst ...interface{}) ([]byte, error)
Set(key string, val interface{}, ttl time.Duration) (err error) Set(key string, val interface{}, ttl time.Duration) (err error)
Del(key string) error Del(key string) error
} }
@ -42,7 +45,64 @@ func New(uri string) (CacheInterface, error) {
return NewMemcacheCache(MemcacheSettings{ return NewMemcacheCache(MemcacheSettings{
Servers: strings.Split(u.Host, ","), Servers: strings.Split(u.Host, ","),
}) })
case Memory:
cleanupTime := query.Get("cleanupTime")
if cleanupTime == "" {
cleanupTime = "30"
}
i, err := strconv.Atoi(cleanupTime)
if err != nil {
return nil, err
}
return NewMemoryCache(time.Duration(i) * time.Second)
} }
return nil, ErrInvalidDriver return nil, ErrInvalidDriver
} }
func encodeValue(val interface{}) ([]byte, error) {
var v []byte
if b, ok := val.([]byte); ok {
v = b
} else if s, ok := val.(string); ok {
b = []byte(s)
} else {
b, err := msgpack.Marshal(val)
if err != nil {
return nil, err
}
v = b
}
return v, nil
}
func decodeDst(b []byte, v interface{}) ([]byte, error) {
switch v := v.(type) {
case *[]byte:
if v != nil {
*v = b
return b, nil
}
case *string:
if v != nil {
*v = string(b)
return b, nil
}
}
err := msgpack.Unmarshal(b, v)
if err != nil {
return nil, err
}
return b, nil
}

2
go.mod
View File

@ -5,4 +5,6 @@ go 1.12
require ( require (
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/vmihailenco/msgpack/v4 v4.2.0
) )

33
go.sum
View File

@ -1,4 +1,37 @@
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 h1:mjZV3MTu2A5gwfT5G9IIiLGdwZNciyVq5qqnmJJZ2JI= github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 h1:mjZV3MTu2A5gwfT5G9IIiLGdwZNciyVq5qqnmJJZ2JI=
github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0/go.mod h1:pMYMxVaKJqCDC1JUg/XbPJ4/fSazB25zORpFzqsIGIc= github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0/go.mod h1:pMYMxVaKJqCDC1JUg/XbPJ4/fSazB25zORpFzqsIGIc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/vmihailenco/msgpack/v4 v4.2.0 h1:c4L4gd938BvSjSsfr9YahJcvasEf5JZ9W7rcEXfgyys=
github.com/vmihailenco/msgpack/v4 v4.2.0/go.mod h1:Mu3B7ZwLd5nNOLVOKt9DecVl7IVg0xkDiEjk6CwMrww=
github.com/vmihailenco/tagparser v0.1.0 h1:u6yzKTY6gW/KxL/K2NTEQUOSXZipyGiIRarGjJKmQzU=
github.com/vmihailenco/tagparser v0.1.0/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,7 +1,6 @@
package cache package cache
import ( import (
"encoding/json"
"github.com/bradfitz/gomemcache/memcache" "github.com/bradfitz/gomemcache/memcache"
"time" "time"
) )
@ -28,33 +27,27 @@ func (mc *MemcacheCache) Has(key string) bool {
return err != nil return err != nil
} }
func (mc *MemcacheCache) Get(key string) ([]byte, error) { func (mc *MemcacheCache) Get(key string, dst ...interface{}) ([]byte, error) {
item, err := mc.backend.Get(key) item, err := mc.backend.Get(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(dst) > 0 && dst[0] != nil {
return decodeDst(item.Value, dst[0])
}
return item.Value, nil return item.Value, nil
} }
func (mc *MemcacheCache) Set(key string, val interface{}, ttl time.Duration) error { func (mc *MemcacheCache) Set(key string, val interface{}, ttl time.Duration) error {
var v []byte v, err := encodeValue(val)
if b, ok := val.([]byte); ok {
v = b
} else if s, ok := val.(string); ok {
b = []byte(s)
} else {
b, err := json.Marshal(val)
if err != nil { if err != nil {
return err return err
} }
v = b
}
return mc.backend.Set(&memcache.Item{Key: key, Value: v, Expiration: int32(ttl.Seconds())}) return mc.backend.Set(&memcache.Item{Key: key, Value: v, Expiration: int32(ttl.Seconds())})
} }

38
memcache_test.go Normal file
View File

@ -0,0 +1,38 @@
package cache
import (
"testing"
"time"
)
func Test_MemcacheURI(t *testing.T) {
cache, err := New("memcache://memcache")
if err != nil {
t.Fatal("Error creating cache:", err)
}
if _, ok := cache.(*MemcacheCache); !ok {
t.Fatal("Cache is not instance of MemcacheCache")
}
}
func Test_MemcacheStoreGet(t *testing.T) {
cache, err := New("memcache://memcache")
if err != nil {
t.Fatal("Error creating cache:", err)
}
obj := "test"
cache.Set("test", obj, time.Minute)
var new string
cache.Get("test", &new)
if obj != new {
t.Fatal("Expected", obj, "got", new)
}
}

165
memory.go Normal file
View File

@ -0,0 +1,165 @@
package cache
import (
"errors"
"github.com/patrickmn/go-cache"
"reflect"
"time"
)
var (
MemoryCacheNotExists = errors.New("item does not exist")
)
type MemoryCache struct {
c *cache.Cache
}
func NewMemoryCache(cleanupTime time.Duration) (CacheInterface, error) {
c := cache.New(1*time.Minute, cleanupTime)
return &MemoryCache{c: c}, nil
}
func (mc *MemoryCache) Has(key string) bool {
_, exists := mc.c.Get(key)
return exists
}
func (mc *MemoryCache) Get(key string, dst ...interface{}) ([]byte, error) {
item, exists := mc.c.Get(key)
if !exists {
return nil, MemoryCacheNotExists
}
v := dst[0]
switch v := v.(type) {
case *string:
if v != nil {
*v = item.(string)
return nil, nil
}
case *[]byte:
if v != nil {
*v = item.([]byte)
return nil, nil
}
case *int:
if v != nil {
*v = item.(int)
return nil, nil
}
case *int8:
if v != nil {
*v = item.(int8)
return nil, nil
}
case *int16:
if v != nil {
*v = item.(int16)
return nil, nil
}
case *int32:
if v != nil {
*v = item.(int32)
return nil, nil
}
case *int64:
if v != nil {
*v = item.(int64)
return nil, nil
}
case *uint:
if v != nil {
*v = item.(uint)
return nil, nil
}
case *uint8:
if v != nil {
*v = item.(uint8)
return nil, nil
}
case *uint16:
if v != nil {
*v = item.(uint16)
return nil, nil
}
case *uint32:
if v != nil {
*v = item.(uint32)
return nil, nil
}
case *uint64:
if v != nil {
*v = item.(uint64)
return nil, nil
}
case *bool:
if v != nil {
*v = item.(bool)
return nil, nil
}
case *float32:
if v != nil {
*v = item.(float32)
return nil, nil
}
case *float64:
if v != nil {
*v = item.(float64)
return nil, nil
}
case *[]string:
*v = item.([]string)
return nil, nil
case *map[string]string:
*v = item.(map[string]string)
return nil, nil
case *map[string]interface{}:
*v = item.(map[string]interface{})
return nil, nil
case *time.Duration:
if v != nil {
*v = item.(time.Duration)
return nil, nil
}
case *time.Time:
if v != nil {
*v = item.(time.Time)
return nil, nil
}
}
vv := reflect.ValueOf(dst[0])
if !vv.IsValid() {
return nil, errors.New("dst pointer is not valid")
}
if vv.Kind() != reflect.Ptr {
return nil, errors.New("dst pointer is not a pointer")
}
vv = vv.Elem()
if !vv.IsValid() {
return nil, errors.New("dst pointer is not a valid element")
}
vv.Set(reflect.ValueOf(item))
return nil, nil
}
func (mc *MemoryCache) Set(key string, val interface{}, ttl time.Duration) error {
mc.c.Set(key, val, ttl)
return nil
}
func (mc *MemoryCache) Del(key string) error {
mc.c.Delete(key)
return nil
}

38
memory_test.go Normal file
View File

@ -0,0 +1,38 @@
package cache
import (
"testing"
"time"
)
func Test_MemoryURI(t *testing.T) {
cache, err := New("memory://")
if err != nil {
t.Fatal("Error creating cache:", err)
}
if _, ok := cache.(*MemoryCache); !ok {
t.Fatal("Cache is not instance of MemoryCache")
}
}
func Test_MemoryStoreGet(t *testing.T) {
cache, err := New("memory://")
if err != nil {
t.Fatal("Error creating cache:", err)
}
obj := "test"
cache.Set("test", obj, time.Minute)
var new string
cache.Get("test", &new)
if obj != new {
t.Fatal("Expected", obj, "got", new)
}
}

View File

@ -1,7 +1,6 @@
package cache package cache
import ( import (
"encoding/json"
"github.com/hoisie/redis" "github.com/hoisie/redis"
"time" "time"
) )
@ -31,27 +30,27 @@ func (rc *RedisCache) Has(key string) bool {
return b return b
} }
func (rc *RedisCache) Get(key string) ([]byte, error) { func (rc *RedisCache) Get(key string, dst ...interface{}) ([]byte, error) {
return rc.c.Get(key) b, err := rc.c.Get(key)
if err != nil {
return nil, err
}
if len(dst) > 0 && dst[0] != nil {
return decodeDst(b, dst[0])
}
return b, err
} }
func (rc *RedisCache) Set(key string, val interface{}, ttl time.Duration) error { func (rc *RedisCache) Set(key string, val interface{}, ttl time.Duration) error {
var v []byte v, err := encodeValue(val)
if b, ok := val.([]byte); ok {
v = b
} else if s, ok := val.(string); ok {
b = []byte(s)
} else {
b, err := json.Marshal(val)
if err != nil { if err != nil {
return err return err
} }
v = b
}
return rc.c.Setex(key, int64(ttl.Seconds()), v) return rc.c.Setex(key, int64(ttl.Seconds()), v)
} }

38
redis_test.go Normal file
View File

@ -0,0 +1,38 @@
package cache
import (
"testing"
"time"
)
func Test_RedisURI(t *testing.T) {
cache, err := New("redis://redis")
if err != nil {
t.Fatal("Error creating cache:", err)
}
if _, ok := cache.(*RedisCache); !ok {
t.Fatal("Cache is not instance of RedisCache")
}
}
func Test_RedisStoreGet(t *testing.T) {
cache, err := New("redis://redis")
if err != nil {
t.Fatal("Error creating cache:", err)
}
obj := "test"
cache.Set("test", obj, time.Minute)
var new string
cache.Get("test", &new)
if obj != new {
t.Fatal("Expected", obj, "got", new)
}
}