-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from Sovietaced/logging
Add support for redis backed map
- Loading branch information
Showing
8 changed files
with
281 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package mapp | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/redis/go-redis/v9" | ||
"github.com/sovietaced/go-redisson/marshal" | ||
) | ||
|
||
type Options[K any, V any] struct { | ||
namespace string | ||
keyMarshaler marshal.Marshaler[K] | ||
valueMarshaler marshal.Marshaler[V] | ||
} | ||
|
||
func defaultOptions[K any, V any]() *Options[K, V] { | ||
opts := &Options[K, V]{} | ||
WithKeyMarshaler[K, V](&marshal.JsonMarshaler[K]{})(opts) | ||
WithValueMarshaler[K, V](&marshal.JsonMarshaler[V]{})(opts) | ||
return opts | ||
} | ||
|
||
type Option[K any, V any] func(*Options[K, V]) | ||
|
||
func WithNamespace[K any, V any](namespace string) Option[K, V] { | ||
return func(mo *Options[K, V]) { | ||
mo.namespace = namespace | ||
} | ||
} | ||
|
||
func WithKeyMarshaler[K any, V any](marshaler marshal.Marshaler[K]) Option[K, V] { | ||
return func(mo *Options[K, V]) { | ||
mo.keyMarshaler = marshaler | ||
} | ||
} | ||
|
||
func WithValueMarshaler[K any, V any](marshaler marshal.Marshaler[V]) Option[K, V] { | ||
return func(mo *Options[K, V]) { | ||
mo.valueMarshaler = marshaler | ||
} | ||
} | ||
|
||
type Mapp[K any, V any] struct { | ||
namespace string | ||
client redis.UniversalClient | ||
keyMarshaler marshal.Marshaler[K] | ||
valueMarshaler marshal.Marshaler[V] | ||
} | ||
|
||
func NewMapp[K any, V any](client redis.UniversalClient, options ...Option[K, V]) *Mapp[K, V] { | ||
opts := defaultOptions[K, V]() | ||
for _, option := range options { | ||
option(opts) | ||
} | ||
|
||
return &Mapp[K, V]{client: client, keyMarshaler: opts.keyMarshaler, valueMarshaler: opts.valueMarshaler, namespace: opts.namespace} | ||
} | ||
|
||
func (c *Mapp[K, V]) Get(ctx context.Context, key K) (V, bool, error) { | ||
keyString, err := c.computeKey(ctx, key) | ||
if err != nil { | ||
return *new(V), false, fmt.Errorf("computing key: %w", err) | ||
} | ||
|
||
result := c.client.Get(ctx, keyString) | ||
if result.Err() != nil { | ||
// Missing key | ||
if result.Err() == redis.Nil { | ||
return *new(V), false, nil | ||
} | ||
return *new(V), false, fmt.Errorf("getting value: %w", err) | ||
} | ||
|
||
value := new(V) | ||
if err = c.valueMarshaler.Unmarshal(ctx, result.Val(), value); err != nil { | ||
return *value, true, fmt.Errorf("unmarshalling value: %w", err) | ||
} | ||
|
||
return *value, true, nil | ||
} | ||
|
||
func (c *Mapp[K, V]) Set(ctx context.Context, key K, value V) error { | ||
keyString, err := c.computeKey(ctx, key) | ||
if err != nil { | ||
return fmt.Errorf("computing key: %w", err) | ||
} | ||
|
||
marshaledValue, err := c.valueMarshaler.Marshal(ctx, value) | ||
if err != nil { | ||
return fmt.Errorf("marshalling value: %w", err) | ||
} | ||
|
||
result := c.client.Set(ctx, keyString, marshaledValue, 0) | ||
if result.Err() != nil { | ||
return fmt.Errorf("setting key=%s: %w", keyString, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Mapp[K, V]) Del(ctx context.Context, key K) error { | ||
keyString, err := c.computeKey(ctx, key) | ||
if err != nil { | ||
return fmt.Errorf("computing key: %w", err) | ||
} | ||
|
||
result := c.client.Del(ctx, keyString) | ||
if result.Err() != nil { | ||
return fmt.Errorf("deleting key=%s: %w", keyString, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Mapp[K, V]) computeKey(ctx context.Context, key K) (string, error) { | ||
marshaledKey, err := c.keyMarshaler.Marshal(ctx, key) | ||
if err != nil { | ||
return "", fmt.Errorf("marshalling key: %w", err) | ||
} | ||
|
||
if len(c.namespace) > 0 { | ||
return fmt.Sprintf("%s:%s", c.namespace, marshaledKey), nil | ||
} | ||
|
||
return marshaledKey, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package mapp | ||
|
||
import ( | ||
"context" | ||
"github.com/redis/go-redis/v9" | ||
"github.com/stretchr/testify/require" | ||
"github.com/testcontainers/testcontainers-go" | ||
"github.com/testcontainers/testcontainers-go/wait" | ||
"math/rand" | ||
"testing" | ||
) | ||
|
||
func TestCache(t *testing.T) { | ||
|
||
ctx := context.Background() | ||
req := testcontainers.ContainerRequest{ | ||
Image: "redis:latest", | ||
ExposedPorts: []string{"6379/tcp"}, | ||
WaitingFor: wait.ForLog("Ready to accept connections"), | ||
} | ||
redisContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ | ||
ContainerRequest: req, | ||
Started: true, | ||
}) | ||
if err != nil { | ||
t.Fatalf("failed to create redis container: %v", err) | ||
} | ||
defer func() { | ||
if err := redisContainer.Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate container: %s", err.Error()) | ||
} | ||
}() | ||
|
||
endpoint, err := redisContainer.Endpoint(ctx, "") | ||
if err != nil { | ||
t.Fatalf("failed to get container endpoint: %v", err) | ||
} | ||
|
||
client := redis.NewClient(&redis.Options{Addr: endpoint}) | ||
|
||
t.Run("get, set, delete string key/value", func(t *testing.T) { | ||
cache := NewMapp[string, string](client, WithNamespace[string, string](RandomNamespace())) | ||
|
||
err = cache.Set(ctx, "key", "value") | ||
require.NoError(t, err) | ||
|
||
value, exists, err := cache.Get(ctx, "key") | ||
require.NoError(t, err) | ||
require.True(t, exists) | ||
require.Equal(t, "value", value) | ||
|
||
err = cache.Del(ctx, "key") | ||
require.NoError(t, err) | ||
|
||
value, exists, err = cache.Get(ctx, "key") | ||
require.NoError(t, err) | ||
require.False(t, exists) | ||
require.Equal(t, "", value) | ||
}) | ||
|
||
} | ||
|
||
func RandomNamespace() string { | ||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | ||
|
||
b := make([]rune, 20) | ||
for i := range b { | ||
b[i] = letters[rand.Intn(len(letters))] | ||
} | ||
return string(b) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package marshal | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
) | ||
|
||
type JsonMarshaler[T any] struct { | ||
} | ||
|
||
func (jm *JsonMarshaler[T]) Marshal(ctx context.Context, value T) (string, error) { | ||
bytes, err := json.Marshal(value) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(bytes), nil | ||
} | ||
|
||
func (jm *JsonMarshaler[T]) Unmarshal(ctx context.Context, valueString string, value *T) error { | ||
return json.Unmarshal([]byte(valueString), value) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package marshal | ||
|
||
import ( | ||
"context" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestJsonMarshaler(t *testing.T) { | ||
|
||
ctx := context.Background() | ||
|
||
t.Run("test marshal and unmarshal", func(t *testing.T) { | ||
marshaler := JsonMarshaler[string]{} | ||
|
||
marshalled, err := marshaler.Marshal(ctx, "test") | ||
require.NoError(t, err) | ||
require.Equal(t, "\"test\"", marshalled) | ||
|
||
var unmarshalled string | ||
err = marshaler.Unmarshal(ctx, marshalled, &unmarshalled) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, "test", unmarshalled) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package marshal | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
type Marshaler[T any] interface { | ||
Marshal(ctx context.Context, value T) (string, error) | ||
Unmarshal(ctx context.Context, valueString string, value *T) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.