diff --git a/README.md b/README.md index b2237e8..470f864 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,7 @@ # gotoolkit -[![Go Reference](https://pkg.go.dev/badge/git.nakama.town/fmartingr/gotoolkit.svg)](https://pkg.go.dev/git.nakama.town/fmartingr/gotoolkit) - A set of basic tools to develop Go applications. -# Index - -- [Cache](#cache) -- [Database](#database) -- [Paths](#paths) -- [Service & Servers](#service--servers) -- [Template](#template) - -## Cache - -A basic cache engine to manage the cache of the application. - -```go -cache := cache.NewMemoryCache() -``` - ## Database A basic database engine to manage the connection to a database. @@ -31,14 +13,6 @@ if err != nil { } ``` -## Paths - -A basic utility to manage the paths of the application. - -```go -path := paths.ExpandUser("~/myapp") -``` - ## Service & Servers A basic way to expose servers within one service. diff --git a/cache/file.go b/cache/file.go deleted file mode 100644 index 9bd7a31..0000000 --- a/cache/file.go +++ /dev/null @@ -1,131 +0,0 @@ -package cache - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "git.nakama.town/fmartingr/gotoolkit/model" -) - -var _ model.Cache = (*FileCache)(nil) - -type FileCache struct { - path string -} - -func (c *FileCache) Get(key string) (result any, err error) { - path := c.getPathForItem(key) - - if _, err := os.Stat(path + ".metadata"); os.IsNotExist(err) { - return result, model.ErrCacheKeyDontExist - } - - metadata, err := c.getMetadata(key) - if err != nil { - return result, fmt.Errorf("error getting metadata: %s", err) - } - - if metadata.TTL != nil && metadata.TTL.Before(time.Now()) { - if err := c.Delete(key); err != nil { - return result, fmt.Errorf("error deleting cache key: %s", err) - } - return result, model.ErrCacheKeyDontExist - } - - contents, err := os.ReadFile(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) { - return result, model.ErrCacheKeyDontExist - } - return - } - - return contents, nil -} - -func (c *FileCache) Set(key string, value any, opts ...model.CacheOption) error { - path := c.getPathForItem(key) - - metadata := model.CacheItem{ - Key: key, - } - - for _, opt := range opts { - opt(&metadata) - } - - if err := c.setMetadata(key, metadata); err != nil { - return fmt.Errorf("error setting metadata: %s", err) - } - - if err := os.WriteFile(path, value.([]byte), 0766); err != nil { - return fmt.Errorf("error writting cache file: %s", err) - } - - return nil -} - -func (c *FileCache) Delete(key string) error { - path := c.getPathForItem(key) - - if err := os.Remove(path); err != nil { - return fmt.Errorf("error deleting cache file: %s", err) - } - - if err := os.Remove(path + ".metadata"); err != nil { - return fmt.Errorf("error deleting cache metadata: %s", err) - } - - return nil -} - -func (c *FileCache) getPathForItem(key string) string { - return filepath.Join(c.path, key) -} - -func (c *FileCache) getMetadata(key string) (model.CacheItem, error) { - path := c.getPathForItem(key) + ".metadata" - - contents, err := os.ReadFile(path) - if err != nil { - return model.CacheItem{}, fmt.Errorf("error reading cache file: %s", err) - } - - var metadata model.CacheItem - if err := json.Unmarshal(contents, &metadata); err != nil { - return model.CacheItem{}, fmt.Errorf("error unmarshalling cache file: %s", err) - } - - return metadata, nil -} - -func (c *FileCache) setMetadata(key string, metadata model.CacheItem) error { - path := c.getPathForItem(key) + ".metadata" - - contents, err := json.Marshal(metadata) - if err != nil { - return fmt.Errorf("error marshalling cache file: %s", err) - } - - return os.WriteFile(path, contents, 0644) -} - -func NewFileCache(folderName string) (*FileCache, error) { - userCacheDir, err := os.UserCacheDir() - if err != nil { - return nil, fmt.Errorf("Could not retrieve user cache directory: %w", err) - } - path := filepath.Join(userCacheDir, folderName) - - if err := os.MkdirAll(path, 0755); err != nil { - return nil, fmt.Errorf("couldn't create cache directory: %w", err) - } - - return &FileCache{ - path: path, - }, nil -} diff --git a/cache/file_test.go b/cache/file_test.go deleted file mode 100644 index 0889ef0..0000000 --- a/cache/file_test.go +++ /dev/null @@ -1,412 +0,0 @@ -package cache - -import ( - "os" - "path/filepath" - "testing" - "time" - - "git.nakama.town/fmartingr/gotoolkit/model" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewFileCache(t *testing.T) { - // Create a test folder name - testFolderName := "test-cache" - - // Test initialization with the temp directory - cache, err := NewFileCache(testFolderName) - require.NoError(t, err, "NewFileCache should not return an error") - require.NotNil(t, cache, "NewFileCache should return a non-nil cache") - - // Verify the directory was created (not checking the exact path as it depends on the os.UserCacheDir output) - dirInfo, err := os.Stat(cache.path) - require.NoError(t, err, "Cache directory should exist") - assert.True(t, dirInfo.IsDir(), "Cache path should be a directory") -} - -func TestFileCache_Set(t *testing.T) { - // Create a temporary directory for testing - tempDir := t.TempDir() - cache := setupTestFileCache(t, tempDir) - - // Test simple set operation - testKey := "test-key" - testValue := []byte("test-value") - err := cache.Set(testKey, testValue) - require.NoError(t, err, "Set should not return an error") - - // Verify the file exists - filePath := filepath.Join(cache.path, testKey) - assert.FileExists(t, filePath, "Cache file should exist") - assert.FileExists(t, filePath+".metadata", "Cache metadata file should exist") - - // Read the file contents - content, err := os.ReadFile(filePath) - require.NoError(t, err, "Should be able to read cache file") - assert.Equal(t, testValue, content, "File content should match set value") - - // Test set with TTL - ttlKey := "ttl-key" - ttlValue := []byte("ttl-value") - ttlDuration := 1 * time.Hour - withTTL := func(item *model.CacheItem) { - expiry := time.Now().Add(ttlDuration) - item.TTL = &expiry - } - - err = cache.Set(ttlKey, ttlValue, withTTL) - require.NoError(t, err, "Set with TTL should not return an error") - - // Verify file exists - ttlFilePath := filepath.Join(cache.path, ttlKey) - assert.FileExists(t, ttlFilePath, "Cache file with TTL should exist") - assert.FileExists(t, ttlFilePath+".metadata", "Cache metadata file with TTL should exist") - - // Verify the metadata has TTL set - metadata, err := cache.getMetadata(ttlKey) - require.NoError(t, err, "Should be able to get metadata") - assert.NotNil(t, metadata.TTL, "TTL should be set in metadata") -} - -func TestFileCache_SetTableDriven(t *testing.T) { - tempDir := t.TempDir() - - tests := []struct { - name string - key string - value []byte - withTTL bool - ttlDelta time.Duration - }{ - { - name: "basic set without TTL", - key: "key1", - value: []byte("value1"), - }, - { - name: "set with future TTL", - key: "key2", - value: []byte("value2"), - withTTL: true, - ttlDelta: 1 * time.Hour, // Future time - }, - { - name: "set with past TTL", - key: "key3", - value: []byte("value3"), - withTTL: true, - ttlDelta: -1 * time.Hour, // Past time - }, - { - name: "set with empty value", - key: "key4", - value: []byte{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cache := setupTestFileCache(t, tempDir) - - var opts []model.CacheOption - if tt.withTTL { - opts = append(opts, func(item *model.CacheItem) { - expiry := time.Now().Add(tt.ttlDelta) - item.TTL = &expiry - }) - } - - err := cache.Set(tt.key, tt.value, opts...) - require.NoError(t, err, "Set should not return an error") - - // Verify file and metadata exist - filePath := filepath.Join(cache.path, tt.key) - assert.FileExists(t, filePath, "Cache file should exist") - assert.FileExists(t, filePath+".metadata", "Cache metadata file should exist") - - // Verify file content - content, err := os.ReadFile(filePath) - require.NoError(t, err, "Should be able to read cache file") - assert.Equal(t, tt.value, content, "File content should match set value") - - // Verify metadata - metadata, err := cache.getMetadata(tt.key) - require.NoError(t, err, "Should be able to get metadata") - assert.Equal(t, tt.key, metadata.Key, "Metadata key should match") - - if tt.withTTL { - assert.NotNil(t, metadata.TTL, "TTL should be set in metadata") - expectedTime := time.Now().Add(tt.ttlDelta) - assert.WithinDuration(t, expectedTime, *metadata.TTL, 2*time.Second, "TTL should be close to expected value") - } else { - assert.Nil(t, metadata.TTL, "TTL should not be set for items without TTL") - } - }) - } -} - -func TestFileCache_Get(t *testing.T) { - // Create a temporary directory for testing - tempDir := t.TempDir() - cache := setupTestFileCache(t, tempDir) - - // Set up test data - validKey := "valid-key" - validValue := []byte("valid-value") - err := cache.Set(validKey, validValue) - require.NoError(t, err, "Set should not return an error for setup") - - // Test getting valid key - retrievedValue, err := cache.Get(validKey) - require.NoError(t, err, "Get should not return an error for valid key") - assert.Equal(t, validValue, retrievedValue, "Retrieved value should match original") - - // Test getting non-existent key - _, err = cache.Get("nonexistent-key") - assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Get should return ErrCacheKeyDontExist for non-existent key") - - // Test getting expired key - expiredKey := "expired-key" - expiredValue := []byte("expired-value") - expiredTTL := func(item *model.CacheItem) { - expiry := time.Now().Add(-1 * time.Minute) // Past time - item.TTL = &expiry - } - - err = cache.Set(expiredKey, expiredValue, expiredTTL) - require.NoError(t, err, "Set should not return an error for expired key setup") - - _, err = cache.Get(expiredKey) - assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Get should return ErrCacheKeyDontExist for expired key") - - // Verify the expired files are deleted - expiredFilePath := filepath.Join(cache.path, expiredKey) - expiredMetadataPath := expiredFilePath + ".metadata" - assert.NoFileExists(t, expiredFilePath, "Expired cache file should be deleted") - assert.NoFileExists(t, expiredMetadataPath, "Expired cache metadata file should be deleted") -} - -func TestFileCache_GetTableDriven(t *testing.T) { - tempDir := t.TempDir() - - tests := []struct { - name string - setupKeys map[string][]byte - setupTTLs map[string]time.Duration - getKey string - expectValue []byte - expectError error - checkFileDeleted bool - }{ - { - name: "get existing key", - setupKeys: map[string][]byte{ - "test-key": []byte("test-value"), - }, - getKey: "test-key", - expectValue: []byte("test-value"), - expectError: nil, - }, - { - name: "get non-existent key", - setupKeys: map[string][]byte{ - "test-key": []byte("test-value"), - }, - getKey: "nonexistent-key", - expectValue: nil, - expectError: model.ErrCacheKeyDontExist, - }, - { - name: "get expired key", - setupKeys: map[string][]byte{ - "expired-key": []byte("expired-value"), - }, - setupTTLs: map[string]time.Duration{ - "expired-key": -1 * time.Minute, // Past time - }, - getKey: "expired-key", - expectValue: nil, - expectError: model.ErrCacheKeyDontExist, - checkFileDeleted: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cache := setupTestFileCache(t, tempDir) - - // Setup test data - for key, value := range tt.setupKeys { - var opts []model.CacheOption - if ttl, ok := tt.setupTTLs[key]; ok { - opts = append(opts, func(item *model.CacheItem) { - expiry := time.Now().Add(ttl) - item.TTL = &expiry - }) - } - - err := cache.Set(key, value, opts...) - require.NoError(t, err, "Setup: Set should not return an error") - } - - // Test get - value, err := cache.Get(tt.getKey) - - if tt.expectError != nil { - assert.ErrorIs(t, err, tt.expectError) - assert.Nil(t, value) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectValue, value) - } - - // Check if expired files are deleted - if tt.checkFileDeleted { - filePath := filepath.Join(cache.path, tt.getKey) - metadataPath := filePath + ".metadata" - assert.NoFileExists(t, filePath, "Expired cache file should be deleted") - assert.NoFileExists(t, metadataPath, "Expired cache metadata file should be deleted") - } - }) - } -} - -func TestFileCache_Delete(t *testing.T) { - // Create a temporary directory for testing - tempDir := t.TempDir() - cache := setupTestFileCache(t, tempDir) - - // Set up test data - testKey := "delete-key" - testValue := []byte("delete-value") - err := cache.Set(testKey, testValue) - require.NoError(t, err, "Set should not return an error for setup") - - // Verify files exist before delete - filePath := filepath.Join(cache.path, testKey) - metadataPath := filePath + ".metadata" - assert.FileExists(t, filePath, "Cache file should exist before delete") - assert.FileExists(t, metadataPath, "Cache metadata file should exist before delete") - - // Test delete - err = cache.Delete(testKey) - require.NoError(t, err, "Delete should not return an error") - - // Verify files are deleted - assert.NoFileExists(t, filePath, "Cache file should be deleted") - assert.NoFileExists(t, metadataPath, "Cache metadata file should be deleted") - - // Test deleting non-existent key - err = cache.Delete("nonexistent-key") - assert.Error(t, err, "Delete should return an error for non-existent key") -} - -func TestFileCache_DeleteTableDriven(t *testing.T) { - tempDir := t.TempDir() - - tests := []struct { - name string - setupKey string - setupValue []byte - deleteKey string - expectError bool - checkNotExist bool - }{ - { - name: "delete existing key", - setupKey: "existing-key", - setupValue: []byte("existing-value"), - deleteKey: "existing-key", - expectError: false, - checkNotExist: true, - }, - { - name: "delete non-existent key", - deleteKey: "nonexistent-key", - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cache := setupTestFileCache(t, tempDir) - - // Setup test data if needed - if tt.setupKey != "" { - err := cache.Set(tt.setupKey, tt.setupValue) - require.NoError(t, err, "Setup: Set should not return an error") - - // Verify files exist before delete - filePath := filepath.Join(cache.path, tt.setupKey) - metadataPath := filePath + ".metadata" - assert.FileExists(t, filePath, "Cache file should exist before delete") - assert.FileExists(t, metadataPath, "Cache metadata file should exist before delete") - } - - // Test delete - err := cache.Delete(tt.deleteKey) - - if tt.expectError { - assert.Error(t, err, "Delete should return an error") - } else { - assert.NoError(t, err, "Delete should not return an error") - } - - // Check files are deleted if needed - if tt.checkNotExist { - filePath := filepath.Join(cache.path, tt.deleteKey) - metadataPath := filePath + ".metadata" - assert.NoFileExists(t, filePath, "Cache file should be deleted") - assert.NoFileExists(t, metadataPath, "Cache metadata file should be deleted") - } - }) - } -} - -func TestFileCache_GetPathForItem(t *testing.T) { - tempDir := t.TempDir() - cache := setupTestFileCache(t, tempDir) - - key := "test-key" - expectedPath := filepath.Join(cache.path, key) - actualPath := cache.getPathForItem(key) - - assert.Equal(t, expectedPath, actualPath, "Path for item should match expected format") -} - -func TestFileCache_GetSetMetadata(t *testing.T) { - tempDir := t.TempDir() - cache := setupTestFileCache(t, tempDir) - - // Create test metadata - key := "metadata-key" - now := time.Now() - metadata := model.CacheItem{ - Key: key, - Value: nil, - TTL: &now, - } - - // Test setMetadata - err := cache.setMetadata(key, metadata) - require.NoError(t, err, "setMetadata should not return an error") - - // Verify metadata file exists - metadataPath := filepath.Join(cache.path, key) + ".metadata" - assert.FileExists(t, metadataPath, "Metadata file should exist") - - // Test getMetadata - retrievedMetadata, err := cache.getMetadata(key) - require.NoError(t, err, "getMetadata should not return an error") - assert.Equal(t, key, retrievedMetadata.Key, "Retrieved metadata key should match original") - assert.WithinDuration(t, now, *retrievedMetadata.TTL, time.Second, "Retrieved metadata TTL should be close to original") -} - -// Helper function to create a FileCache for testing -func setupTestFileCache(t *testing.T, tempDir string) *FileCache { - return &FileCache{ - path: tempDir, - } -} diff --git a/cache/memory.go b/cache/memory.go deleted file mode 100644 index 81c990e..0000000 --- a/cache/memory.go +++ /dev/null @@ -1,68 +0,0 @@ -package cache - -import ( - "sync" - "time" - - "git.nakama.town/fmartingr/gotoolkit/model" -) - -var _ model.Cache = (*MemoryCache)(nil) - -type MemoryCache struct { - data map[string]model.CacheItem - dataMu sync.RWMutex -} - -func (c *MemoryCache) Get(key string) (result any, err error) { - c.dataMu.RLock() - - item, exists := c.data[key] - if !exists { - c.dataMu.RUnlock() - return result, model.ErrCacheKeyDontExist - } - - // Check expiration while still holding the lock - if item.TTL != nil && item.TTL.Before(time.Now()) { - c.dataMu.RUnlock() - _ = c.Delete(key) - return result, model.ErrCacheKeyDontExist - } - - value := item.Value - c.dataMu.RUnlock() - - return value, nil -} - -func (c *MemoryCache) Set(key string, value any, opts ...model.CacheOption) error { - item := model.CacheItem{ - Key: key, - Value: value, - } - - for _, opt := range opts { - opt(&item) - } - - c.dataMu.Lock() - c.data[key] = item - c.dataMu.Unlock() - - return nil -} - -func (c *MemoryCache) Delete(key string) error { - c.dataMu.Lock() - delete(c.data, key) - c.dataMu.Unlock() - return nil -} - -func NewMemoryCache() *MemoryCache { - return &MemoryCache{ - data: make(map[string]model.CacheItem), - dataMu: sync.RWMutex{}, - } -} diff --git a/cache/memory_test.go b/cache/memory_test.go deleted file mode 100644 index 0f09110..0000000 --- a/cache/memory_test.go +++ /dev/null @@ -1,285 +0,0 @@ -package cache - -import ( - "sync" - "testing" - "time" - - "git.nakama.town/fmartingr/gotoolkit/model" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewMemoryCache(t *testing.T) { - cache := NewMemoryCache() - require.NotNil(t, cache, "NewMemoryCache returned nil") - require.NotNil(t, cache.data, "Cache data map was not initialized") -} - -func TestMemoryCache_Set(t *testing.T) { - cache := NewMemoryCache() - - // Test basic set - err := cache.Set("key1", "value1") - require.NoError(t, err, "Set returned unexpected error") - - // Verify the item was stored correctly - cache.dataMu.RLock() - item, exists := cache.data["key1"] - cache.dataMu.RUnlock() - require.True(t, exists, "Set did not store the item in the cache") - assert.Equal(t, "key1", item.Key, "Wrong key stored") - assert.Equal(t, "value1", item.Value, "Wrong value stored") - assert.Nil(t, item.TTL, "TTL should be nil") - - // Test set with TTL - withTTL := func(item *model.CacheItem) { - expiry := time.Now().Add(10 * time.Minute) - item.TTL = &expiry - } - - err = cache.Set("key2", "value2", withTTL) - require.NoError(t, err, "Set with TTL returned unexpected error") - - // Verify the item with TTL was stored correctly - cache.dataMu.RLock() - itemWithTTL, exists := cache.data["key2"] - cache.dataMu.RUnlock() - require.True(t, exists, "Set did not store the item with TTL in the cache") - assert.NotNil(t, itemWithTTL.TTL, "TTL option was not applied") -} - -func TestMemoryCache_SetTableDriven(t *testing.T) { - tests := []struct { - name string - key string - value interface{} - ttl *time.Duration - }{ - { - name: "string value without TTL", - key: "string-key", - value: "string-value", - ttl: nil, - }, - { - name: "int value without TTL", - key: "int-key", - value: 42, - ttl: nil, - }, - { - name: "struct value without TTL", - key: "struct-key", - value: struct{ Name string }{"test"}, - ttl: nil, - }, - { - name: "string value with TTL", - key: "string-key-ttl", - value: "string-value-ttl", - ttl: ptrDuration(5 * time.Minute), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cache := NewMemoryCache() - - var opts []model.CacheOption - if tt.ttl != nil { - opts = append(opts, func(item *model.CacheItem) { - expiry := time.Now().Add(*tt.ttl) - item.TTL = &expiry - }) - } - - err := cache.Set(tt.key, tt.value, opts...) - assert.NoError(t, err) - - cache.dataMu.RLock() - item, exists := cache.data[tt.key] - cache.dataMu.RUnlock() - - assert.True(t, exists) - assert.Equal(t, tt.key, item.Key) - assert.Equal(t, tt.value, item.Value) - - if tt.ttl != nil { - assert.NotNil(t, item.TTL) - } else { - assert.Nil(t, item.TTL) - } - }) - } -} - -func TestMemoryCache_Get(t *testing.T) { - cache := NewMemoryCache() - - // Test getting non-existent key - _, err := cache.Get("nonexistent") - assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Wrong error returned for non-existent key") - - // Test getting existing key - require.NoError(t, cache.Set("key1", "value1")) - value, err := cache.Get("key1") - require.NoError(t, err, "Get returned unexpected error") - assert.Equal(t, "value1", value, "Wrong value returned") - - // Test getting expired key - expiredTTL := func(item *model.CacheItem) { - expiry := time.Now().Add(-1 * time.Minute) // Past time - item.TTL = &expiry - } - require.NoError(t, cache.Set("expired", "value", expiredTTL)) - - _, err = cache.Get("expired") - assert.ErrorIs(t, err, model.ErrCacheKeyDontExist, "Wrong error returned for expired item") - - // Verify the expired key was deleted - cache.dataMu.RLock() - _, exists := cache.data["expired"] - cache.dataMu.RUnlock() - assert.False(t, exists, "Expired item was not deleted during Get") -} - -func TestMemoryCache_GetTableDriven(t *testing.T) { - tests := []struct { - name string - setupKey string - setupValue any - setupTTL *time.Duration - getKey string - expectValue any - expectError error - }{ - { - name: "get existing key", - setupKey: "test-key", - setupValue: "test-value", - getKey: "test-key", - expectValue: "test-value", - expectError: nil, - }, - { - name: "get non-existent key", - setupKey: "test-key", - setupValue: "test-value", - getKey: "different-key", - expectValue: nil, - expectError: model.ErrCacheKeyDontExist, - }, - { - name: "get expired key", - setupKey: "expired-key", - setupValue: "expired-value", - setupTTL: ptrDuration(-1 * time.Minute), // Negative to create an expired key - getKey: "expired-key", - expectValue: nil, - expectError: model.ErrCacheKeyDontExist, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cache := NewMemoryCache() - - // Setup - var opts []model.CacheOption - if tt.setupTTL != nil { - opts = append(opts, func(item *model.CacheItem) { - expiry := time.Now().Add(*tt.setupTTL) - item.TTL = &expiry - }) - } - - if tt.setupKey != "" { - err := cache.Set(tt.setupKey, tt.setupValue, opts...) - require.NoError(t, err) - } - - // Test - value, err := cache.Get(tt.getKey) - - if tt.expectError != nil { - assert.ErrorIs(t, err, tt.expectError) - } else { - assert.NoError(t, err) - } - - assert.Equal(t, tt.expectValue, value) - - // If it was an expired key, ensure it's deleted - if tt.setupTTL != nil && *tt.setupTTL < 0 { - cache.dataMu.RLock() - _, exists := cache.data[tt.setupKey] - cache.dataMu.RUnlock() - assert.False(t, exists, "Expired item was not deleted during Get") - } - }) - } -} - -func TestMemoryCache_Delete(t *testing.T) { - cache := NewMemoryCache() - - // Setup test data - require.NoError(t, cache.Set("key1", "value1")) - - // Test deleting existing key - err := cache.Delete("key1") - require.NoError(t, err, "Delete returned unexpected error") - - // Verify the key was deleted - cache.dataMu.RLock() - _, exists := cache.data["key1"] - cache.dataMu.RUnlock() - assert.False(t, exists, "Item was not deleted from cache") - - // Test deleting non-existent key (should not error) - err = cache.Delete("nonexistent") - assert.NoError(t, err, "Delete on non-existent key returned error") -} - -func TestMemoryCache_Concurrency(t *testing.T) { - cache := NewMemoryCache() - const goroutines = 10 - const operations = 100 - - wg := sync.WaitGroup{} - wg.Add(goroutines * 2) - - // Writers - for i := range goroutines { - go func(id int) { - for j := range operations { - key := "key" + string(rune('A'+id)) + string(rune('0'+j%10)) - err := cache.Set(key, j) - assert.NoError(t, err) - } - wg.Done() - }(i) - } - - // Readers - for i := range goroutines { - go func(id int) { - for j := range operations { - key := "key" + string(rune('A'+id)) + string(rune('0'+j%10)) - _, _ = cache.Get(key) // We ignore errors here as keys might not exist yet - } - wg.Done() - }(i) - } - - // Wait for all goroutines to complete - wg.Wait() - - // If we reached here without deadlock or panic, the test passes -} - -// Helper function to create duration pointers -func ptrDuration(d time.Duration) *time.Duration { - return &d -} diff --git a/cache/options.go b/cache/options.go deleted file mode 100644 index 6497b83..0000000 --- a/cache/options.go +++ /dev/null @@ -1,14 +0,0 @@ -package cache - -import ( - "time" - - "git.nakama.town/fmartingr/gotoolkit/model" -) - -func WithTTL(ttl time.Duration) model.CacheOption { - return func(item *model.CacheItem) { - exp := time.Now().Add(ttl) - item.TTL = &exp - } -} diff --git a/go.mod b/go.mod index 3314212..372d4c0 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,17 @@ go 1.23.3 require ( github.com/pelletier/go-toml/v2 v2.2.3 - github.com/stretchr/testify v1.9.0 modernc.org/sqlite v1.34.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/sys v0.22.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index 4550c4b..17e4d56 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,6 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= diff --git a/model/cache.go b/model/cache.go deleted file mode 100644 index 4b58326..0000000 --- a/model/cache.go +++ /dev/null @@ -1,22 +0,0 @@ -package model - -import ( - "errors" - "time" -) - -var ErrCacheKeyDontExist = errors.New("cache key don't exist") - -type Cache interface { - Get(key string) (any, error) - Set(key string, value any, opts ...CacheOption) error - Delete(key string) error -} - -type CacheItem struct { - Key string - Value any - TTL *time.Time -} - -type CacheOption func(item *CacheItem) diff --git a/paths/user.go b/paths/user.go deleted file mode 100644 index 0810c9d..0000000 --- a/paths/user.go +++ /dev/null @@ -1,20 +0,0 @@ -package paths - -import ( - "os/user" - "path/filepath" - "strings" -) - -func ExpandUser(providedPath string) string { - var path string = providedPath - usr, _ := user.Current() - dir := usr.HomeDir - - if providedPath == "~" { - path = dir - } else if strings.HasPrefix(providedPath, "~/") { - path = filepath.Join(dir, providedPath[2:]) - } - return path -} diff --git a/template/engine.go b/template/engine.go index 2e1e3ac..53414d3 100644 --- a/template/engine.go +++ b/template/engine.go @@ -2,9 +2,9 @@ package template import ( "bytes" + "embed" "fmt" "html/template" - "io/fs" ) // Engine is a template engine @@ -24,8 +24,8 @@ func (e *Engine) Render(name string, data any) ([]byte, error) { } // NewTemplateEngine creates a new template engine from the given templates -func NewEngine(templates fs.FS) (*Engine, error) { - tmpls, err := template.ParseFS(templates, "*.html") +func NewEngine(templates embed.FS) (*Engine, error) { + tmpls, err := template.ParseFS(templates, "**/*.html") if err != nil { return nil, fmt.Errorf("failed to parse templates: %w", err) }