diff --git a/ads/ads.go b/ads/ads.go index 6fa3c39..2856f79 100644 --- a/ads/ads.go +++ b/ads/ads.go @@ -196,7 +196,7 @@ const ( ) // A SubscriptionHandler will receive notifications for the cache entries it has subscribed to using -// [Cache.Subscribe]. Note that it is imperative that implementations be hashable as it will be +// RawCache.Subscribe. Note that it is imperative that implementations be hashable as it will be // stored as the key to a map (unhashable types include slices and functions). type SubscriptionHandler[T proto.Message] interface { // Notify is invoked when the given entry is modified. A deletion is denoted with a nil resource. The given time diff --git a/cache.go b/cache.go index b957552..84ab67b 100644 --- a/cache.go +++ b/cache.go @@ -1,6 +1,7 @@ package diderot import ( + "fmt" "iter" "sync" "time" @@ -15,19 +16,14 @@ import ( // For example, it can be used to store the set of "envoy.config.listener.v3.Listener" available to // clients. type Cache[T proto.Message] interface { - // Type returns the corresponding [Type] for this cache. - Type() Type - // EntryNames returns an [iter.Seq] that will iterate over all the current entry names in the cache. - EntryNames() iter.Seq[string] - // EstimateSubscriptionSize estimates the number of resources targeted by the given list of - // subscriptions. This is only an estimation since the resource count is dynamic, and repeated - // invocations of this function with the same parameters may not yield the same results. - EstimateSubscriptionSize(resourceNamesSubscribe []string) int + RawCache // Set stores the given resource in the cache. If the resource name corresponds to a resource URN, it // will also be stored in the corresponding glob collection (see [TP1 proposal] for additional // details on the format). See Subscribe for more details on how the resources added by this method - // can be subscribed to. A zero [time.Time] can be used to represent that the time at which the - // resource was created or modified is unknown (or ignored). + // can be subscribed to. Invoking Set whenever possible is preferred to RawCache.SetRaw, since it can + // return an error if the given resource's type does not match the expected type while Set validates + // at compile time that the given value matches the desired type. A zero [time.Time] can be used to + // represent that the time at which the resource was created or modified is unknown (or ignored). // // WARNING: It is imperative that the Resource and the underlying [proto.Message] not be modified // after insertion! This resource will be read by subscribers to the cache and callers of Get, and @@ -42,11 +38,6 @@ type Cache[T proto.Message] interface { SetResource(r *ads.Resource[T], modifiedAt time.Time) // Get fetches the entry, or nil if it's not present and/or has been deleted. Get(name string) *ads.Resource[T] - // Clear clears the entry (if present) and notifies all subscribers that the entry has been deleted. - // A zero [time.Time] can be used to represent that the time at which the resource was cleared is - // unknown (or ignored). For example, when watching a directory, the filesystem does not keep track - // of when the file was deleted. - Clear(name string, clearedAt time.Time) // IsSubscribedTo checks whether the given handler is subscribed to the given named entry. IsSubscribedTo(name string, handler ads.SubscriptionHandler[T]) bool // Subscribe registers the handler as a subscriber of the given named resource. The handler is always @@ -116,6 +107,35 @@ type Cache[T proto.Message] interface { Unsubscribe(name string, handler ads.SubscriptionHandler[T]) } +// RawCache is a subset of the [Cache] interface and provides a number of methods to interact with +// the [Cache] without needing to know the underlying resource type at compile time. All RawCache +// implementations *must* also implement [Cache] for the underlying resource type. +type RawCache interface { + // Type returns the corresponding [Type] for this cache. + Type() Type + // EntryNames returns an [iter.Seq] that will iterate over all the current entry names in the cache. + EntryNames() iter.Seq[string] + // GetRaw is the untyped equivalent of Cache.Get. There are uses for this method, but the preferred + // way is to use Cache.Get because this function incurs the cost of marshaling the resource. Returns + // an error if the resource cannot be marshaled. + GetRaw(name string) (*ads.RawResource, error) + // SetRaw is the untyped equivalent of Cache.Set. There are uses for this method, but the preferred + // way is to use Cache.Set since it offers a typed API instead of the untyped ads.RawResource parameter. + // Subscribers will be notified of the new version of this resource. See Cache.Set for additional + // details on how the resources are stored. Returns an error if the given resource's type URL does + // not match the expected type URL, or the resource cannot be unmarshaled. + SetRaw(r *ads.RawResource, modifiedAt time.Time) error + // Clear clears the entry (if present) and notifies all subscribers that the entry has been deleted. + // A zero [time.Time] can be used to represent that the time at which the resource was cleared is + // unknown (or ignored). For example, when watching a directory, the filesystem does not keep track + // of when the file was deleted. + Clear(name string, clearedAt time.Time) + // EstimateSubscriptionSize estimates the number of resources targeted by the given list of + // subscriptions. This is only an estimation since the resource count is dynamic, and repeated + // invocations of this function with the same parameters may not yield the same results. + EstimateSubscriptionSize(resourceNamesSubscribe []string) int +} + // NewCache returns a simple Cache with only 1 priority (see NewPrioritizedCache). func NewCache[T proto.Message]() Cache[T] { return NewPrioritizedCache[T](1)[0] @@ -359,6 +379,14 @@ func (c *cache[T]) Get(name string) (r *ads.Resource[T]) { return r } +func (c *cache[T]) GetRaw(name string) (*ads.RawResource, error) { + r := c.Get(name) + if r == nil { + return nil, nil + } + return r.Marshal() +} + func (c *cache[T]) EntryNames() iter.Seq[string] { return func(yield func(string) bool) { c.resources.Range()(func(k string, v *internal.WatchableValue[T]) bool { @@ -420,3 +448,19 @@ func (c *cacheWithPriority[T]) SetResource(r *ads.Resource[T], modifiedAt time.T v.Set(c.p, r, modifiedAt) }) } + +func (c *cacheWithPriority[T]) SetRaw(raw *ads.RawResource, modifiedAt time.Time) error { + // Ensure that the given resource's type URL is correct. + if u := raw.GetResource().GetTypeUrl(); u != c.typeReference.URL() { + return fmt.Errorf("diderot: invalid type URL, expected %q got %q", c.typeReference, u) + } + + r, err := ads.UnmarshalRawResource[T](raw) + if err != nil { + return err + } + + c.SetResource(r, modifiedAt) + + return nil +} diff --git a/cache_test.go b/cache_test.go index 0d575e8..4944226 100644 --- a/cache_test.go +++ b/cache_test.go @@ -507,7 +507,7 @@ func TestCacheCollections(t *testing.T) { // subscribers are wrapped in a wrappedHandler, which is then used as the key in the subscriber map. // It's important to check that subscribing then unsubscribing works as expected. func TestCacheRaw(t *testing.T) { - c := diderot.ToRawCache(newCache()) + c := diderot.RawCache(newCache()) r := newResource(name1, "42") ch := make(chan *ads.RawResource, 1) @@ -521,16 +521,16 @@ func TestCacheRaw(t *testing.T) { }, ) - c.Subscribe(name1, h) + diderot.Subscribe(c, name1, h) <-ch - require.NoError(t, c.Set(testutils.MustMarshal(t, r), noTime)) - raw, err := c.Get(r.Name) + require.NoError(t, c.SetRaw(testutils.MustMarshal(t, r), noTime)) + raw, err := c.GetRaw(r.Name) require.NoError(t, err) require.Same(t, testutils.MustMarshal(t, r), raw) <-ch c.Clear(name1, noTime) <-ch - c.Unsubscribe(name1, h) + diderot.Unsubscribe(c, name1, h) select { case raw := <-ch: require.Fail(t, "Received unexpected update after unsubscription", raw) @@ -847,7 +847,7 @@ func TestGlobRace(t *testing.T) { const ( entries = 100 - writers = 10 + writers = 100 count = 100 readers = 100 @@ -862,7 +862,7 @@ func TestGlobRace(t *testing.T) { var writesDone, readsDone sync.WaitGroup writesDone.Add(writers) - readsDone.Add(entries * readers) + readsDone.Add(writers * readers) for range readers { h := testutils.NewSubscriptionHandler(func(name string, r *ads.Resource[*Timestamp], _ ads.SubscriptionMetadata) { diff --git a/client.go b/client.go index 17069aa..c77960e 100644 --- a/client.go +++ b/client.go @@ -111,12 +111,6 @@ func Watch[T proto.Message](c *ADSClient, name string, watcher Watcher[T]) { } } -// Watch is the equivalent of the top-level [Watch] function, except that it can be used to watch -// resources without knowing the hard type [T] at runtime. Useful when writing type-agnostic code. -func (c *ADSClient) Watch(t Type, name string, watcher Watcher[proto.Message]) { - t.watch(c, name, watcher) -} - // getResourceHandler gets or initializes the [internal.ResourceHandler] for the specified type in // the given client. func getResourceHandler[T proto.Message](c *ADSClient) *internal.ResourceHandler[T] { diff --git a/examples/quickstart/main.go b/examples/quickstart/main.go index 554df00..8af8538 100644 --- a/examples/quickstart/main.go +++ b/examples/quickstart/main.go @@ -69,15 +69,15 @@ func (sl SimpleResourceLocator) Subscribe( // Do nothing if the given type is not supported return func() {} } - c.Subscribe(resourceName, handler) + diderot.Subscribe(c, resourceName, handler) return func() { - c.Unsubscribe(resourceName, handler) + diderot.Unsubscribe(c, resourceName, handler) } } // getCache extracts a typed [diderot.Cache] from the given [SimpleResourceLocator]. func getCache[T proto.Message](sl SimpleResourceLocator) diderot.Cache[T] { - return diderot.MustUnwrapRawCache[T](sl[diderot.TypeOf[T]().URL()]) + return sl[diderot.TypeOf[T]().URL()].(diderot.Cache[T]) } func (sl SimpleResourceLocator) GetListenerCache() diderot.Cache[*ads.Listener] { diff --git a/internal/cache/subscription_type.go b/internal/cache/subscription_type.go index 4b43c9f..67414dd 100644 --- a/internal/cache/subscription_type.go +++ b/internal/cache/subscription_type.go @@ -4,7 +4,7 @@ package internal type subscriptionType byte // The following subscriptionType constants define the ways a client can subscribe to a resource. See -// [Cache.Subscribe] for additional details. +// RawCache.Subscribe for additional details. const ( // An ExplicitSubscription means the client subscribed to a resource by explicit providing its name. ExplicitSubscription = subscriptionType(iota) diff --git a/server.go b/server.go index 460c8df..3abcfdd 100644 --- a/server.go +++ b/server.go @@ -152,7 +152,7 @@ func WithControlPlane(controlPlane *corev3.ControlPlane) ADSServerOption { // estimator will not be invoked, as it may result in pre-allocating a very large map that will likely // not be fully utilized. // -// For convenience, this is trivially implemented by [Cache.EstimateSubscriptionSize]. +// For convenience, this is trivially implemented by [RawCache.EstimateSubscriptionSize]. // // [initial resource versions]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/discovery/v3/discovery.proto#service-discovery-v3-deltadiscoveryrequest type SendBufferSizeEstimator interface { diff --git a/server_test.go b/server_test.go index dcfd179..93f85e2 100644 --- a/server_test.go +++ b/server_test.go @@ -99,9 +99,9 @@ func (tl *testLocator) Subscribe( handler ads.RawSubscriptionHandler, ) (unsubscribe func()) { c := tl.caches[typeURL] - c.Subscribe(resourceName, handler) + Subscribe(c, resourceName, handler) return func() { - c.Unsubscribe(resourceName, handler) + Unsubscribe(c, resourceName, handler) } } @@ -118,7 +118,7 @@ func newTestLocator(t *testing.T, node *ads.Node, types ...Type) *testLocator { } func getCache[T proto.Message](tl *testLocator) Cache[T] { - return MustUnwrapRawCache[T](tl.caches[TypeOf[T]().URL()]) + return tl.caches[TypeOf[T]().URL()].(Cache[T]) } type mockSizeEstimator struct { @@ -175,7 +175,7 @@ func TestEndToEnd(t *testing.T) { for _, r := range resources { c, ok := locator.caches[r.Resource.TypeUrl] require.Truef(t, ok, "Unknown type loaded from test config %q: %+v", r.Resource.TypeUrl, r) - require.NoError(t, c.Set(r, time.Now())) + require.NoError(t, c.SetRaw(r, time.Now())) } addr := ts.Addr().(*net.TCPAddr) @@ -733,12 +733,12 @@ func TestSubscriptionManagerSubscriptions(t *testing.T) { ) checkSubs := func(t *testing.T, c RawCache, h ads.RawSubscriptionHandler, wildcard, r1Sub, r2Sub bool) { t.Helper() - require.Equal(t, wildcard, c.IsSubscribedTo(ads.WildcardSubscription, h), "wildcard") - require.Equal(t, r1Sub, c.IsSubscribedTo(r1, h), r1) - require.Equal(t, r2Sub, c.IsSubscribedTo(r2, h), r2) + require.Equal(t, wildcard, IsSubscribedTo(c, ads.WildcardSubscription, h), "wildcard") + require.Equal(t, r1Sub, IsSubscribedTo(c, r1, h), r1) + require.Equal(t, r2Sub, IsSubscribedTo(c, r2, h), r2) } - newCacheAndHandler := func(t *testing.T) (RawCache, ResourceLocator, *simpleBatchHandler) { + newCacheAndHandler := func(t *testing.T) (Cache[*wrapperspb.BoolValue], ResourceLocator, *simpleBatchHandler) { tl := newTestLocator(t, nil, TypeOf[*wrapperspb.BoolValue]()) c := getCache[*wrapperspb.BoolValue](tl) expected := ads.NewResource(r1, "0", wrapperspb.Bool(true)) @@ -758,7 +758,7 @@ func TestSubscriptionManagerSubscriptions(t *testing.T) { }, } - return ToRawCache(c), tl, h + return c, tl, h } for _, streamType := range []ads.StreamType{ads.DeltaStreamType, ads.SotWStreamType} { diff --git a/type.go b/type.go index ca85eed..c14f934 100644 --- a/type.go +++ b/type.go @@ -1,20 +1,11 @@ package diderot import ( - "fmt" - "iter" - "time" - "github.com/linkedin/diderot/ads" "github.com/linkedin/diderot/internal/utils" "google.golang.org/protobuf/proto" ) -// TypeOf returns a TypeReference that corresponds to the type parameter. -func TypeOf[T proto.Message]() TypeReference[T] { - return typeReference[T](utils.GetTypeURL[T]()) -} - // typeReference is the only implementation of the Type and, by extension, the TypeReference // interface. It is not exposed publicly to ensure that all instances are generated through TypeOf, // which uses reflection on the type parameter to determine the type URL. This is to avoid potential @@ -33,15 +24,16 @@ type Type interface { // TrimmedURL returns the type URL for this Type without the leading "types.googleapis.com/" prefix. // This string is useful when constructing xdstp URLs. TrimmedURL() string - // NewCache is the untyped equivalent of this package's NewCache. The returned RawCache still retains - // the runtime type information and can be safely cast to the corresponding Cache type with - // [UnwrapRawCache]. + // NewCache is the untyped equivalent of this package's NewCache. The returned RawCache still + // retains the runtime type information and can be safely cast to the corresponding Cache type. NewCache() RawCache // NewPrioritizedCache is the untyped equivalent of this package's NewPrioritizedCache. The returned - // RawCache instances can be safely cast to the corresponding Cache type with [UnwrapRawCache]. + // RawCache instances can be safely cast to the corresponding Cache type. NewPrioritizedCache(prioritySlots int) []RawCache - watch(c *ADSClient, name string, watcher Watcher[proto.Message]) + isSubscribedTo(c RawCache, name string, handler ads.RawSubscriptionHandler) bool + subscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) + unsubscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) } func (t typeReference[T]) URL() string { @@ -53,92 +45,18 @@ func (t typeReference[T]) TrimmedURL() string { } func (t typeReference[T]) NewCache() RawCache { - return rawCache[T]{NewCache[T]()} + return NewCache[T]() } func (t typeReference[T]) NewPrioritizedCache(prioritySlots int) []RawCache { caches := NewPrioritizedCache[T](prioritySlots) out := make([]RawCache, len(caches)) for i, c := range caches { - out[i] = rawCache[T]{c} + out[i] = c } return out } -type wrappedWatcher[T proto.Message] struct { - Watcher[proto.Message] -} - -func (r wrappedWatcher[T]) Notify(resources iter.Seq2[string, *ads.Resource[T]]) error { - return r.Watcher.Notify(func(yield func(string, *ads.Resource[proto.Message]) bool) { - for name, resource := range resources { - if !yield(name, &ads.Resource[proto.Message]{ - Name: resource.Name, - Version: resource.Version, - Resource: resource.Resource, - Ttl: resource.Ttl, - CacheControl: resource.CacheControl, - Metadata: resource.Metadata, - }) { - return - } - } - }) -} - -func (t typeReference[T]) watch(c *ADSClient, name string, watcher Watcher[proto.Message]) { - Watch[T](c, name, wrappedWatcher[T]{watcher}) -} - -// A RawCache is an alternate API for a [Cache]. It allows untyped operations against the underlying -// cache and offers the same set of operations as the typed interface. This is useful when the hard -// type [T] is not known at runtime. Can only be created via [ToRawCache]. -type RawCache interface { - // Type returns the corresponding [Type] for this cache. - Type() Type - // EntryNames returns an [iter.Seq] that will iterate over all the current entry names in the cache. - EntryNames() iter.Seq[string] - // EstimateSubscriptionSize estimates the number of resources targeted by the given list of - // subscriptions. This is only an estimation since the resource count is dynamic, and repeated - // invocations of this function with the same parameters may not yield the same results. - EstimateSubscriptionSize(resourceNamesSubscribe []string) int - // Subscribe registers the handler as a subscriber of the given named resource by invoking the - // underlying generic API [diderot.Cache.Subscribe]. - Subscribe(name string, handler ads.RawSubscriptionHandler) - // Unsubscribe unregisters the handler as a subscriber of the given named resource by invoking the - // underlying generic API [diderot.Cache.Unsubscribe]. - Unsubscribe(name string, handler ads.RawSubscriptionHandler) - // IsSubscribedTo checks whether the given handler is subscribed to the given named resource by invoking - // the underlying generic API [diderot.Cache.IsSubscribedTo]. - IsSubscribedTo(name string, handler ads.RawSubscriptionHandler) bool - // Get is the untyped equivalent of [Cache.Get]. There are uses for this method, but the preferred - // way is to use [Cache.Get] because this function incurs the cost of marshaling the resource. - // Returns an error if the resource cannot be marshaled. - Get(name string) (*ads.RawResource, error) - // Set is the untyped equivalent of [Cache.Set]. There are uses for this method, but the preferred - // way is to use [Cache.Set] since it offers a typed API instead of the untyped [ads.RawResource] - // parameter which can return an error. Subscribers will be notified of the new version of this - // resource. See [Cache.Set] for additional details on how the resources are stored. Returns an error - // if the given resource's type URL does not match the expected type URL, or the resource cannot be - // unmarshaled. - Set(r *ads.RawResource, modifiedAt time.Time) error - // Clear clears the entry (if present) and notifies all subscribers that the entry has been deleted. - // A zero [time.Time] can be used to represent that the time at which the resource was cleared is - // unknown (or ignored). For example, when watching a directory, the filesystem does not keep track - // of when the file was deleted. - Clear(name string, clearedAt time.Time) - - private() -} - -type rawCache[T proto.Message] struct { - Cache[T] -} - -func ToRawCache[T proto.Message](c Cache[T]) RawCache { - return rawCache[T]{c} -} - type wrappedHandler[T proto.Message] struct { ads.RawSubscriptionHandler } @@ -163,66 +81,43 @@ func (w wrappedHandler[T]) Notify(name string, r *ads.Resource[T], metadata ads. // // var c Cache[*ads.Endpoint] // var rawHandler RawSubscriptionHandler -// c.Subscribe("foo", toGenericHandler[*ads.Endpoint](rawHandler)) -// c.Unsubscribe("foo", toGenericHandler[*ads.Endpoint](rawHandler)) -func (c rawCache[T]) toGenericHandler(raw ads.RawSubscriptionHandler) ads.SubscriptionHandler[T] { +// c.Subscribe("foo", ToGenericHandler[*ads.Endpoint](rawHandler)) +// c.Unsubscribe("foo", ToGenericHandler[*ads.Endpoint](rawHandler)) +func (t typeReference[T]) toGenericHandler(raw ads.RawSubscriptionHandler) ads.SubscriptionHandler[T] { return wrappedHandler[T]{raw} } -func (c rawCache[T]) Subscribe(name string, handler ads.RawSubscriptionHandler) { - c.Cache.Subscribe(name, c.toGenericHandler(handler)) +func (t typeReference[T]) isSubscribedTo(c RawCache, name string, handler ads.RawSubscriptionHandler) bool { + return c.(Cache[T]).IsSubscribedTo(name, t.toGenericHandler(handler)) } -func (c rawCache[T]) Unsubscribe(name string, handler ads.RawSubscriptionHandler) { - c.Cache.Unsubscribe(name, c.toGenericHandler(handler)) +func (t typeReference[T]) subscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) { + c.(Cache[T]).Subscribe(name, t.toGenericHandler(handler)) } -func (c rawCache[T]) IsSubscribedTo(name string, handler ads.RawSubscriptionHandler) bool { - return c.Cache.IsSubscribedTo(name, c.toGenericHandler(handler)) +func (t typeReference[T]) unsubscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) { + c.(Cache[T]).Unsubscribe(name, t.toGenericHandler(handler)) } -func (c rawCache[T]) Get(name string) (*ads.RawResource, error) { - r := c.Cache.Get(name) - if r == nil { - return nil, nil - } - return r.Marshal() +// TypeOf returns a TypeReference that corresponds to the type parameter. +func TypeOf[T proto.Message]() TypeReference[T] { + return typeReference[T](utils.GetTypeURL[T]()) } -func (c rawCache[T]) Set(raw *ads.RawResource, modifiedAt time.Time) error { - // Ensure that the given resource's type URL is correct. - if u := raw.GetResource().GetTypeUrl(); u != c.Cache.Type().URL() { - return fmt.Errorf("diderot: invalid type URL, expected %q got %q", c.Cache.Type(), u) - } - - r, err := ads.UnmarshalRawResource[T](raw) - if err != nil { - return err - } - - c.Cache.SetResource(r, modifiedAt) - - return nil +// IsSubscribedTo checks whether the given handler is subscribed to the given named resource by invoking +// the underlying generic API [diderot.Cache.IsSubscribedTo]. +func IsSubscribedTo(c RawCache, name string, handler ads.RawSubscriptionHandler) bool { + return c.Type().isSubscribedTo(c, name, handler) } -func (c rawCache[T]) private() {} - -// UnwrapRawCache returns the underlying [Cache] for the given [RawCache] if its type is [T], -// otherwise it returns nil, false. -func UnwrapRawCache[T proto.Message](raw RawCache) (Cache[T], bool) { - c, ok := raw.(rawCache[T]) - if !ok { - return nil, false - } - return c.Cache, true +// Subscribe registers the handler as a subscriber of the given named resource by invoking the +// underlying generic API [diderot.Cache.Subscribe]. +func Subscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) { + c.Type().subscribe(c, name, handler) } -// MustUnwrapRawCache is the equivalent of [UnwrapCache], except that it panics if the given -// [RawCache]'s type is not [T]. -func MustUnwrapRawCache[T proto.Message](raw RawCache) Cache[T] { - c, ok := UnwrapRawCache[T](raw) - if !ok { - panic("RawCache was for type " + raw.Type().URL() + " instead of " + TypeOf[T]().URL()) - } - return c +// Unsubscribe unregisters the handler as a subscriber of the given named resource by invoking the +// underlying generic API [diderot.Cache.Unsubscribe]. +func Unsubscribe(c RawCache, name string, handler ads.RawSubscriptionHandler) { + c.Type().unsubscribe(c, name, handler) } diff --git a/type_test.go b/type_test.go index 6b441de..df759d3 100644 --- a/type_test.go +++ b/type_test.go @@ -37,7 +37,7 @@ func TestType(t *testing.T) { Resource: wrapperspb.Bool(true), } if test.UseRawSetter { - require.NoError(t, ToRawCache(c).Set(testutils.MustMarshal(t, r), time.Time{})) + require.NoError(t, c.SetRaw(testutils.MustMarshal(t, r), time.Time{})) } else { c.SetResource(r, time.Time{}) } @@ -49,13 +49,3 @@ func TestType(t *testing.T) { }) } } - -func TestUnwrapCache(t *testing.T) { - c := NewCache[*wrapperspb.BoolValue]() - _, ok := UnwrapRawCache[*wrapperspb.Int64Value](ToRawCache(c)) - require.False(t, ok) - require.Panics(t, func() { - MustUnwrapRawCache[*wrapperspb.Int64Value](ToRawCache(c)) - }) - require.Same(t, c, MustUnwrapRawCache[*wrapperspb.BoolValue](ToRawCache(c))) -}