Skip to content

Commit

Permalink
Merge pull request #41 from vimeo/proto_fetcher_wrapper
Browse files Browse the repository at this point in the history
protocodec: add BackendGetterV2 and GalaxyGet
  • Loading branch information
dfinkel authored May 24, 2023
2 parents f7799df + 108ab0f commit 9d1dfca
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
48 changes: 48 additions & 0 deletions protocodec/backend_getter_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//go:build go1.18

package protocodec

import (
"context"
"fmt"

"github.com/vimeo/galaxycache"

"google.golang.org/protobuf/proto"
)

// BackendGetterV2 is an adapter that implements galaxycache.BackendGetter
// (it wraps an unexported type because type-inference is much better on function-calls)
func BackendGetterV2[C any, T pointerMessage[C]](f func(ctx context.Context, key string) (T, error)) galaxycache.BackendGetter {
return backendGetterV2[C, T](f)
}

// backendGetterV2 is an adapter type that implements galaxycache.BackendGetter
type backendGetterV2[C any, T pointerMessage[C]] func(ctx context.Context, key string) (T, error)

// Get populates dest with the value identified by key
// The returned data must be unversioned. That is, key must
// uniquely describe the loaded data, without an implicit
// current time, and without relying on cache expiration
// mechanisms.
func (b backendGetterV2[C, T]) Get(ctx context.Context, key string, dest galaxycache.Codec) error {
out, bgErr := b(ctx, key)
if bgErr != nil {
return bgErr
}
switch d := dest.(type) {
case *CodecV2[C, T]:
d.Set(out)
default:
vs, mErr := proto.Marshal(out)
if mErr != nil {
return fmt.Errorf("failed to marshal value as bytes: %w", mErr)
}

if uErr := dest.UnmarshalBinary(vs); uErr != nil {
return fmt.Errorf("destination codec (type %T) Unmarshal failed: %w", dest, uErr)
}
}

return nil
}
106 changes: 106 additions & 0 deletions protocodec/backend_getter_v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//go:build go1.18

package protocodec_test

import (
"context"
"errors"
"fmt"
"testing"

"google.golang.org/protobuf/proto"

"github.com/vimeo/galaxycache"
"github.com/vimeo/galaxycache/protocodec"
"github.com/vimeo/galaxycache/protocodec/internal/testpbv2"
)

// Test of common-good-case
func TestBackendGetterV2Good(t *testing.T) {
beGood := func(ctx context.Context, key string) (*testpbv2.TestMessage, error) {
return &testpbv2.TestMessage{
Name: proto.String("TestName"),
City: proto.String("TestCity"),
}, nil
}

be := protocodec.BackendGetterV2(beGood)

ctx := context.Background()

// test with a proto codec passed (local common-case)
{
pc := protocodec.NewV2[testpbv2.TestMessage]()

if getErr := be.Get(ctx, "foobar", &pc); getErr != nil {
t.Errorf("noop Get call failed: %s", getErr)
}

pv := pc.Get()
if pv.City == nil || *pv.City != "TestCity" {
t.Errorf("unexpected value for City: %v", pv.City)
}
if pv.Name == nil || *pv.Name != "TestName" {
t.Errorf("unexpected value for Name: %v", pv.Name)
}
}
// test with a ByteCodec to exercise the common-case when a remote-fetch is done
{
c := galaxycache.ByteCodec{}

if getErr := be.Get(ctx, "foobar", &c); getErr != nil {
t.Errorf("noop Get call failed: %s", getErr)
}

if len(c) < len("TestName")+len("TestCity") {
t.Errorf("marshaled bytes too short (less than sum of two string fields)")
}

pc := protocodec.NewV2[testpbv2.TestMessage]()

if umErr := pc.UnmarshalBinary([]byte(c)); umErr != nil {
t.Errorf("failed to unmarshal bytes: %s", umErr)
}

pv := pc.Get()
if pv.City == nil || *pv.City != "TestCity" {
t.Errorf("unexpected value for City: %v", pv.City)
}
if pv.Name == nil || *pv.Name != "TestName" {
t.Errorf("unexpected value for Name: %v", pv.Name)
}
}
}

func TestBackendGetterV2Bad(t *testing.T) {
sentinel := errors.New("sentinel error")

beErrorer := func(ctx context.Context, key string) (*testpbv2.TestMessage, error) {
return nil, fmt.Errorf("error: %w", sentinel)
}

be := protocodec.BackendGetterV2(beErrorer)

ctx := context.Background()

// test with a proto codec passed (local common-case)
{
pc := protocodec.NewV2[testpbv2.TestMessage]()

if getErr := be.Get(ctx, "foobar", &pc); getErr == nil {
t.Errorf("noop Get call didn't fail")
} else if !errors.Is(getErr, sentinel) {
t.Errorf("Error from Get did not wrap/equal sentinel")
}
}
// test with a ByteCodec to exercise the common-case when a remote-fetch is done
{
c := galaxycache.ByteCodec{}

if getErr := be.Get(ctx, "foobar", &c); getErr == nil {
t.Errorf("noop Get call didn't fail")
} else if !errors.Is(getErr, sentinel) {
t.Errorf("Error from Get did not wrap/equal sentinel")
}
}
}
20 changes: 20 additions & 0 deletions protocodec/galaxywrap_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build go1.18

package protocodec

import (
"context"

"github.com/vimeo/galaxycache"
)

// GalaxyGet is a simple wrapper around a Galaxy.Get method-call that takes
// care of constructing the protocodec.CodecV2, etc. (making the interface more idomatic for Go)
func GalaxyGet[C any, T pointerMessage[C]](ctx context.Context, g *galaxycache.Galaxy, key string) (T, error) {
pc := NewV2[C, T]()
getErr := g.Get(ctx, key, &pc)
if getErr != nil {
return nil, getErr
}
return pc.Get(), nil
}

0 comments on commit 9d1dfca

Please sign in to comment.