Skip to content

Commit d195f90

Browse files
committed
protocodec: add BackendGetterWithInfo wrapper
1 parent d68b82e commit d195f90

File tree

3 files changed

+135
-3
lines changed

3 files changed

+135
-3
lines changed

protocodec/backend_getter_v2.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@ func (b backendGetterV2[C, T]) Get(ctx context.Context, key string, dest galaxyc
3434
d.Set(out)
3535
return nil
3636
}
37-
return b.setSlow(out, dest)
38-
37+
return setSlowV2(out, dest)
3938
}
4039

41-
func (b backendGetterV2[C, T]) setSlow(out T, dest galaxycache.Codec) error {
40+
func setSlowV2[C any, T pointerMessage[C]](out T, dest galaxycache.Codec) error {
4241
vs, mErr := proto.Marshal(out)
4342
if mErr != nil {
4443
return fmt.Errorf("failed to marshal value as bytes: %w", mErr)
@@ -49,3 +48,29 @@ func (b backendGetterV2[C, T]) setSlow(out T, dest galaxycache.Codec) error {
4948
}
5049
return nil
5150
}
51+
52+
// BackendGetterWithInfo is an adapter that implements [galaxycache.BackendGetterWithInfo]
53+
// (it wraps an unexported type because type-inference is much better on function-calls)
54+
func BackendGetterWithInfo[C any, T pointerMessage[C]](f func(ctx context.Context, key string) (T, galaxycache.BackendGetInfo, error)) galaxycache.BackendGetterWithInfo {
55+
return backendGetterWithInfo[C, T](f)
56+
}
57+
58+
// backendGetterWithInfo is an adapter type that implements galaxycache.BackendGetterWithInfo
59+
type backendGetterWithInfo[C any, T pointerMessage[C]] func(ctx context.Context, key string) (T, galaxycache.BackendGetInfo, error)
60+
61+
// Get populates dest with the value identified by key
62+
// The returned data must be unversioned. That is, key must
63+
// uniquely describe the loaded data, without an implicit
64+
// current time, and without relying on cache expiration
65+
// mechanisms.
66+
func (b backendGetterWithInfo[C, T]) GetWithInfo(ctx context.Context, key string, dest galaxycache.Codec) (galaxycache.BackendGetInfo, error) {
67+
out, bgInfo, bgErr := b(ctx, key)
68+
if bgErr != nil {
69+
return galaxycache.BackendGetInfo{}, bgErr
70+
}
71+
if d, ok := dest.(*CodecV2[C, T]); ok {
72+
d.Set(out)
73+
return bgInfo, nil
74+
}
75+
return bgInfo, setSlowV2(out, dest)
76+
}

protocodec/backend_getter_v2_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"testing"
10+
"time"
1011

1112
"google.golang.org/protobuf/proto"
1213

@@ -104,3 +105,98 @@ func TestBackendGetterV2Bad(t *testing.T) {
104105
}
105106
}
106107
}
108+
109+
// Test of common-good-case
110+
func TestBackendGetterWithInfoGood(t *testing.T) {
111+
expiry := time.Now().Add(time.Minute * 97)
112+
beGood := func(ctx context.Context, key string) (*testpbv2.TestMessage, galaxycache.BackendGetInfo, error) {
113+
return &testpbv2.TestMessage{
114+
Name: proto.String("TestName"),
115+
City: proto.String("TestCity"),
116+
}, galaxycache.BackendGetInfo{Expiration: expiry}, nil
117+
}
118+
119+
be := protocodec.BackendGetterWithInfo(beGood)
120+
121+
ctx := context.Background()
122+
123+
// test with a proto codec passed (local common-case)
124+
{
125+
pc := protocodec.NewV2[testpbv2.TestMessage]()
126+
127+
if bgInfo, getErr := be.GetWithInfo(ctx, "foobar", &pc); getErr != nil {
128+
t.Errorf("noop Get call failed: %s", getErr)
129+
} else if !bgInfo.Expiration.Equal(expiry) {
130+
t.Errorf("unexpected expiration: %s; wanted %s", bgInfo.Expiration, expiry)
131+
}
132+
133+
pv := pc.Get()
134+
if pv.City == nil || *pv.City != "TestCity" {
135+
t.Errorf("unexpected value for City: %v", pv.City)
136+
}
137+
if pv.Name == nil || *pv.Name != "TestName" {
138+
t.Errorf("unexpected value for Name: %v", pv.Name)
139+
}
140+
}
141+
// test with a ByteCodec to exercise the common-case when a remote-fetch is done
142+
{
143+
c := galaxycache.ByteCodec{}
144+
145+
if bgInfo, getErr := be.GetWithInfo(ctx, "foobar", &c); getErr != nil {
146+
t.Errorf("noop Get call failed: %s", getErr)
147+
} else if !bgInfo.Expiration.Equal(expiry) {
148+
t.Errorf("unexpected expiration: %s; wanted %s", bgInfo.Expiration, expiry)
149+
}
150+
151+
if len(c) < len("TestName")+len("TestCity") {
152+
t.Errorf("marshaled bytes too short (less than sum of two string fields)")
153+
}
154+
155+
pc := protocodec.NewV2[testpbv2.TestMessage]()
156+
157+
if umErr := pc.UnmarshalBinary([]byte(c)); umErr != nil {
158+
t.Errorf("failed to unmarshal bytes: %s", umErr)
159+
}
160+
161+
pv := pc.Get()
162+
if pv.City == nil || *pv.City != "TestCity" {
163+
t.Errorf("unexpected value for City: %v", pv.City)
164+
}
165+
if pv.Name == nil || *pv.Name != "TestName" {
166+
t.Errorf("unexpected value for Name: %v", pv.Name)
167+
}
168+
}
169+
}
170+
171+
func TestBackendGetterWithInfoBad(t *testing.T) {
172+
sentinel := errors.New("sentinel error")
173+
174+
beErrorer := func(ctx context.Context, key string) (*testpbv2.TestMessage, galaxycache.BackendGetInfo, error) {
175+
return nil, galaxycache.BackendGetInfo{}, fmt.Errorf("error: %w", sentinel)
176+
}
177+
178+
be := protocodec.BackendGetterWithInfo(beErrorer)
179+
180+
ctx := context.Background()
181+
182+
// test with a proto codec passed (local common-case)
183+
{
184+
pc := protocodec.NewV2[testpbv2.TestMessage]()
185+
186+
if _, getErr := be.GetWithInfo(ctx, "foobar", &pc); getErr == nil {
187+
t.Errorf("noop Get call didn't fail")
188+
} else if !errors.Is(getErr, sentinel) {
189+
t.Errorf("Error from Get did not wrap/equal sentinel")
190+
}
191+
}
192+
// test with a ByteCodec to exercise the common-case when a remote-fetch is done
193+
{
194+
c := galaxycache.ByteCodec{}
195+
196+
if _, getErr := be.GetWithInfo(ctx, "foobar", &c); getErr == nil {
197+
t.Errorf("noop Get call didn't fail")
198+
} else if !errors.Is(getErr, sentinel) {
199+
t.Errorf("Error from Get did not wrap/equal sentinel")
200+
}
201+
}
202+
}

protocodec/galaxywrap_v2.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@ func GalaxyGet[C any, T pointerMessage[C]](ctx context.Context, g *galaxycache.G
1818
}
1919
return pc.msg, nil
2020
}
21+
22+
// GalaxyGetWithInfo is a simple wrapper around a [galaxycache.Galaxy.GetWithOptions] method-call that takes
23+
// care of constructing the [protocodec.CodecV2], etc. (making the interface more idiomatic for Go)
24+
func GalaxyGetWithInfo[C any, T pointerMessage[C]](ctx context.Context, g *galaxycache.Galaxy, opts galaxycache.GetOptions, key string) (m T, info galaxycache.GetInfo, getErr error) {
25+
pc := CodecV2[C, T]{}
26+
info, getErr = g.GetWithOptions(ctx, opts, key, &pc)
27+
if getErr != nil {
28+
return // use named return values to bring the inlining cost down
29+
}
30+
return pc.msg, info, nil
31+
}

0 commit comments

Comments
 (0)