mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-29 16:12:15 +00:00
refactor: Replace interface{} with any type alias across codebase
- Replace interface{} with any in api_integration_test.go makeRequest method and test response maps
- Replace interface{} with any in logging.go WithField method and all logging function signatures
- Replace interface{} with any in middleware etag.go sync.Map.Range callback
- Replace interface{} with any in middleware redis_ratelimit.go RedisClientInterface and result parsing
- Replace interface{} with any in middleware redis_ratelimit_
This commit is contained in:
@@ -56,33 +56,33 @@ func NewETagGenerator(secretKey string) *ETagGenerator {
|
||||
func (g *ETagGenerator) GenerateETag(data ETagData) string {
|
||||
// Create a composite hash from multiple factors
|
||||
h := sha256.New()
|
||||
|
||||
|
||||
// Include path and method
|
||||
h.Write([]byte(data.Path))
|
||||
h.Write([]byte(data.Method))
|
||||
|
||||
|
||||
// Include content if available
|
||||
if len(data.Content) > 0 {
|
||||
h.Write(data.Content)
|
||||
}
|
||||
|
||||
|
||||
// Include timestamp (rounded to second for consistency)
|
||||
timestampBytes := []byte(fmt.Sprintf("%d", data.Timestamp.Unix()))
|
||||
timestampBytes := fmt.Appendf(nil, "%d", data.Timestamp.Unix())
|
||||
h.Write(timestampBytes)
|
||||
|
||||
|
||||
// Include user context for personalized responses
|
||||
if data.UserID != "" {
|
||||
h.Write([]byte(data.UserID))
|
||||
}
|
||||
|
||||
|
||||
// Include query parameters for cache differentiation
|
||||
if data.Params != "" {
|
||||
h.Write([]byte(data.Params))
|
||||
}
|
||||
|
||||
|
||||
// Add secret key for security
|
||||
h.Write(g.secretKey)
|
||||
|
||||
|
||||
// Generate final ETag
|
||||
hash := h.Sum(nil)
|
||||
return fmt.Sprintf("\"%x\"", hash)
|
||||
@@ -111,7 +111,7 @@ func (g *ETagGenerator) CacheETag(key, etag string) {
|
||||
|
||||
// InvalidateCache removes ETag entries matching a pattern
|
||||
func (g *ETagGenerator) InvalidateCache(pattern string) {
|
||||
g.cacheStorage.Range(func(key, value interface{}) bool {
|
||||
g.cacheStorage.Range(func(key, value any) bool {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if strings.Contains(keyStr, pattern) {
|
||||
g.cacheStorage.Delete(key)
|
||||
@@ -133,26 +133,26 @@ func generateETag(c *gin.Context) string {
|
||||
Timestamp: time.Now(),
|
||||
Params: c.Request.URL.RawQuery,
|
||||
}
|
||||
|
||||
|
||||
// Add user context if available
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if idStr, ok := userID.(string); ok {
|
||||
data.UserID = idStr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Try to get cached ETag first
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", data.Method, data.Path, data.Params)
|
||||
if cached, exists := etagGenerator.GetCachedETag(cacheKey); exists {
|
||||
return cached
|
||||
}
|
||||
|
||||
|
||||
// Generate new ETag
|
||||
etag := etagGenerator.GenerateETag(data)
|
||||
|
||||
|
||||
// Cache the ETag for future use
|
||||
etagGenerator.CacheETag(cacheKey, etag)
|
||||
|
||||
|
||||
return etag
|
||||
}
|
||||
|
||||
@@ -165,14 +165,14 @@ func generateContentETag(c *gin.Context, content []byte) string {
|
||||
Timestamp: time.Now(),
|
||||
Params: c.Request.URL.RawQuery,
|
||||
}
|
||||
|
||||
|
||||
// Add user context if available
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if idStr, ok := userID.(string); ok {
|
||||
data.UserID = idStr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return etagGenerator.GenerateETag(data)
|
||||
}
|
||||
|
||||
@@ -184,14 +184,14 @@ func generateWeakETag(c *gin.Context) string {
|
||||
Timestamp: time.Now(),
|
||||
Params: c.Request.URL.RawQuery,
|
||||
}
|
||||
|
||||
|
||||
// Add user context if available
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if idStr, ok := userID.(string); ok {
|
||||
data.UserID = idStr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return etagGenerator.GenerateWeakETag(data)
|
||||
}
|
||||
|
||||
@@ -203,12 +203,12 @@ func ETagValidationMiddleware() gin.HandlerFunc {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Check If-None-Match header (for GET requests)
|
||||
if noneMatch := c.GetHeader("If-None-Match"); noneMatch != "" {
|
||||
// Generate current ETag
|
||||
currentETag := generateETag(c)
|
||||
|
||||
|
||||
// Check if any of the ETags match
|
||||
if etagMatches(noneMatch, currentETag) {
|
||||
c.Header("ETag", currentETag)
|
||||
@@ -217,18 +217,18 @@ func ETagValidationMiddleware() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check If-Match header (for PUT/PATCH/DELETE requests)
|
||||
if match := c.GetHeader("If-Match"); match != "" {
|
||||
currentETag := generateETag(c)
|
||||
|
||||
|
||||
if !etagMatches(match, currentETag) {
|
||||
c.Status(http.StatusPreconditionFailed)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -242,14 +242,14 @@ func ContentBasedETagMiddleware() gin.HandlerFunc {
|
||||
body: bytes.NewBuffer(nil),
|
||||
}
|
||||
c.Writer = wrapper
|
||||
|
||||
|
||||
c.Next()
|
||||
|
||||
|
||||
// Only generate ETags for successful GET requests
|
||||
if c.Request.Method != "GET" || c.Writer.Status() != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Generate ETag based on captured response content
|
||||
responseBody := wrapper.Body()
|
||||
if len(responseBody) > 0 {
|
||||
@@ -268,15 +268,15 @@ func CacheInvalidationMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Store original request for post-processing
|
||||
c.Next()
|
||||
|
||||
|
||||
// Invalidate cache for POST, PUT, PATCH, DELETE requests
|
||||
if c.Request.Method == "POST" || c.Request.Method == "PUT" ||
|
||||
c.Request.Method == "PATCH" || c.Request.Method == "DELETE" {
|
||||
|
||||
if c.Request.Method == "POST" || c.Request.Method == "PUT" ||
|
||||
c.Request.Method == "PATCH" || c.Request.Method == "DELETE" {
|
||||
|
||||
// Invalidate ETags for this resource pattern
|
||||
pattern := c.Request.URL.Path
|
||||
etagGenerator.InvalidateCache(pattern)
|
||||
|
||||
|
||||
// Also invalidate related collection resources
|
||||
if strings.Contains(pattern, "/") {
|
||||
parts := strings.Split(strings.Trim(pattern, "/"), "/")
|
||||
@@ -295,19 +295,19 @@ func etagMatches(headerValue, currentETag string) bool {
|
||||
if headerValue == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Parse the header value (can contain multiple ETags)
|
||||
etags := strings.Split(headerValue, ",")
|
||||
for _, etag := range etags {
|
||||
etags := strings.SplitSeq(headerValue, ",")
|
||||
for etag := range etags {
|
||||
etag = strings.TrimSpace(etag)
|
||||
// Remove quotes for comparison
|
||||
etag = strings.Trim(etag, "\"")
|
||||
current := strings.Trim(currentETag, "\"W/")
|
||||
|
||||
|
||||
if etag == current || etag == "W/"+current {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// RedisClientInterface defines the interface for Redis operations
|
||||
type RedisClientInterface interface {
|
||||
Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd
|
||||
Eval(ctx context.Context, script string, keys []string, args ...any) *redis.Cmd
|
||||
Del(ctx context.Context, keys ...string) *redis.IntCmd
|
||||
ZCount(ctx context.Context, key, min, max string) *redis.IntCmd
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func (r *RedisRateLimiter) Allow(ctx context.Context, key string) (*RateLimitRes
|
||||
}
|
||||
|
||||
// Parse Lua script result
|
||||
resultSlice, ok := result.([]interface{})
|
||||
resultSlice, ok := result.([]any)
|
||||
if !ok || len(resultSlice) < 3 {
|
||||
return nil, fmt.Errorf("invalid redis result format")
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (r *RedisRateLimiter) AllowTokenBucket(ctx context.Context, key string) (*R
|
||||
return nil, fmt.Errorf("redis token bucket check failed: %w", err)
|
||||
}
|
||||
|
||||
resultSlice, ok := result.([]interface{})
|
||||
resultSlice, ok := result.([]any)
|
||||
if !ok || len(resultSlice) < 3 {
|
||||
return nil, fmt.Errorf("invalid redis result format")
|
||||
}
|
||||
@@ -207,10 +207,7 @@ func (r *RedisRateLimiter) GetStats(ctx context.Context, key string) (*RateLimit
|
||||
return nil, fmt.Errorf("failed to get rate limit stats: %w", err)
|
||||
}
|
||||
|
||||
remaining := r.limit - int64(count)
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
remaining := max(r.limit-int64(count), 0)
|
||||
|
||||
return &RateLimitResult{
|
||||
Allowed: remaining > 0,
|
||||
|
||||
@@ -18,8 +18,8 @@ type MockRedisClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockRedisClient) Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd {
|
||||
argsList := make([]interface{}, 0, len(keys)+len(args)+1)
|
||||
func (m *MockRedisClient) Eval(ctx context.Context, script string, keys []string, args ...any) *redis.Cmd {
|
||||
argsList := make([]any, 0, len(keys)+len(args)+1)
|
||||
argsList = append(argsList, script)
|
||||
for _, key := range keys {
|
||||
argsList = append(argsList, key)
|
||||
@@ -55,11 +55,11 @@ func TestRedisRateLimiter_Allow(t *testing.T) {
|
||||
mockClient := new(MockRedisClient)
|
||||
|
||||
// Mock Redis response based on test case
|
||||
var mockResult []interface{}
|
||||
var mockResult []any
|
||||
if tt.expected {
|
||||
mockResult = []interface{}{int64(1), int64(tt.limit - 1), int64(time.Now().Unix() + 60)}
|
||||
mockResult = []any{int64(1), int64(tt.limit - 1), int64(time.Now().Unix() + 60)}
|
||||
} else {
|
||||
mockResult = []interface{}{int64(0), int64(0), int64(time.Now().Unix() + 60), int64(30)}
|
||||
mockResult = []any{int64(0), int64(0), int64(time.Now().Unix() + 60), int64(30)}
|
||||
}
|
||||
|
||||
cmd := redis.NewCmd(context.Background())
|
||||
@@ -115,7 +115,7 @@ func TestRedisRateLimitMiddleware(t *testing.T) {
|
||||
|
||||
// Mock successful rate limit check
|
||||
cmd := redis.NewCmd(context.Background())
|
||||
cmd.SetVal([]interface{}{int64(1), int64(4), int64(time.Now().Unix() + 60)})
|
||||
cmd.SetVal([]any{int64(1), int64(4), int64(time.Now().Unix() + 60)})
|
||||
mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(cmd)
|
||||
|
||||
// Create rate limiter with mock client
|
||||
@@ -267,10 +267,10 @@ func TestMultiTierRateLimiter(t *testing.T) {
|
||||
|
||||
// Mock different tier responses
|
||||
freeCmd := redis.NewCmd(context.Background())
|
||||
freeCmd.SetVal([]interface{}{int64(1), int64(9), int64(time.Now().Unix() + 60)})
|
||||
freeCmd.SetVal([]any{int64(1), int64(9), int64(time.Now().Unix() + 60)})
|
||||
|
||||
premiumCmd := redis.NewCmd(context.Background())
|
||||
premiumCmd.SetVal([]interface{}{int64(1), int64(99), int64(time.Now().Unix() + 60)})
|
||||
premiumCmd.SetVal([]any{int64(1), int64(99), int64(time.Now().Unix() + 60)})
|
||||
|
||||
mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(freeCmd).Once()
|
||||
mockClient.On("Eval", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(premiumCmd).Once()
|
||||
|
||||
Reference in New Issue
Block a user