prop 2.2.5 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/lib/prop.rb +1 -1
- data/lib/prop/interval_strategy.rb +11 -7
- data/lib/prop/leaky_bucket_strategy.rb +54 -20
- data/lib/prop/limiter.rb +30 -23
- data/lib/prop/options.rb +13 -6
- metadata +3 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: bf33dac797d0f3b3cf7126709f1622e3069b7cf476f0bfde211178b3e4e743e1
         | 
| 4 | 
            +
              data.tar.gz: e166a98af608dab54400289bb8fd28738b4ba8ab18ee0604613e75907ae4ec5c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 953571f21e59adbbd743490543fa4a7faf30b0e07951f3646bf7e3af5043ed2e24d0f93c6c5d375b1bb866ed5719e1eefe206bf4ce230b5001d80a7fc0f48cee
         | 
| 7 | 
            +
              data.tar.gz: ca8960bee3c4f2789d4ddc9a07e489ee646c45cbbe9391da6b76f7e06f81711224093806e60a49f85cd5f1451389868f22b58f1259283b33cf7ffad21665948b
         | 
    
        data/README.md
    CHANGED
    
    | @@ -18,7 +18,7 @@ To store values, prop needs a cache: | |
| 18 18 | 
             
            Prop.cache = Rails.cache # needs read/write/increment methods
         | 
| 19 19 | 
             
            ```
         | 
| 20 20 |  | 
| 21 | 
            -
             | 
| 21 | 
            +
            When using the interval strategy, prop sets a key expiry to its interval.  Because the leaky bucket strategy does not set a ttl, it is best to use memcached or similar for all prop caching, not redis.
         | 
| 22 22 |  | 
| 23 23 | 
             
            ## Setting a Callback
         | 
| 24 24 |  | 
| @@ -37,6 +37,9 @@ Example: Limit on accepted emails per hour from a given user, by defining a thre | |
| 37 37 |  | 
| 38 38 | 
             
            ```ruby
         | 
| 39 39 | 
             
            Prop.configure(:mails_per_hour, threshold: 100, interval: 1.hour, description: "Mail rate limit exceeded")
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            # Block requests by setting threshold to 0
         | 
| 42 | 
            +
            Prop.configure(:mails_per_hour, threshold: 0, interval: 1.hour, description: "All mail is blocked")
         | 
| 40 43 | 
             
            ```
         | 
| 41 44 |  | 
| 42 45 | 
             
            ```ruby
         | 
| @@ -224,6 +227,8 @@ You can add two additional configurations: `:strategy` and `:burst_rate` to use | |
| 224 227 | 
             
            Prop will handle the details after configured, and you don't have to specify `:strategy` 
         | 
| 225 228 | 
             
            again when using `throttle`, `throttle!` or any other methods.
         | 
| 226 229 |  | 
| 230 | 
            +
            The leaky bucket algorithm used is "leaky bucket as a meter".
         | 
| 231 | 
            +
             | 
| 227 232 | 
             
            ```ruby
         | 
| 228 233 | 
             
            Prop.configure(:api_request, strategy: :leaky_bucket, burst_rate: 20, threshold: 5, interval: 1.minute)
         | 
| 229 234 | 
             
            ```
         | 
    
        data/lib/prop.rb
    CHANGED
    
    
| @@ -16,16 +16,16 @@ module Prop | |
| 16 16 | 
             
                  # options argument is kept for api consistency for all strategies
         | 
| 17 17 | 
             
                  def increment(cache_key, amount, options = {})
         | 
| 18 18 | 
             
                    raise ArgumentError, "Change amount must be a Integer, was #{amount.class}" unless amount.is_a?(Integer)
         | 
| 19 | 
            -
                    cache.increment(cache_key, amount) || (cache.write(cache_key, amount, raw: true) && amount) # WARNING: potential race condition
         | 
| 19 | 
            +
                    cache.increment(cache_key, amount) || (cache.write(cache_key, amount, raw: true, expires_in: options.fetch(:interval, nil)) && amount) # WARNING: potential race condition
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                  def decrement(cache_key, amount, options = {})
         | 
| 23 23 | 
             
                    raise ArgumentError, "Change amount must be a Integer, was #{amount.class}" unless amount.is_a?(Integer)
         | 
| 24 | 
            -
                    cache.decrement(cache_key, amount) || (cache.write(cache_key, 0, raw: true) && 0) # WARNING: potential race condition
         | 
| 24 | 
            +
                    cache.decrement(cache_key, amount) || (cache.write(cache_key, 0, raw: true, expires_in: options.fetch(:interval, nil)) && 0) # WARNING: potential race condition
         | 
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 | 
            -
                  def reset(cache_key)
         | 
| 28 | 
            -
                    cache.write(cache_key, zero_counter, raw: true)
         | 
| 27 | 
            +
                  def reset(cache_key, options = {})
         | 
| 28 | 
            +
                    cache.write(cache_key, zero_counter, raw: true, expires_in: options.fetch(:interval, nil))
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 |  | 
| 31 31 | 
             
                  def compare_threshold?(counter, operator, options)
         | 
| @@ -56,8 +56,8 @@ module Prop | |
| 56 56 | 
             
                  end
         | 
| 57 57 |  | 
| 58 58 | 
             
                  def validate_options!(options)
         | 
| 59 | 
            -
                     | 
| 60 | 
            -
                     | 
| 59 | 
            +
                    validate_threshold(options[:threshold], :threshold)
         | 
| 60 | 
            +
                    validate_interval(options[:interval], :interval)
         | 
| 61 61 |  | 
| 62 62 | 
             
                    amount = options[:increment] || options[:decrement]
         | 
| 63 63 | 
             
                    if amount
         | 
| @@ -67,7 +67,11 @@ module Prop | |
| 67 67 |  | 
| 68 68 | 
             
                  private
         | 
| 69 69 |  | 
| 70 | 
            -
                  def  | 
| 70 | 
            +
                  def validate_threshold(option, key)
         | 
| 71 | 
            +
                    raise ArgumentError.new("#{key.inspect} must be a non-negative Integer") if !option.is_a?(Integer) || option < 0
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def validate_interval(option, key)
         | 
| 71 75 | 
             
                    raise ArgumentError.new("#{key.inspect} must be a positive Integer") if !option.is_a?(Integer) || option <= 0
         | 
| 72 76 | 
             
                  end
         | 
| 73 77 |  | 
| @@ -5,39 +5,69 @@ require 'prop/key' | |
| 5 5 | 
             
            module Prop
         | 
| 6 6 | 
             
              class LeakyBucketStrategy
         | 
| 7 7 | 
             
                class << self
         | 
| 8 | 
            +
                  def _throttle_leaky_bucket(handle, key, cache_key, options)
         | 
| 9 | 
            +
                    (over_limit, bucket) = options.key?(:decrement) ?
         | 
| 10 | 
            +
                      decrement(cache_key, options.fetch(:decrement), options) :
         | 
| 11 | 
            +
                      increment(cache_key, options.fetch(:increment, 1), options)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    [over_limit, bucket]
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 8 16 | 
             
                  def counter(cache_key, options)
         | 
| 9 | 
            -
                     | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 17 | 
            +
                    cache.read(cache_key) || zero_counter
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def leak_amount(bucket, amount, options, now)
         | 
| 21 | 
            +
                    leak_rate = (now - bucket.fetch(:last_leak_time, 0)) / options.fetch(:interval).to_f
         | 
| 22 | 
            +
                    leak_amount = (leak_rate * options.fetch(:threshold).to_f)
         | 
| 23 | 
            +
                    leak_amount.to_i
         | 
| 24 | 
            +
                  end
         | 
| 12 25 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
                     | 
| 15 | 
            -
                     | 
| 26 | 
            +
                  def update_bucket(current_bucket_size, max_bucket_size, amount)
         | 
| 27 | 
            +
                    over_limit = (max_bucket_size-current_bucket_size) < amount
         | 
| 28 | 
            +
                    updated_bucket_size = over_limit ? current_bucket_size : current_bucket_size + amount
         | 
| 29 | 
            +
                    [over_limit, updated_bucket_size]
         | 
| 16 30 | 
             
                  end
         | 
| 17 31 |  | 
| 18 32 | 
             
                  # WARNING: race condition
         | 
| 19 33 | 
             
                  # this increment is not atomic, so it might miss counts when used frequently
         | 
| 20 34 | 
             
                  def increment(cache_key, amount, options)
         | 
| 21 | 
            -
                     | 
| 22 | 
            -
                     | 
| 23 | 
            -
                     | 
| 24 | 
            -
                     | 
| 35 | 
            +
                    bucket = counter(cache_key, options)
         | 
| 36 | 
            +
                    now = Time.now.to_i
         | 
| 37 | 
            +
                    max_bucket_size = options.fetch(:burst_rate)
         | 
| 38 | 
            +
                    current_bucket_size = bucket.fetch(:bucket, 0)
         | 
| 39 | 
            +
                    leak_amount = leak_amount(bucket, amount, options, now)
         | 
| 40 | 
            +
                    if leak_amount > 0
         | 
| 41 | 
            +
                      # maybe TODO, update last_leak_time to reflect the exact time for the current leak amount
         | 
| 42 | 
            +
                      # the current strategy will always reflect a little less leakage, probably not an issue though
         | 
| 43 | 
            +
                      bucket[:last_leak_time] = now
         | 
| 44 | 
            +
                      current_bucket_size = [(current_bucket_size - leak_amount), 0].max
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    over_limit, updated_bucket_size = update_bucket(current_bucket_size, max_bucket_size, amount)
         | 
| 48 | 
            +
                    bucket[:bucket] = updated_bucket_size
         | 
| 49 | 
            +
                    bucket[:over_limit] = over_limit
         | 
| 50 | 
            +
                    cache.write(cache_key, bucket)
         | 
| 51 | 
            +
                    [over_limit, bucket]
         | 
| 25 52 | 
             
                  end
         | 
| 26 53 |  | 
| 27 54 | 
             
                  def decrement(cache_key, amount, options)
         | 
| 28 | 
            -
                     | 
| 29 | 
            -
                     | 
| 30 | 
            -
                     | 
| 31 | 
            -
                     | 
| 32 | 
            -
                     | 
| 55 | 
            +
                    now = Time.now.to_i
         | 
| 56 | 
            +
                    bucket = counter(cache_key, options)
         | 
| 57 | 
            +
                    leak_amount = leak_amount(bucket, amount, options, now)
         | 
| 58 | 
            +
                    bucket[:bucket] = [bucket[:bucket] - amount - leak_amount, 0].max
         | 
| 59 | 
            +
                    bucket[:last_leak_time] = now if leak_amount > 0
         | 
| 60 | 
            +
                    bucket[:over_limit] = false
         | 
| 61 | 
            +
                    cache.write(cache_key, bucket)
         | 
| 62 | 
            +
                    [false, bucket]
         | 
| 33 63 | 
             
                  end
         | 
| 34 64 |  | 
| 35 | 
            -
                  def reset(cache_key)
         | 
| 36 | 
            -
                     | 
| 65 | 
            +
                  def reset(cache_key, options = {})
         | 
| 66 | 
            +
                    cache.write(cache_key, zero_counter, raw: true)
         | 
| 37 67 | 
             
                  end
         | 
| 38 68 |  | 
| 39 | 
            -
                  def compare_threshold?( | 
| 40 | 
            -
                     | 
| 69 | 
            +
                  def compare_threshold?(bucket, operator, options)
         | 
| 70 | 
            +
                    bucket.fetch(:over_limit, false)
         | 
| 41 71 | 
             
                  end
         | 
| 42 72 |  | 
| 43 73 | 
             
                  def build(options)
         | 
| @@ -69,7 +99,11 @@ module Prop | |
| 69 99 | 
             
                  end
         | 
| 70 100 |  | 
| 71 101 | 
             
                  def zero_counter
         | 
| 72 | 
            -
                    { bucket: 0,  | 
| 102 | 
            +
                    { bucket: 0, last_leak_time: 0, over_limit: false }
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def cache
         | 
| 106 | 
            +
                    Prop::Limiter.cache
         | 
| 73 107 | 
             
                  end
         | 
| 74 108 | 
             
                end
         | 
| 75 109 | 
             
              end
         | 
    
        data/lib/prop/limiter.rb
    CHANGED
    
    | @@ -46,8 +46,7 @@ module Prop | |
| 46 46 | 
             
                  #
         | 
| 47 47 | 
             
                  # Raises Prop::RateLimited if the number if the threshold for this handle has been reached
         | 
| 48 48 | 
             
                  def configure(handle, defaults)
         | 
| 49 | 
            -
                     | 
| 50 | 
            -
                    raise ArgumentError.new("Invalid interval setting")  unless defaults[:interval].to_i > 0
         | 
| 49 | 
            +
                    Prop::Options.validate_options!(defaults)
         | 
| 51 50 |  | 
| 52 51 | 
             
                    self.handles ||= {}
         | 
| 53 52 | 
             
                    self.handles[handle] = defaults
         | 
| @@ -56,7 +55,7 @@ module Prop | |
| 56 55 | 
             
                  # Public: Disables Prop for a block of code
         | 
| 57 56 | 
             
                  #
         | 
| 58 57 | 
             
                  # block    - a block of code within which Prop will not raise
         | 
| 59 | 
            -
                  def disabled(& | 
| 58 | 
            +
                  def disabled(&_block)
         | 
| 60 59 | 
             
                    @disabled = true
         | 
| 61 60 | 
             
                    yield
         | 
| 62 61 | 
             
                  ensure
         | 
| @@ -72,8 +71,8 @@ module Prop | |
| 72 71 | 
             
                  #
         | 
| 73 72 | 
             
                  # Returns true if the threshold for this handle has been reached, else returns false
         | 
| 74 73 | 
             
                  def throttle(handle, key = nil, options = {})
         | 
| 75 | 
            -
                    options, cache_key = prepare(handle, key, options)
         | 
| 76 | 
            -
                    throttled = _throttle(handle, key, cache_key, options).first
         | 
| 74 | 
            +
                    options, cache_key, strategy = prepare(handle, key, options)
         | 
| 75 | 
            +
                    throttled = _throttle(strategy, handle, key, cache_key, options).first
         | 
| 77 76 | 
             
                    block_given? && !throttled ? yield : throttled
         | 
| 78 77 | 
             
                  end
         | 
| 79 78 |  | 
| @@ -87,8 +86,8 @@ module Prop | |
| 87 86 | 
             
                  # Raises Prop::RateLimited if the threshold for this handle has been reached
         | 
| 88 87 | 
             
                  # Returns the value of the block if given a such, otherwise the current count of the throttle
         | 
| 89 88 | 
             
                  def throttle!(handle, key = nil, options = {}, &block)
         | 
| 90 | 
            -
                    options, cache_key = prepare(handle, key, options)
         | 
| 91 | 
            -
                    throttled, counter = _throttle(handle, key, cache_key, options)
         | 
| 89 | 
            +
                    options, cache_key, strategy = prepare(handle, key, options)
         | 
| 90 | 
            +
                    throttled, counter = _throttle(strategy, handle, key, cache_key, options)
         | 
| 92 91 |  | 
| 93 92 | 
             
                    if throttled
         | 
| 94 93 | 
             
                      raise Prop::RateLimited.new(options.merge(
         | 
| @@ -108,9 +107,9 @@ module Prop | |
| 108 107 | 
             
                  #
         | 
| 109 108 | 
             
                  # Returns true if a call to `throttle!` with same parameters would raise, otherwise false
         | 
| 110 109 | 
             
                  def throttled?(handle, key = nil, options = {})
         | 
| 111 | 
            -
                    options, cache_key = prepare(handle, key, options)
         | 
| 112 | 
            -
                    counter =  | 
| 113 | 
            -
                     | 
| 110 | 
            +
                    options, cache_key, strategy = prepare(handle, key, options)
         | 
| 111 | 
            +
                    counter = strategy.counter(cache_key, options)
         | 
| 112 | 
            +
                    strategy.compare_threshold?(counter, :>=, options)
         | 
| 114 113 | 
             
                  end
         | 
| 115 114 |  | 
| 116 115 | 
             
                  # Public: Resets a specific throttle
         | 
| @@ -120,8 +119,8 @@ module Prop | |
| 120 119 | 
             
                  #
         | 
| 121 120 | 
             
                  # Returns nothing
         | 
| 122 121 | 
             
                  def reset(handle, key = nil, options = {})
         | 
| 123 | 
            -
                    _options, cache_key = prepare(handle, key, options)
         | 
| 124 | 
            -
                     | 
| 122 | 
            +
                    _options, cache_key, strategy = prepare(handle, key, options)
         | 
| 123 | 
            +
                    strategy.reset(cache_key, options)
         | 
| 125 124 | 
             
                  end
         | 
| 126 125 |  | 
| 127 126 | 
             
                  # Public: Counts the number of times the given handle/key combination has been hit in the current window
         | 
| @@ -131,8 +130,8 @@ module Prop | |
| 131 130 | 
             
                  #
         | 
| 132 131 | 
             
                  # Returns a count of hits in the current window
         | 
| 133 132 | 
             
                  def count(handle, key = nil, options = {})
         | 
| 134 | 
            -
                    options, cache_key = prepare(handle, key, options)
         | 
| 135 | 
            -
                     | 
| 133 | 
            +
                    options, cache_key, strategy = prepare(handle, key, options)
         | 
| 134 | 
            +
                    strategy.counter(cache_key, options)
         | 
| 136 135 | 
             
                  end
         | 
| 137 136 | 
             
                  alias :query :count
         | 
| 138 137 |  | 
| @@ -143,18 +142,26 @@ module Prop | |
| 143 142 |  | 
| 144 143 | 
             
                  private
         | 
| 145 144 |  | 
| 146 | 
            -
                  def  | 
| 147 | 
            -
                     | 
| 145 | 
            +
                  def leaky_bucket_strategy?(strategy)
         | 
| 146 | 
            +
                    strategy == Prop::LeakyBucketStrategy
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  def _throttle(strategy, handle, key, cache_key, options)
         | 
| 150 | 
            +
                    return [false, strategy.zero_counter] if disabled?
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    if leaky_bucket_strategy?(strategy)
         | 
| 153 | 
            +
                      return Prop::LeakyBucketStrategy._throttle_leaky_bucket(handle, key, cache_key, options)
         | 
| 154 | 
            +
                    end
         | 
| 148 155 |  | 
| 149 156 | 
             
                    counter = options.key?(:decrement) ?
         | 
| 150 | 
            -
                       | 
| 151 | 
            -
                       | 
| 157 | 
            +
                      strategy.decrement(cache_key, options.fetch(:decrement), options) :
         | 
| 158 | 
            +
                      strategy.increment(cache_key, options.fetch(:increment, 1), options)
         | 
| 152 159 |  | 
| 153 | 
            -
                    if  | 
| 160 | 
            +
                    if strategy.compare_threshold?(counter, :>, options)
         | 
| 154 161 | 
             
                      before_throttle_callback &&
         | 
| 155 162 | 
             
                        before_throttle_callback.call(handle, key, options[:threshold], options[:interval])
         | 
| 156 163 |  | 
| 157 | 
            -
                      result = if options[:first_throttled] &&  | 
| 164 | 
            +
                      result = if options[:first_throttled] && strategy.first_throttled?(counter, options)
         | 
| 158 165 | 
             
                        :first_throttled
         | 
| 159 166 | 
             
                      else
         | 
| 160 167 | 
             
                        true
         | 
| @@ -177,11 +184,11 @@ module Prop | |
| 177 184 |  | 
| 178 185 | 
             
                    options = Prop::Options.build(key: key, params: params, defaults: defaults)
         | 
| 179 186 |  | 
| 180 | 
            -
                     | 
| 187 | 
            +
                    strategy = options.fetch(:strategy)
         | 
| 181 188 |  | 
| 182 | 
            -
                    cache_key =  | 
| 189 | 
            +
                    cache_key = strategy.build(key: key, handle: handle, interval: options[:interval])
         | 
| 183 190 |  | 
| 184 | 
            -
                    [ options, cache_key ]
         | 
| 191 | 
            +
                    [ options, cache_key, strategy ]
         | 
| 185 192 | 
             
                  end
         | 
| 186 193 | 
             
                end
         | 
| 187 194 | 
             
              end
         | 
    
        data/lib/prop/options.rb
    CHANGED
    
    | @@ -12,17 +12,24 @@ module Prop | |
| 12 12 | 
             
                  result   = defaults.merge(params)
         | 
| 13 13 |  | 
| 14 14 | 
             
                  result[:key] = Prop::Key.normalize(key)
         | 
| 15 | 
            +
                  result[:strategy] = get_strategy(result)
         | 
| 15 16 |  | 
| 16 | 
            -
                  result[:strategy] | 
| 17 | 
            +
                  result[:strategy].validate_options!(result)
         | 
| 18 | 
            +
                  result
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def self.validate_options!(options)
         | 
| 22 | 
            +
                  get_strategy(options).validate_options!(options)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def self.get_strategy(options)
         | 
| 26 | 
            +
                  if leaky_bucket.include?(options[:strategy])
         | 
| 17 27 | 
             
                    Prop::LeakyBucketStrategy
         | 
| 18 | 
            -
                  elsif  | 
| 28 | 
            +
                  elsif options[:strategy] == nil
         | 
| 19 29 | 
             
                    Prop::IntervalStrategy
         | 
| 20 30 | 
             
                  else
         | 
| 21 | 
            -
                     | 
| 31 | 
            +
                    options[:strategy] # allowing any new/unknown strategy to be used
         | 
| 22 32 | 
             
                  end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  result[:strategy].validate_options!(result)
         | 
| 25 | 
            -
                  result
         | 
| 26 33 | 
             
                end
         | 
| 27 34 |  | 
| 28 35 | 
             
                def self.leaky_bucket
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: prop
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.6.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Morten Primdahl
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-08-12 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rake
         | 
| @@ -115,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 115 115 | 
             
                - !ruby/object:Gem::Version
         | 
| 116 116 | 
             
                  version: '0'
         | 
| 117 117 | 
             
            requirements: []
         | 
| 118 | 
            -
             | 
| 119 | 
            -
            rubygems_version: 2.7.6
         | 
| 118 | 
            +
            rubygems_version: 3.1.6
         | 
| 120 119 | 
             
            signing_key: 
         | 
| 121 120 | 
             
            specification_version: 4
         | 
| 122 121 | 
             
            summary: Gem for implementing rate limits.
         |