Class: SecApi::RateLimitState

Inherits:
Dry::Struct
  • Object
show all
Defined in:
lib/sec_api/rate_limit_state.rb

Overview

Immutable value object representing rate limit state from sec-api.io response headers.

This class uses Dry::Struct for type safety and immutability, ensuring thread-safe access to rate limit information. The state is extracted from HTTP response headers:

  • X-RateLimit-Limit: Total requests allowed per time window

  • X-RateLimit-Remaining: Requests remaining in current window

  • X-RateLimit-Reset: Unix timestamp when the limit resets

Examples:

Access rate limit state from client

client = SecApi::Client.new
client.query.ticker("AAPL").search

state = client.rate_limit_state
state.limit        # => 100
state.remaining    # => 95
state.reset_at     # => 2026-01-07 12:00:00 +0000

Check if rate limit is exhausted

if client.rate_limit_state&.exhausted?
  sleep_until(client.rate_limit_state.reset_at)
end

Calculate percentage remaining for threshold checks

state = client.rate_limit_state
if state&.percentage_remaining && state.percentage_remaining < 10
  # Less than 10% remaining, consider throttling
end

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ RateLimitState

Override constructor to ensure immutability



125
126
127
128
# File 'lib/sec_api/rate_limit_state.rb', line 125

def initialize(attributes = {})
  super
  freeze
end

Instance Method Details

#available?Boolean

Checks if requests are available (not exhausted).

Returns true if remaining is greater than zero OR if remaining is unknown. This conservative approach assumes requests are available when state is unknown.

Examples:

state = RateLimitState.new(limit: 100, remaining: 5, reset_at: Time.now + 60)
state.available?  # => true

state = RateLimitState.new  # No headers received
state.available?  # => true (unknown state, assume available)

state = RateLimitState.new(limit: 100, remaining: 0, reset_at: Time.now + 60)
state.available?  # => false

Returns:

  • (Boolean)

    true if remaining > 0 or remaining is unknown, false if exhausted



91
92
93
# File 'lib/sec_api/rate_limit_state.rb', line 91

def available?
  !exhausted?
end

#exhausted?Boolean

Checks if the rate limit has been exhausted.

Returns true only when we know for certain that remaining requests is zero. Returns false if remaining is unknown (nil) or greater than zero.

Examples:

state = RateLimitState.new(limit: 100, remaining: 0, reset_at: Time.now + 60)
state.exhausted?  # => true

state = RateLimitState.new(limit: 100, remaining: 5, reset_at: Time.now + 60)
state.exhausted?  # => false

state = RateLimitState.new  # No headers received
state.exhausted?  # => false (unknown state, assume available)

Returns:

  • (Boolean)

    true if remaining requests is exactly 0, false otherwise



70
71
72
# File 'lib/sec_api/rate_limit_state.rb', line 70

def exhausted?
  remaining == 0
end

#limitInteger?

Total requests allowed per time window (from X-RateLimit-Limit header).

Returns:

  • (Integer, nil)

    The total quota, or nil if header was not present



43
# File 'lib/sec_api/rate_limit_state.rb', line 43

attribute? :limit, Types::Coercible::Integer.optional

#percentage_remainingFloat?

Calculates the percentage of rate limit quota remaining.

Returns nil if either limit or remaining is unknown, as percentage cannot be calculated without both values.

Examples:

Calculate percentage for threshold checking

state = RateLimitState.new(limit: 100, remaining: 25, reset_at: Time.now + 60)
state.percentage_remaining  # => 25.0

state = RateLimitState.new(limit: 100, remaining: 0, reset_at: Time.now + 60)
state.percentage_remaining  # => 0.0

Handle unknown state

state = RateLimitState.new  # No headers received
state.percentage_remaining  # => nil

if (pct = state.percentage_remaining) && pct < 10
  # Throttle when below 10%
end

Returns:

  • (Float, nil)

    Percentage remaining (0.0 to 100.0), or nil if unknown



117
118
119
120
121
122
# File 'lib/sec_api/rate_limit_state.rb', line 117

def percentage_remaining
  return nil if limit.nil? || remaining.nil?
  return 0.0 if limit.zero?

  (remaining.to_f / limit * 100).round(1)
end

#remainingInteger?

Requests remaining in current time window (from X-RateLimit-Remaining header).

Returns:

  • (Integer, nil)

    Remaining requests, or nil if header was not present



47
# File 'lib/sec_api/rate_limit_state.rb', line 47

attribute? :remaining, Types::Coercible::Integer.optional

#reset_atTime?

Time when the rate limit window resets (from X-RateLimit-Reset header).

Returns:

  • (Time, nil)

    Reset time, or nil if header was not present



51
# File 'lib/sec_api/rate_limit_state.rb', line 51

attribute? :reset_at, Types::Strict::Time.optional