diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 9a23ef9d2..0cc43ad87 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -7,69 +7,22 @@ import ( "github.com/patrickmn/go-cache" ) -// Package cache provides a generic caching implementation that wraps the go-cache library -// with additional support for custom eviction policies. It allows both time-based expiration -// (inherited from go-cache) and custom eviction rules through user-defined policies. -// -// The cache is type-safe through Go generics, thread-safe through mutex locks, and supports -// all basic cache operations. Keys are strings, and values can be of any type. Each cached -// value stores its insertion timestamp, allowing for time-based validation in custom policies. -// -// Example usage with contract reader: -// type Object { -// Value interface{} -// } -// -// type Event struct { -// Timestamp int64 -// Data string -// } -// -// type ContractReader interface { -// QueryEvents(ctx context.Context, filter QueryFilter) ([]Event, error) -// } -// -// reader := NewContractReader() -// -// // Create cache with contract reader in closure -// cache := NewCustomCache[Object]( -// 5*time.Minute, // Default expiration -// 10*time.Minute, // Cleanup interval -// func(o Event, storedAt time.Time) bool { -// ctx := context.Background() -// filter := QueryFilter{ -// FromTimestamp: storedAt.Unix(), -// Confidence: Finalized, -// } -// -// // Query for any events after our cache insertion time -// newEvents, err := reader.QueryEvents(ctx, filter) -// if err != nil { -// return false // Keep cache on error -// } -// -// // Evict if new events exist after our cache time -// return len(newEvents) > 0 -// }, -// ) -// -// // Cache an object -// o := Object{Data: "..."} -// cache.Set("key", o, NoExpiration) -// -// // Later: event will be evicted if newer ones exist on chain -// o, found := cache.Get("key") -// -// The cache ensures data freshness through: -// - Automatic time-based expiration from go-cache -// - Custom eviction policies with access to storage timestamps -// - Thread-safe operations for concurrent access -// - Type safety through Go generics - const ( NoExpiration = cache.NoExpiration ) +// Cache defines the interface for cache operations +type Cache[V any] interface { + // Set adds an item to the cache with an expiration time + Set(key string, value V, expiration time.Duration) + // Get retrieves an item from the cache + Get(key string) (V, bool) + // Delete removes an item from the cache + Delete(key string) + // Items returns all items in the cache + Items() map[string]V +} + // timestampedValue wraps a value with its storage timestamp type timestampedValue[V any] struct { Value V @@ -129,6 +82,11 @@ func (c *CustomCache[V]) Get(key string) (V, bool) { return wrapped.Value, true } +// Delete removes an item from the cache +func (c *CustomCache[V]) Delete(key string) { + c.Cache.Delete(key) +} + // Items returns all items in the cache func (c *CustomCache[V]) Items() map[string]V { c.mutex.RLock() @@ -145,3 +103,61 @@ func (c *CustomCache[V]) Items() map[string]V { return result } + +// Package documentation +/* +Package cache provides a generic caching implementation that wraps the go-cache library +with additional support for custom eviction policies. It allows both time-based expiration +(inherited from go-cache) and custom eviction rules through user-defined policies. + +The cache is type-safe through Go generics, thread-safe through mutex locks, and supports +all basic cache operations. Keys are strings, and values can be of any type. Each cached +value stores its insertion timestamp, allowing for time-based validation in custom policies. + +Example usage with contract reader: + type Event struct { + Timestamp int64 + Data string + } + + type ContractReader interface { + QueryEvents(ctx context.Context, filter QueryFilter) ([]Event, error) + } + + reader := NewContractReader() + + // Create cache with contract reader in closure + cache := NewCustomCache[Event]( + 5*time.Minute, // Default expiration + 10*time.Minute, // Cleanup interval + func(ev Event, _ time.Time) bool { + ctx := context.Background() + filter := QueryFilter{ + FromTimestamp: ev.Timestamp(), + Confidence: Finalized, + } + + // Query for any events after our cache insertion time + newEvents, err := reader.QueryEvents(ctx, filter) + if err != nil { + return false // Keep cache on error + } + + // Evict if new events exist after our cache time + return len(newEvents) > 0 + }, + ) + + // Cache an event + ev := Event{Timestamp: time.Now().Unix(), Data: "..."} + cache.Set("key", ev, NoExpiration) + + // Later: event will be evicted if newer ones exist on chain + ev, found := cache.Get("key") + +The cache ensures data freshness through: + - Automatic time-based expiration from go-cache + - Custom eviction policies with access to storage timestamps + - Thread-safe operations for concurrent access + - Type safety through Go generics +*/ diff --git a/mocks/internal_/cache/cache.go b/mocks/internal_/cache/cache.go index 603330b71..b728af8c7 100644 --- a/mocks/internal_/cache/cache.go +++ b/mocks/internal_/cache/cache.go @@ -2,69 +2,60 @@ package cache -import mock "github.com/stretchr/testify/mock" +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) // MockCache is an autogenerated mock type for the Cache type -type MockCache[K comparable, V interface{}] struct { +type MockCache[V interface{}] struct { mock.Mock } -type MockCache_Expecter[K comparable, V interface{}] struct { +type MockCache_Expecter[V interface{}] struct { mock *mock.Mock } -func (_m *MockCache[K, V]) EXPECT() *MockCache_Expecter[K, V] { - return &MockCache_Expecter[K, V]{mock: &_m.Mock} +func (_m *MockCache[V]) EXPECT() *MockCache_Expecter[V] { + return &MockCache_Expecter[V]{mock: &_m.Mock} } // Delete provides a mock function with given fields: key -func (_m *MockCache[K, V]) Delete(key K) bool { - ret := _m.Called(key) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 bool - if rf, ok := ret.Get(0).(func(K) bool); ok { - r0 = rf(key) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 +func (_m *MockCache[V]) Delete(key string) { + _m.Called(key) } // MockCache_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type MockCache_Delete_Call[K comparable, V interface{}] struct { +type MockCache_Delete_Call[V interface{}] struct { *mock.Call } // Delete is a helper method to define mock.On call -// - key K -func (_e *MockCache_Expecter[K, V]) Delete(key interface{}) *MockCache_Delete_Call[K, V] { - return &MockCache_Delete_Call[K, V]{Call: _e.mock.On("Delete", key)} +// - key string +func (_e *MockCache_Expecter[V]) Delete(key interface{}) *MockCache_Delete_Call[V] { + return &MockCache_Delete_Call[V]{Call: _e.mock.On("Delete", key)} } -func (_c *MockCache_Delete_Call[K, V]) Run(run func(key K)) *MockCache_Delete_Call[K, V] { +func (_c *MockCache_Delete_Call[V]) Run(run func(key string)) *MockCache_Delete_Call[V] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(K)) + run(args[0].(string)) }) return _c } -func (_c *MockCache_Delete_Call[K, V]) Return(_a0 bool) *MockCache_Delete_Call[K, V] { - _c.Call.Return(_a0) +func (_c *MockCache_Delete_Call[V]) Return() *MockCache_Delete_Call[V] { + _c.Call.Return() return _c } -func (_c *MockCache_Delete_Call[K, V]) RunAndReturn(run func(K) bool) *MockCache_Delete_Call[K, V] { +func (_c *MockCache_Delete_Call[V]) RunAndReturn(run func(string)) *MockCache_Delete_Call[V] { _c.Call.Return(run) return _c } // Get provides a mock function with given fields: key -func (_m *MockCache[K, V]) Get(key K) (V, bool) { +func (_m *MockCache[V]) Get(key string) (V, bool) { ret := _m.Called(key) if len(ret) == 0 { @@ -73,16 +64,16 @@ func (_m *MockCache[K, V]) Get(key K) (V, bool) { var r0 V var r1 bool - if rf, ok := ret.Get(0).(func(K) (V, bool)); ok { + if rf, ok := ret.Get(0).(func(string) (V, bool)); ok { return rf(key) } - if rf, ok := ret.Get(0).(func(K) V); ok { + if rf, ok := ret.Get(0).(func(string) V); ok { r0 = rf(key) } else { r0 = ret.Get(0).(V) } - if rf, ok := ret.Get(1).(func(K) bool); ok { + if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(key) } else { r1 = ret.Get(1).(bool) @@ -92,87 +83,122 @@ func (_m *MockCache[K, V]) Get(key K) (V, bool) { } // MockCache_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type MockCache_Get_Call[K comparable, V interface{}] struct { +type MockCache_Get_Call[V interface{}] struct { *mock.Call } // Get is a helper method to define mock.On call -// - key K -func (_e *MockCache_Expecter[K, V]) Get(key interface{}) *MockCache_Get_Call[K, V] { - return &MockCache_Get_Call[K, V]{Call: _e.mock.On("Get", key)} +// - key string +func (_e *MockCache_Expecter[V]) Get(key interface{}) *MockCache_Get_Call[V] { + return &MockCache_Get_Call[V]{Call: _e.mock.On("Get", key)} } -func (_c *MockCache_Get_Call[K, V]) Run(run func(key K)) *MockCache_Get_Call[K, V] { +func (_c *MockCache_Get_Call[V]) Run(run func(key string)) *MockCache_Get_Call[V] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(K)) + run(args[0].(string)) }) return _c } -func (_c *MockCache_Get_Call[K, V]) Return(_a0 V, _a1 bool) *MockCache_Get_Call[K, V] { +func (_c *MockCache_Get_Call[V]) Return(_a0 V, _a1 bool) *MockCache_Get_Call[V] { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockCache_Get_Call[K, V]) RunAndReturn(run func(K) (V, bool)) *MockCache_Get_Call[K, V] { +func (_c *MockCache_Get_Call[V]) RunAndReturn(run func(string) (V, bool)) *MockCache_Get_Call[V] { _c.Call.Return(run) return _c } -// Set provides a mock function with given fields: key, value -func (_m *MockCache[K, V]) Set(key K, value V) bool { - ret := _m.Called(key, value) +// Items provides a mock function with given fields: +func (_m *MockCache[V]) Items() map[string]V { + ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for Set") + panic("no return value specified for Items") } - var r0 bool - if rf, ok := ret.Get(0).(func(K, V) bool); ok { - r0 = rf(key, value) + var r0 map[string]V + if rf, ok := ret.Get(0).(func() map[string]V); ok { + r0 = rf() } else { - r0 = ret.Get(0).(bool) + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]V) + } } return r0 } +// MockCache_Items_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Items' +type MockCache_Items_Call[V interface{}] struct { + *mock.Call +} + +// Items is a helper method to define mock.On call +func (_e *MockCache_Expecter[V]) Items() *MockCache_Items_Call[V] { + return &MockCache_Items_Call[V]{Call: _e.mock.On("Items")} +} + +func (_c *MockCache_Items_Call[V]) Run(run func()) *MockCache_Items_Call[V] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockCache_Items_Call[V]) Return(_a0 map[string]V) *MockCache_Items_Call[V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockCache_Items_Call[V]) RunAndReturn(run func() map[string]V) *MockCache_Items_Call[V] { + _c.Call.Return(run) + return _c +} + +// Set provides a mock function with given fields: key, value, expiration +func (_m *MockCache[V]) Set(key string, value V, expiration time.Duration) { + _m.Called(key, value, expiration) +} + // MockCache_Set_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Set' -type MockCache_Set_Call[K comparable, V interface{}] struct { +type MockCache_Set_Call[V interface{}] struct { *mock.Call } // Set is a helper method to define mock.On call -// - key K +// - key string // - value V -func (_e *MockCache_Expecter[K, V]) Set(key interface{}, value interface{}) *MockCache_Set_Call[K, V] { - return &MockCache_Set_Call[K, V]{Call: _e.mock.On("Set", key, value)} +// - expiration time.Duration +func (_e *MockCache_Expecter[V]) Set(key interface{}, value interface{}, expiration interface{}) *MockCache_Set_Call[V] { + return &MockCache_Set_Call[V]{Call: _e.mock.On("Set", key, value, expiration)} } -func (_c *MockCache_Set_Call[K, V]) Run(run func(key K, value V)) *MockCache_Set_Call[K, V] { +func (_c *MockCache_Set_Call[V]) Run(run func(key string, value V, expiration time.Duration)) *MockCache_Set_Call[V] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(K), args[1].(V)) + run(args[0].(string), args[1].(V), args[2].(time.Duration)) }) return _c } -func (_c *MockCache_Set_Call[K, V]) Return(_a0 bool) *MockCache_Set_Call[K, V] { - _c.Call.Return(_a0) +func (_c *MockCache_Set_Call[V]) Return() *MockCache_Set_Call[V] { + _c.Call.Return() return _c } -func (_c *MockCache_Set_Call[K, V]) RunAndReturn(run func(K, V) bool) *MockCache_Set_Call[K, V] { +func (_c *MockCache_Set_Call[V]) RunAndReturn(run func(string, V, time.Duration)) *MockCache_Set_Call[V] { _c.Call.Return(run) return _c } // NewMockCache creates a new instance of MockCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewMockCache[K comparable, V interface{}](t interface { +func NewMockCache[V interface{}](t interface { mock.TestingT Cleanup(func()) -}) *MockCache[K, V] { - mock := &MockCache[K, V]{} +}) *MockCache[V] { + mock := &MockCache[V]{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) })