Class: Lutaml::Hal::RateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/lutaml/hal/rate_limiter.rb

Overview

Rate limiter to handle API rate limiting with exponential backoff

Constant Summary collapse

DEFAULT_MAX_RETRIES =
3
DEFAULT_BASE_DELAY =
1.0
DEFAULT_MAX_DELAY =
60.0
DEFAULT_BACKOFF_FACTOR =
2.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ RateLimiter



14
15
16
17
18
19
20
# File 'lib/lutaml/hal/rate_limiter.rb', line 14

def initialize(options = {})
  @max_retries = options[:max_retries] || DEFAULT_MAX_RETRIES
  @base_delay = options[:base_delay] || DEFAULT_BASE_DELAY
  @max_delay = options[:max_delay] || DEFAULT_MAX_DELAY
  @backoff_factor = options[:backoff_factor] || DEFAULT_BACKOFF_FACTOR
  @enabled = options[:enabled] != false # Default to enabled unless explicitly disabled
end

Instance Attribute Details

#backoff_factorObject (readonly)

Returns the value of attribute backoff_factor.



12
13
14
# File 'lib/lutaml/hal/rate_limiter.rb', line 12

def backoff_factor
  @backoff_factor
end

#base_delayObject (readonly)

Returns the value of attribute base_delay.



12
13
14
# File 'lib/lutaml/hal/rate_limiter.rb', line 12

def base_delay
  @base_delay
end

#max_delayObject (readonly)

Returns the value of attribute max_delay.



12
13
14
# File 'lib/lutaml/hal/rate_limiter.rb', line 12

def max_delay
  @max_delay
end

#max_retriesObject (readonly)

Returns the value of attribute max_retries.



12
13
14
# File 'lib/lutaml/hal/rate_limiter.rb', line 12

def max_retries
  @max_retries
end

Instance Method Details

#calculate_delay(attempt, error = nil) ⇒ Object

Calculate delay with exponential backoff



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/lutaml/hal/rate_limiter.rb', line 55

def calculate_delay(attempt, error = nil)
  # Check for Retry-After header if it's a rate limit error
  if error.is_a?(TooManyRequestsError) && error.respond_to?(:response) && error.response
    retry_after = extract_retry_after(error.response)
    return retry_after if retry_after
  end

  # Exponential backoff: base_delay * (backoff_factor ^ (attempt - 1))
  delay = @base_delay * (@backoff_factor**(attempt - 1))

  # Cap at max_delay
  [delay, @max_delay].min
end

#disable!Object

Disable rate limiting



95
96
97
# File 'lib/lutaml/hal/rate_limiter.rb', line 95

def disable!
  @enabled = false
end

#enable!Object

Enable rate limiting



90
91
92
# File 'lib/lutaml/hal/rate_limiter.rb', line 90

def enable!
  @enabled = true
end

#enabled?Boolean

Check if rate limiting is enabled



100
101
102
# File 'lib/lutaml/hal/rate_limiter.rb', line 100

def enabled?
  @enabled
end

#extract_retry_after(response) ⇒ Object

Extract Retry-After header value



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/lutaml/hal/rate_limiter.rb', line 70

def extract_retry_after(response)
  headers = response[:headers] || {}
  retry_after = headers['retry-after'] || headers['Retry-After']
  return nil unless retry_after

  # Retry-After can be in seconds (integer) or HTTP date
  if retry_after.match?(/^\d+$/)
    retry_after.to_i
  else
    # Parse HTTP date and calculate seconds from now
    begin
      retry_time = Time.parse(retry_after)
      [retry_time - Time.now, 0].max
    rescue ArgumentError
      nil
    end
  end
end

#should_retry?(error, attempt) ⇒ Boolean

Check if we should retry based on the error and attempt count



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/lutaml/hal/rate_limiter.rb', line 40

def should_retry?(error, attempt)
  return false if attempt > @max_retries

  case error
  when TooManyRequestsError
    true
  when ServerError
    # Always retry on server errors
    true
  else
    false
  end
end

#with_rate_limitingObject

Execute a block with rate limiting and retry logic



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/lutaml/hal/rate_limiter.rb', line 23

def with_rate_limiting
  return yield unless @enabled

  attempt = 0
  begin
    attempt += 1
    yield
  rescue TooManyRequestsError, ServerError => e
    raise unless should_retry?(e, attempt)

    delay = calculate_delay(attempt, e)
    sleep(delay)
    retry
  end
end