package infra import ( "math" "time" ) // RateLimiterCheck evalua si un request identificado por key puede consumir un token. // Recarga tokens segun el tiempo transcurrido desde el ultimo check (token bucket). // Retorna RateLimitResult con Allowed, Remaining, ResetAt y RetryAfter. // Es seguro para uso concurrente. func RateLimiterCheck(rl *RateLimiter, key string) RateLimitResult { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() client, ok := rl.clients[key] if !ok { // Primera vez para esta key: bucket lleno menos el token consumido. client = &rateLimiterClient{tokens: float64(rl.burst), lastSeen: now} rl.clients[key] = client } else { // Recargar tokens segun tiempo transcurrido. elapsed := now.Sub(client.lastSeen).Seconds() client.tokens = math.Min(float64(rl.burst), client.tokens+elapsed*rl.rate) client.lastSeen = now } if client.tokens >= 1 { client.tokens -= 1 remaining := int(math.Floor(client.tokens)) // ResetAt: cuando volvera a estar lleno. missing := float64(rl.burst) - client.tokens secondsToFull := missing / rl.rate return RateLimitResult{ Allowed: true, Remaining: remaining, ResetAt: now.Add(time.Duration(secondsToFull * float64(time.Second))), RetryAfter: 0, } } // Sin tokens suficientes — calcular cuanto esperar. needed := 1 - client.tokens retryAfter := needed / rl.rate missing := float64(rl.burst) - client.tokens secondsToFull := missing / rl.rate return RateLimitResult{ Allowed: false, Remaining: 0, ResetAt: now.Add(time.Duration(secondsToFull * float64(time.Second))), RetryAfter: retryAfter, } }