RateLimitingUtil adding status to interpret redis key result

This commit is contained in:
simonredfern 2025-12-27 07:12:29 +01:00
parent 1fc0ab720c
commit cd52665f35
4 changed files with 25 additions and 13 deletions

View File

@ -228,7 +228,7 @@ object RateLimitingUtil extends MdcLoggable {
def consumerRateLimitState(consumerKey: String): immutable.Seq[((Option[Long], Option[Long], String), LimitCallPeriod)] = {
def getCallCounterForPeriod(consumerKey: String, period: LimitCallPeriod): ((Option[Long], Option[Long]), LimitCallPeriod) = {
def getCallCounterForPeriod(consumerKey: String, period: LimitCallPeriod): ((Option[Long], Option[Long], String), LimitCallPeriod) = {
val key = createUniqueKey(consumerKey, period)
// get TTL
@ -256,7 +256,16 @@ object RateLimitingUtil extends MdcLoggable {
case None => Some(0L) // Redis unavailable -> 0 TTL
}
((calls, normalizedTtl), period)
// Calculate status based on Redis TTL response
val status = ttlOpt match {
case Some(ttl) if ttl > 0 => "ACTIVE" // Counter running with time remaining
case Some(-2) => "NO_COUNTER" // Key does not exist, never been set
case Some(ttl) if ttl <= 0 => "EXPIRED" // Key expired (TTL=0) or no expiry (TTL=-1)
case None => "REDIS_UNAVAILABLE" // Redis connection failed
}
((calls, normalizedTtl, status), period)
}
getCallCounterForPeriod(consumerKey, RateLimitingPeriod.PER_SECOND) ::

View File

@ -809,7 +809,7 @@ object JSONFactory310{
def createBadLoginStatusJson(badLoginStatus: BadLoginAttempt) : BadLoginStatusJson = {
BadLoginStatusJson(badLoginStatus.username,badLoginStatus.badAttemptsSinceLastSuccessOrReset, badLoginStatus.lastFailureDate)
}
def createCallLimitJson(consumer: Consumer, rateLimits: List[((Option[Long], Option[Long]), LimitCallPeriod)]) : CallLimitJson = {
def createCallLimitJson(consumer: Consumer, rateLimits: List[((Option[Long], Option[Long], String), LimitCallPeriod)]) : CallLimitJson = {
val redisRateLimit = rateLimits match {
case Nil => None
case _ =>
@ -817,7 +817,8 @@ object JSONFactory310{
rateLimits.filter(_._2 == period) match {
case x :: Nil =>
x._1 match {
case (Some(x), Some(y)) => Some(RateLimit(Some(x), Some(y)))
case (Some(x), Some(y), _) => Some(RateLimit(Some(x), Some(y)))
// Ignore status field for v3.1.0 API (backward compatibility)
case _ => None
}

View File

@ -236,7 +236,10 @@ trait APIMethods600 {
|
|**Status Values:**
|- `ACTIVE`: Rate limit counter is active and tracking calls. Both `calls_made` and `reset_in_seconds` will have numeric values.
|- `UNKNOWN`: Data is not available. This could mean the rate limit period has expired, no rate limit is configured, or the data cannot be retrieved. Both `calls_made` and `reset_in_seconds` will be null.
|- `NO_COUNTER`: Key does not exist - the consumer has not made any API calls in this time period yet.
|- `EXPIRED`: The rate limit counter has expired (TTL reached 0). The counter will be recreated on the next API call.
|- `REDIS_UNAVAILABLE`: Cannot retrieve data from Redis. This indicates a system connectivity issue.
|- `DATA_MISSING`: Unexpected error - period data is missing from the response. This should not occur under normal circumstances.
|
|${userAuthenticationMessage(true)}
|

View File

@ -402,19 +402,18 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
def createRedisCallCountersJson(
// Convert list to map for easy lookup by period
rateLimits: List[((Option[Long], Option[Long]), LimitCallPeriod)]
rateLimits: List[((Option[Long], Option[Long], String), LimitCallPeriod)]
): RedisCallCountersJsonV600 = {
val grouped: Map[LimitCallPeriod, (Option[Long], Option[Long])] =
val grouped: Map[LimitCallPeriod, (Option[Long], Option[Long], String)] =
rateLimits.map { case (limits, period) => period -> limits }.toMap
def getCallCounterForPeriod(period: RateLimitingPeriod.Value): RateLimitV600 =
grouped.get(period) match {
// ACTIVE: Both calls and TTL exist, and TTL > 0 (key has time remaining)
// UNKNOWN: Missing data, TTL <= 0 (expired), or Redis unavailable
case Some((Some(calls), Some(ttl))) if ttl > 0 =>
RateLimitV600(Some(calls), Some(ttl), "ACTIVE")
// Use status calculated by RateLimitingUtil (ACTIVE, NO_COUNTER, EXPIRED, REDIS_UNAVAILABLE)
case Some((calls, ttl, status)) =>
RateLimitV600(calls, ttl, status)
case _ =>
RateLimitV600(None, None, "UNKNOWN")
RateLimitV600(None, None, "DATA_MISSING")
}
RedisCallCountersJsonV600(
@ -591,7 +590,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
}
def createActiveCallLimitsJsonV600FromCallLimit(
rateLimit: code.api.util.RateLimitingJson.CallLimit,
rateLimitIds: List[String],
activeDate: java.util.Date